# frozen_string_literal: true RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do subject { described_class } describe ".apply" do it "raises error for nil inputs" do expect { subject.apply(nil, "search", "replace") }.to raise_error(ArgumentError) expect { subject.apply("content", nil, "replace") }.to raise_error(ArgumentError) expect { subject.apply("content", "search", nil) }.to raise_error(ArgumentError) end it "prioritizes exact matches over all fuzzy matches" do content = <<~TEXT line 1 line 1 lin 1 TEXT search = " line 1" replace = " new_line" expected = <<~TEXT line 1 new_line lin 1 TEXT expect(subject.apply(content, search, replace).strip).to eq(expected.strip) end it "raises error when no match is found" do content = "line1\ncompletely_different\nline3" search = "nothing_like_this" replace = "new_line" expect { subject.apply(content, search, replace) }.to raise_error( DiscourseAi::Utils::DiffUtils::SimpleDiff::NoMatchError, ) end it "replaces all matching occurrences" do content = "line1\nline2\nmiddle\nline2\nend" search = "line2" replace = "new_line2" expect(subject.apply(content, search, replace)).to eq( "line1\nnew_line2\nmiddle\nnew_line2\nend", ) end it "replaces exact matches" do content = "line1\nline2\nline3" search = "line2" replace = "new_line2" expect(subject.apply(content, search, replace)).to eq("line1\nnew_line2\nline3") end it "handles multi-line replacements" do content = "start\nline1\nline2\nend" search = "line1\nline2" replace = "new_line" expect(subject.apply(content, search, replace)).to eq("start\nnew_line\nend") end it "is forgiving of whitespace differences" do content = "line1\n line2\nline3" search = "line2" replace = "new_line2" expect(subject.apply(content, search, replace).strip).to eq("line1\n new_line2\nline3") end it "is forgiving of small character differences" do content = "line one one one\nlin2\nline three three" # Notice 'lin2' instead of 'line2' search = "line2" replace = "new_line2" expect(subject.apply(content, search, replace)).to eq( "line one one one\nnew_line2\nline three three", ) end it "is forgiving in multi-line blocks with indentation differences" do content = "def method\n line1\n line2\nend" search = "line1\nline2" replace = "new_content" expect(subject.apply(content, search, replace)).to eq("def method\nnew_content\nend") end it "handles CSS blocks in different orders" do content = <<~CSS .first { color: red; padding: 10px; } .second { color: blue; margin: 20px; } CSS search = <<~CSS .second { color: blue; margin: 20px; } .first { color: red; padding: 10px; } CSS replace = <<~CSS .new-block { color: green; } CSS expected = <<~CSS .new-block { color: green; } CSS expect(subject.apply(content, search, replace)).to eq(expected.strip) end it "handles partial line matches" do content = "abc hello efg\nabc hello efg" search = "hello" replace = "bob" expect(subject.apply(content, search, replace)).to eq("abc bob efg\nabc bob efg") end it "handles JavaScript blocks in different orders" do content = <<~JS function first() { const x = 1; return x + 2; } function second() { if (true) { return 42; } return 0; } JS search = <<~JS function second() { if (true) { return 42; } return 0; } function first() { const x = 1; return x + 2; } JS replace = <<~JS function replacement() { return 'new'; } JS expected = <<~JS function replacement() { return 'new'; } JS expect(subject.apply(content, search, replace).strip).to eq(expected.strip) end it "handles missing lines in search" do original = <<~TEXT line1 line2 line3 line4 line5 line1 line2 TEXT search = <<~TEXT line1 ... line3 ... line1 TEXT replace = "" expected = <<~TEXT line2 TEXT expect(subject.apply(original, search, replace).strip).to eq(expected.strip) end end end