internal/httpd/diff_test.go

aa03e8c8e02357cfe5f94188fdab3c79846cebae
gitbay/internal/httpd/diff_test.go history · blame · raw

208 lines · 5669 bytes

  1package httpd
  2
  3import (
  4	"strings"
  5	"testing"
  6)
  7
  8const samplePatch = `diff --git a/main.go b/main.go
  9index 1234567..89abcde 100644
 10--- a/main.go
 11+++ b/main.go
 12@@ -1,6 +1,7 @@
 13 package main
 14 
 15-func old() string {
 16-	return "a"
 17+func replaced() string {
 18+	// a comment
 19+	return "b"
 20 }
 21diff --git a/notes.txt b/README.md
 22similarity index 60%
 23rename from notes.txt
 24rename to README.md
 25--- a/notes.txt
 26+++ b/README.md
 27@@ -1 +1 @@
 28-old title
 29+new title
 30diff --git a/logo.png b/logo.png
 31new file mode 100644
 32index 0000000..1111111
 33Binary files /dev/null and b/logo.png differ
 34`
 35
 36func TestParseDiffFiles(t *testing.T) {
 37	files := parseDiff(samplePatch)
 38	if len(files) != 3 {
 39		t.Fatalf("got %d files, want 3", len(files))
 40	}
 41
 42	if got := files[0].Path; got != "main.go" {
 43		t.Errorf("file 0 path = %q", got)
 44	}
 45	if files[0].Adds != 3 || files[0].Dels != 2 {
 46		t.Errorf("main.go stat = +%d -%d, want +3 -2", files[0].Adds, files[0].Dels)
 47	}
 48	if files[0].Status != "modified" {
 49		t.Errorf("main.go status = %q", files[0].Status)
 50	}
 51
 52	if files[1].Status != "renamed" || files[1].OldPath != "notes.txt" || files[1].Path != "README.md" {
 53		t.Errorf("rename = %q %q -> %q", files[1].Status, files[1].OldPath, files[1].Path)
 54	}
 55
 56	if !files[2].Binary || files[2].Status != "added" || files[2].Path != "logo.png" {
 57		t.Errorf("binary add = %+v", files[2])
 58	}
 59	if len(files[2].Lines) != 0 {
 60		t.Errorf("binary file has %d lines, want none", len(files[2].Lines))
 61	}
 62
 63	if st := statOf(files); st.Files != 3 || st.Adds != 4 || st.Dels != 3 {
 64		t.Errorf("stat = %+v, want 3 files +4 -3", st)
 65	}
 66}
 67
 68// Line numbers anchor review threads, so an off-by-one here silently moves
 69// every comment on a merge request.
 70func TestParseDiffLineNumbers(t *testing.T) {
 71	lines := parseDiff(samplePatch)[0].Lines
 72	type want struct {
 73		class    string
 74		old, new int64
 75		content  string
 76	}
 77	wants := []want{
 78		{"hunk", 0, 0, ""},
 79		{"ctx", 1, 1, "package main"},
 80		{"ctx", 2, 2, ""},
 81		{"del", 3, 0, "func old() string {"},
 82		{"del", 4, 0, "\treturn \"a\""},
 83		{"add", 0, 3, "func replaced() string {"},
 84		{"add", 0, 4, "\t// a comment"},
 85		{"add", 0, 5, "\treturn \"b\""},
 86		{"ctx", 5, 6, "}"},
 87	}
 88	if len(lines) != len(wants) {
 89		t.Fatalf("got %d lines, want %d: %+v", len(lines), len(wants), lines)
 90	}
 91	for i, w := range wants {
 92		got := lines[i]
 93		if got.Class != w.class || got.OldLine != w.old || got.NewLine != w.new {
 94			t.Errorf("line %d = %s old=%d new=%d, want %s old=%d new=%d",
 95				i, got.Class, got.OldLine, got.NewLine, w.class, w.old, w.new)
 96		}
 97		if w.class != "hunk" && got.Content != w.content {
 98			t.Errorf("line %d content = %q, want %q", i, got.Content, w.content)
 99		}
100	}
101}
102
103// Highlighting runs per hunk side and is mapped back line by line; the
104// mapping is what breaks, so check that every code line got markup and that
105// it still says what the source said.
106func TestParseDiffHighlighting(t *testing.T) {
107	for _, l := range parseDiff(samplePatch)[0].Lines {
108		if l.Class == "hunk" || l.Content == "" {
109			continue
110		}
111		if l.Code == "" {
112			t.Errorf("%s line %q got no highlighted markup", l.Class, l.Content)
113			continue
114		}
115		if text := strings.TrimSpace(stripTags(string(l.Code))); text != strings.TrimSpace(l.Content) {
116			t.Errorf("highlighted %q reads as %q", l.Content, text)
117		}
118	}
119}
120
121// chroma wraps each line in <span class="line">, and its stylesheet gives
122// that display:flex. A diff row is already one line, so the wrapper only
123// serves to break the +/- marker onto a row of its own.
124func TestParseDiffHasNoLineWrappers(t *testing.T) {
125	for _, l := range parseDiff(samplePatch)[0].Lines {
126		if strings.Contains(string(l.Code), `class="line"`) {
127			t.Errorf("%s line %q kept chroma's line wrapper: %s", l.Class, l.Content, l.Code)
128		}
129	}
130}
131
132// A file whose type chroma does not know renders as plain text rather than
133// being guessed at.
134func TestParseDiffUnknownType(t *testing.T) {
135	files := parseDiff(`diff --git a/x.zzz b/x.zzz
136--- a/x.zzz
137+++ b/x.zzz
138@@ -1 +1 @@
139-before
140+after
141`)
142	if len(files) != 1 {
143		t.Fatalf("got %d files", len(files))
144	}
145	for _, l := range files[0].Lines {
146		if l.Class == "hunk" {
147			continue
148		}
149		if l.Code != "" {
150			t.Errorf("unknown type got markup: %q", l.Code)
151		}
152	}
153}
154
155// splitHighlighted has to close and reopen spans that straddle a newline,
156// or one unterminated tag swallows the rest of the file.
157func TestSplitHighlightedBalancesTags(t *testing.T) {
158	got := splitHighlighted(`<span class="c">line one
159line two</span>plain`)
160	want := []string{`<span class="c">line one</span>`, `<span class="c">line two</span>plain`}
161	if len(got) != len(want) {
162		t.Fatalf("got %d lines: %q", len(got), got)
163	}
164	for i := range want {
165		if got[i] != want[i] {
166			t.Errorf("line %d = %q, want %q", i, got[i], want[i])
167		}
168	}
169}
170
171func stripTags(s string) string {
172	var b strings.Builder
173	depth := 0
174	for _, r := range s {
175		switch {
176		case r == '<':
177			depth++
178		case r == '>':
179			depth--
180		case depth == 0:
181			b.WriteRune(r)
182		}
183	}
184	return strings.ReplaceAll(b.String(), "&#34;", `"`)
185}
186
187// An added line whose own text begins with a dash — a Markdown bullet, a
188// docstring list — keeps it. Deriving the highlighting source from the raw
189// line means stripping the marker, and stripping "+" then "-" takes the
190// content's dash along with it.
191func TestParseDiffKeepsDashAfterMarker(t *testing.T) {
192	files := parseDiff(`diff --git a/README.md b/README.md
193--- a/README.md
194+++ b/README.md
195@@ -1,2 +1,3 @@
196 - one
197+- two
198 - three
199`)
200	for _, l := range files[0].Lines {
201		if l.Class != "add" {
202			continue
203		}
204		if text := stripTags(string(l.Code)); text != l.Content {
205			t.Errorf("added line %q highlighted as %q", l.Content, text)
206		}
207	}
208}