internal/httpd/diff_test.go
208 lines · 5669 bytes
9 symbols in this file
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(), """, `"`)
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}