internal/httpd/diff_test.go

v1.0.0
gitbay/internal/httpd/diff_test.go history · blame · raw

174 lines · 4552 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// A file whose type chroma does not know renders as plain text rather than
122// being guessed at.
123func TestParseDiffUnknownType(t *testing.T) {
124	files := parseDiff(`diff --git a/x.zzz b/x.zzz
125--- a/x.zzz
126+++ b/x.zzz
127@@ -1 +1 @@
128-before
129+after
130`)
131	if len(files) != 1 {
132		t.Fatalf("got %d files", len(files))
133	}
134	for _, l := range files[0].Lines {
135		if l.Class == "hunk" {
136			continue
137		}
138		if l.Code != "" {
139			t.Errorf("unknown type got markup: %q", l.Code)
140		}
141	}
142}
143
144// splitHighlighted has to close and reopen spans that straddle a newline,
145// or one unterminated tag swallows the rest of the file.
146func TestSplitHighlightedBalancesTags(t *testing.T) {
147	got := splitHighlighted(`<span class="c">line one
148line two</span>plain`)
149	want := []string{`<span class="c">line one</span>`, `<span class="c">line two</span>plain`}
150	if len(got) != len(want) {
151		t.Fatalf("got %d lines: %q", len(got), got)
152	}
153	for i := range want {
154		if got[i] != want[i] {
155			t.Errorf("line %d = %q, want %q", i, got[i], want[i])
156		}
157	}
158}
159
160func stripTags(s string) string {
161	var b strings.Builder
162	depth := 0
163	for _, r := range s {
164		switch {
165		case r == '<':
166			depth++
167		case r == '>':
168			depth--
169		case depth == 0:
170			b.WriteRune(r)
171		}
172	}
173	return strings.ReplaceAll(b.String(), "&#34;", `"`)
174}