// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package diff import ( "strings" "testing" ) func render(chunks []Chunk) string { var b strings.Builder for _, c := range chunks { switch c.Op { case Equal: for _, l := range c.Lines { b.WriteString(" " + l + "\n") } case Removed: for _, l := range c.Lines { b.WriteString("- " + l + "\n") } case Added: for _, l := range c.Lines { b.WriteString("+ " + l + "\n") } case Skipped: b.WriteString("… " + strings.Repeat("#", c.Skipped) + "\n") } } return b.String() } func TestIdentical(t *testing.T) { got := render(Chunks("alpha\nbeta\n", "alpha\nbeta\n", 3)) if got != " alpha\n beta\n" { t.Errorf("identical diff = %q", got) } } func TestSingleChange(t *testing.T) { got := render(Chunks("alpha\nbeta\ngamma\n", "alpha\nbeta+\ngamma\n", 3)) want := " alpha\n- beta\n+ beta+\n gamma\n" if got != want { t.Errorf("change diff = %q, want %q", got, want) } } func TestInsertAndDelete(t *testing.T) { got := render(Chunks("one\ntwo\n", "one\nthree\nfour\ntwo\n", 3)) want := " one\n+ three\n+ four\n two\n" if got != want { t.Errorf("insert diff = %q, want %q", got, want) } got = render(Chunks("one\nthree\ntwo\n", "one\ntwo\n", 3)) want = " one\n- three\n two\n" if got != want { t.Errorf("delete diff = %q, want %q", got, want) } } func TestEmptySides(t *testing.T) { got := render(Chunks("", "alpha\n", 3)) if got != "+ alpha\n" { t.Errorf("empty old = %q", got) } got = render(Chunks("alpha\n", "", 3)) if got != "- alpha\n" { t.Errorf("empty new = %q", got) } got = render(Chunks("", "", 3)) if got != "" { t.Errorf("both empty = %q", got) } } func TestContextCollapse(t *testing.T) { // Twelve equal lines between two changes: with context 3 the middle // six collapse into one skipped chunk. old := "x1\nx2\nx3\nx4\nx5\nx6\nx7\nx8\nx9\nx10\nx11\nx12\nx13\nx14\n" new := "A1\nx2\nx3\nx4\nx5\nx6\nx7\nx8\nx9\nx10\nx11\nx12\nx13\nA14\n" got := render(Chunks(old, new, 3)) want := "- x1\n+ A1\n" + " x2\n x3\n x4\n" + "… ######\n" + " x11\n x12\n x13\n" + "- x14\n+ A14\n" if got != want { t.Errorf("collapse diff = %q, want %q", got, want) } } func TestTrailingNewlineIsNotALine(t *testing.T) { with := render(Chunks("alpha\n", "alpha\n", 3)) without := render(Chunks("alpha", "alpha", 3)) if with != without { t.Errorf("trailing newline changed the diff: %q vs %q", with, without) } } func TestCellBudgetFallsBackToReplace(t *testing.T) { // 2500 equal lines on both sides exceeds the cell budget; the diff // must degrade to a full replacement instead of hanging. var b strings.Builder for range 2500 { b.WriteString(strings.Repeat("x", 40) + "\n") } big := b.String() chunks := Chunks(big, big, 3) var removed, added bool for _, c := range chunks { switch c.Op { case Removed: removed = true case Added: added = true } } if !removed || !added { t.Errorf("oversized input did not degrade to replacement (removed=%v added=%v)", removed, added) } } func TestOpIs(t *testing.T) { if !Removed.Is("removed") || Added.Is("removed") || Skipped.Is("skipped") == false { t.Error("Op.Is misclassifies") } }