discourse-ai/spec/lib/personas/artifact_update_strategies/diff_spec.rb

243 lines
5.9 KiB
Ruby

# frozen_string_literal: true
RSpec.describe DiscourseAi::Personas::ArtifactUpdateStrategies::Diff do
fab!(:user)
fab!(:post)
fab!(:artifact) { Fabricate(:ai_artifact) }
fab!(:llm_model)
let(:llm) { llm_model.to_llm }
let(:instructions) { "Update the button color to red" }
let(:strategy) do
described_class.new(
llm: llm,
post: post,
user: user,
artifact: artifact,
artifact_version: nil,
instructions: instructions,
)
end
describe "#apply" do
it "processes simple search/replace blocks" do
original_css = ".button { color: blue; }"
artifact.update!(css: original_css)
response = <<~RESPONSE
[CSS]
<<<<<<< SEARCH
.button { color: blue; }
=======
.button { color: red; }
>>>>>>> REPLACE
[/CSS]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expect(artifact.versions.last.css).to eq(".button { color: red; }")
end
it "handles multiple search/replace blocks in the same section" do
original_css = <<~CSS
.button { color: blue; }
.text { font-size: 12px; }
CSS
artifact.update!(css: original_css)
response = <<~RESPONSE
[CSS]
<<<<<<< SEARCH
.button { color: blue; }
=======
.button { color: red; }
>>>>>>> REPLACE
<<<<<<< SEARCH
.text { font-size: 12px; }
=======
.text { font-size: 16px; }
>>>>>>> REPLACE
[/CSS]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expected = <<~CSS.strip
.button { color: red; }
.text { font-size: 16px; }
CSS
expect(artifact.versions.last.css.strip).to eq(expected.strip)
end
it "handles non-contiguous search/replace using ..." do
original_css = <<~CSS
body {
color: red;
}
.button {
color: blue;
}
.alert {
background-color: green;
}
CSS
artifact.update!(css: original_css)
response = <<~RESPONSE
[CSS]
<<<<<<< SEARCH
body {
...
background-color: green;
}
=======
body {
color: red;
}
>>>>>>> REPLACE
[/CSS]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expect(artifact.versions.last.css).to eq("body {\n color: red;\n}")
end
it "can handle removal with blank blocks" do
original_css = <<~CSS
body {
color: red;
}
.button {
color: blue;
}
CSS
artifact.update!(css: original_css)
response = <<~RESPONSE
[CSS]
<<<<<<< SEARCH
body {
color: red;
}
=======
>>>>>>> REPLACE
[/CSS]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expect(artifact.versions.last.css.strip).to eq(".button {\n color: blue;\n}")
end
it "tracks failed searches" do
original_css = ".button { color: blue; }"
artifact.update!(css: original_css)
response = <<~RESPONSE
[CSS]
<<<<<<< SEARCH
.button { color: green; }
=======
.button { color: red; }
>>>>>>> REPLACE
[/CSS]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expect(strategy.failed_searches).to contain_exactly(
{ section: :css, search: ".button { color: green; }" },
)
expect(artifact.versions.last.css).to eq(original_css)
end
it "handles complete section replacements" do
original_html = "<div>old content</div>"
artifact.update!(html: original_html)
response = <<~RESPONSE
[HTML]
<div>new content</div>
[/HTML]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
expect(artifact.versions.last.html.strip).to eq("<div>new content</div>")
end
it "ignores empty or 'no changes' sections part 1" do
original = {
html: "<div>content</div>",
css: ".button { color: blue; }",
js: "console.log('test');",
}
artifact.update!(html: original[:html], css: original[:css], js: original[:js])
response = <<~RESPONSE
[HTML]
no changes
[/HTML]
[CSS]
(NO CHANGES)
[/CSS]
[JavaScript]
<<<<<<< SEARCH
console.log('test');
=======
console.log('(no changes)');
>>>>>>> REPLACE
[/JavaScript]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
version = artifact.versions.last
expect(version.html).to eq(original[:html])
expect(version.css).to eq(original[:css])
expect(version.js).to eq("console.log('(no changes)');")
end
it "ignores empty or 'no changes' section part 2" do
original = {
html: "<div>content</div>",
css: ".button { color: blue; }",
js: "console.log('test');",
}
artifact.update!(html: original[:html], css: original[:css], js: original[:js])
response = <<~RESPONSE
[HTML]
(no changes)
[/HTML]
[CSS]
[/CSS]
[JavaScript]
<<<<<<< SEARCH
console.log('test');
=======
console.log('updated');
>>>>>>> REPLACE
[/JavaScript]
RESPONSE
DiscourseAi::Completions::Llm.with_prepared_responses([response]) { strategy.apply }
version = artifact.versions.last
expect(version.html).to eq(original[:html])
expect(version.css).to eq(original[:css])
expect(version.js).to eq("console.log('updated');")
end
end
end