internal/httpd/difflayout_test.go

v1.43.0
gitbay/internal/httpd/difflayout_test.go history · blame · raw

202 lines · 6691 bytes

  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&amp;cline=2&amp;cside=old&amp;layout=split#compose`,
113		`cpath=a.txt&amp;cline=2&amp;cside=new&amp;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}