internal/httpd/difflayout_test.go
202 lines · 6691 bytes
11 symbols in this file
1package httpd
2
3import (
4 "net/http/httptest"
5 "strings"
6 "testing"
7
8 "gitbay.org/gitbay/internal/config"
9 "gitbay.org/gitbay/internal/store"
10 "gitbay.org/gitbay/internal/web"
11)
12
13const layoutPatch = `diff --git a/a.txt b/a.txt
14--- a/a.txt
15+++ b/a.txt
16@@ -1,4 +1,4 @@
17 keep
18-old one
19-old two
20+new one
21 tail
22`
23
24func renderCommitDiff(t *testing.T, layout diffLayout, mutate func([]diffFile)) string {
25 t.Helper()
26 files := parseDiff(layoutPatch)
27 if mutate != nil {
28 mutate(files)
29 }
30 if layout.Split {
31 splitFiles(files)
32 }
33 var sb strings.Builder
34 if err := web.Render(&sb, "commit.html", struct {
35 repoPage
36 SHA, ShortSHA, AuthorName, AuthorEmail, AuthorUser, CommitterEmail, Date, Message string
37 Parents []string
38 Sig sigView
39 Checks []store.CommitStatus
40 DiffFiles []diffFile
41 DiffTruncated bool
42 Layout diffLayout
43 }{repoPage: testRepoPage(), SHA: strings.Repeat("a", 40), ShortSHA: "aaaaaaaaaa", DiffFiles: files, Layout: layout}); err != nil {
44 t.Fatalf("render: %v", err)
45 }
46 return sb.String()
47}
48
49func TestSplitRowsPairDeletionsWithAdditions(t *testing.T) {
50 files := parseDiff(layoutPatch)
51 splitFiles(files)
52 var pairs []splitRow
53 for _, r := range files[0].Rows {
54 if r.Kind == "pair" {
55 pairs = append(pairs, r)
56 }
57 }
58 if len(pairs) != 4 {
59 t.Fatalf("want 4 pair rows, got %d", len(pairs))
60 }
61 if pairs[1].Old == nil || pairs[1].New == nil || pairs[1].Old.OldLine != 2 || pairs[1].New.NewLine != 2 {
62 t.Errorf("first change row does not pair old 2 with new 2: %+v", pairs[1])
63 }
64 if pairs[2].Old == nil || pairs[2].Old.OldLine != 3 || pairs[2].New != nil {
65 t.Errorf("surplus deletion is not left-only: %+v", pairs[2])
66 }
67}
68
69func TestDiffRendersUnified(t *testing.T) {
70 out := renderCommitDiff(t, diffLayout{UnifiedURL: "/x?layout=unified", SplitURL: "/x?layout=split"}, nil)
71 if strings.Contains(out, `difftable split`) {
72 t.Error("unified layout rendered the split table")
73 }
74 if !strings.Contains(out, `<a href="/x?layout=split">split</a>`) {
75 t.Errorf("no link to the split layout:\n%s", out)
76 }
77}
78
79func TestDiffRendersSplit(t *testing.T) {
80 out := renderCommitDiff(t, diffLayout{Split: true, UnifiedURL: "/x?layout=unified", SplitURL: "/x?layout=split"}, nil)
81 if !strings.Contains(out, `class="difftable split"`) {
82 t.Fatalf("no split table:\n%s", out)
83 }
84 for _, want := range []string{`id="f0-o2"`, `id="f0-o3"`, `id="f0-n2"`, `id="f0-n1"`, `<a href="/x?layout=unified">unified</a>`} {
85 if !strings.Contains(out, want) {
86 t.Errorf("split output lacks %s", want)
87 }
88 }
89 if strings.Count(out, `id="f0-n1"`) != 1 {
90 t.Error("a context line's id appears more than once")
91 }
92 if !strings.Contains(out, `class="ln none"`) {
93 t.Error("the surplus deletion has no empty right side")
94 }
95}
96
97// Each column's line number links its own side, and the layout override
98// travels with the link.
99func TestSplitCommentLinksKeepSides(t *testing.T) {
100 files := parseDiff(layoutPatch)
101 splitFiles(files)
102 var sb strings.Builder
103 rp := testRepoPage()
104 rp.Viewer = "alice"
105 err := web.Render(&sb, "mr.html", mrPageData{repoPage: rp, MR: testMR("open"), View: "diff",
106 DiffFiles: files, Layout: diffLayout{Split: true, Carry: "split"}})
107 if err != nil {
108 t.Fatalf("render: %v", err)
109 }
110 out := sb.String()
111 for _, want := range []string{
112 `cpath=a.txt&cline=2&cside=old&layout=split#compose`,
113 `cpath=a.txt&cline=2&cside=new&layout=split#compose`,
114 } {
115 if !strings.Contains(out, want) {
116 i := strings.Index(out, "cpath")
117 t.Errorf("lacks %s; sample %q", want, out[max(i-50, 0):min(i+150, len(out))])
118 }
119 }
120}
121
122func TestDiffLayoutForQueryOverridesAccount(t *testing.T) {
123 st, err := store.Open(":memory:")
124 if err != nil {
125 t.Fatal(err)
126 }
127 defer st.Close()
128 if err := st.MigrateUp(); err != nil {
129 t.Fatal(err)
130 }
131 uid, _ := st.CreateUser("alice", false)
132 cfg := config.Default()
133 cfg.Web.Mode = "accounts"
134 s := New(cfg, st, nil)
135 ck := sessionCookieFor(t, s, st, uid)
136
137 get := func(target string) diffLayout {
138 req := httptest.NewRequest("GET", target, nil)
139 req.AddCookie(ck)
140 return s.diffLayoutFor(req)
141 }
142 if get("/o/r/commit/abc").Split {
143 t.Error("a new account defaults to split")
144 }
145 if err := st.SetDiffLayout(uid, "split"); err != nil {
146 t.Fatal(err)
147 }
148 if l := get("/o/r/commit/abc"); !l.Split || l.Carry != "" {
149 t.Errorf("account setting ignored: %+v", l)
150 }
151 if l := get("/o/r/commit/abc?layout=unified"); l.Split || l.Carry != "unified" {
152 t.Errorf("?layout=unified did not override: %+v", l)
153 }
154 l := get("/o/r/compare?base=main&head=x")
155 if !strings.Contains(l.UnifiedURL, "base=main") || !strings.Contains(l.UnifiedURL, "layout=unified") {
156 t.Errorf("switch link dropped the query: %s", l.UnifiedURL)
157 }
158 anon := httptest.NewRequest("GET", "/o/r/commit/abc?layout=split", nil)
159 if !s.diffLayoutFor(anon).Split {
160 t.Error("?layout=split ignored for a signed-out reader")
161 }
162}
163
164func splitPairs(patch string) []splitRow {
165 files := parseDiff(patch)
166 splitFiles(files)
167 var out []splitRow
168 for _, r := range files[0].Rows {
169 out = append(out, r)
170 }
171 return out
172}
173
174const noNL = `\ No newline at end of file`
175
176func TestSplitNoNewlineMarkerPairs(t *testing.T) {
177 head := "diff --git a/a.txt b/a.txt\n--- a/a.txt\n+++ b/a.txt\n@@ -1 +1 @@\n"
178 cases := map[string]struct {
179 patch, oldNote, newNote string
180 }{
181 "newline added": {head + "-foo\n" + noNL + "\n+foo\n", noNL, ""},
182 "newline removed": {head + "-foo\n+foo\n" + noNL + "\n", "", noNL},
183 "both lack": {head + "-foo\n" + noNL + "\n+bar\n" + noNL + "\n", noNL, noNL},
184 }
185 for name, c := range cases {
186 rows := splitPairs(c.patch)
187 if len(rows) != 2 || rows[1].Kind != "pair" || rows[1].Old == nil || rows[1].New == nil {
188 t.Errorf("%s: want a hunk and one paired row, got %+v", name, rows)
189 continue
190 }
191 if rows[1].OldNote != c.oldNote || rows[1].NewNote != c.newNote {
192 t.Errorf("%s: notes %q %q", name, rows[1].OldNote, rows[1].NewNote)
193 }
194 }
195}
196
197func TestSplitEmptyCellsAreHiddenFromAssistiveTech(t *testing.T) {
198 out := renderCommitDiff(t, diffLayout{Split: true, UnifiedURL: "/x", SplitURL: "/y"}, nil)
199 if !strings.Contains(out, `class="ln none" aria-hidden="true"`) || !strings.Contains(out, `class="src none" aria-hidden="true"`) {
200 t.Error("empty cells are not aria-hidden")
201 }
202}