package diff
import (
"strings"
"testing"
)
// The ticket's own motivating case, from pikchr a7f1c35b: one line
// gains "(including" at its end. The reader must see THAT — a marked
// range on each side — not two fully-coloured lines to diff by eye.
func TestIntraLineMarks(t *testing.T) {
u := `--- a/pikchr.y
+++ b/pikchr.y
@@ -5220,7 +5220,8 @@ static void x(void){
int i;
- /* Remove leading and trailing whitespace from each argument.
+ /* Remove leading and trailing whitespace from each argument (including
more;
`
files := Parse(u)
if len(files) != 1 || files[0].Path != "pikchr.y" || files[0].Change != Change {
t.Fatalf("files = %+v", files)
}
h := files[0].Hunks[0]
if h.OldStart != 5220 || h.NewStart != 5220 || h.Header != "static void x(void){" {
t.Fatalf("hunk = %+v", h)
}
// Numbers: context carries both, deletion old-only, addition new-only.
if h.Lines[0].OldNum != 5220 || h.Lines[0].NewNum != 5220 ||
h.Lines[1].OldNum != 5221 || h.Lines[1].NewNum != 0 ||
h.Lines[2].NewNum != 5221 || h.Lines[2].OldNum != 0 ||
h.Lines[3].OldNum != 5222 || h.Lines[3].NewNum != 5222 {
t.Fatalf("numbering wrong: %+v", h.Lines)
}
Enrich(files)
del, add := files[0].Hunks[0].Lines[1], files[0].Hunks[0].Lines[2]
marked := func(l Line) string {
var b strings.Builder
for _, tok := range l.Tokens {
if tok.Mark {
b.WriteString(tok.Text)
}
}
return b.String()
}
if got := marked(add); !strings.Contains(got, "(including") {
t.Fatalf("addition marks = %q, want the added words", got)
}
if got := marked(add); strings.Contains(got, "Remove leading") {
t.Fatalf("addition over-marked the unchanged text: %q", got)
}
if got := marked(del); strings.Contains(got, "Remove leading") {
t.Fatalf("deletion over-marked: %q", got)
}
// Syntax classes arrived positionally (pikchr.y matches a lexer via
// the .y suffix); reassembling all tokens must reproduce the text
// exactly — the splice must never lose or duplicate a byte.
var whole strings.Builder
for _, tok := range add.Tokens {
whole.WriteString(tok.Text)
}
if whole.String() != " /* Remove leading and trailing whitespace from each argument (including" {
t.Fatalf("splice corrupted the text: %q", whole.String())
}
}
// A new file and a deleted file are CHANGE KINDS, not diffs against
// /dev/null; and total rewrites mark nothing — a fully-marked pair
// says less than plain colour.
func TestChangeKindsAndRewriteGuard(t *testing.T) {
u := `--- /dev/null
+++ b/fresh.go
@@ -0,0 +1,1 @@
+package fresh
--- a/gone.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package gone
`
files := Parse(u)
if len(files) != 2 || files[0].Change != Added || files[0].Path != "fresh.go" ||
files[1].Change != Deleted || files[1].Path != "gone.go" {
t.Fatalf("kinds = %+v", files)
}
da, db := wordDiff("completely different text here", "nothing shared at all now")
if da != nil || db != nil {
t.Fatalf("a rewrite was word-marked: %v %v", da, db)
}
}
// Per-line tokenization meets multi-line constructs it cannot close —
// a raw string's backtick, a diff's @@ inside a string — and chroma
// answers with Error tokens, which the stylesheet paints as red
// warning boxes over healthy code. A lexer's shrug must render PLAIN.
func TestLexerErrorsRenderPlain(t *testing.T) {
u := "--- a/x.go\n+++ b/x.go\n@@ -1,1 +1,2 @@\n context\n+\tu := `--- embedded\n"
files := Parse(u)
Enrich(files)
for _, l := range files[0].Hunks[0].Lines {
for _, tok := range l.Tokens {
if tok.Class == "err" {
t.Fatalf("an Error token leaked a class: %+v", l.Tokens)
}
}
}
}