# 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 = "