package httpd import ( "net/http/httptest" "strings" "testing" "gitbay.org/gitbay/internal/config" "gitbay.org/gitbay/internal/store" "gitbay.org/gitbay/internal/web" ) const layoutPatch = `diff --git a/a.txt b/a.txt --- a/a.txt +++ b/a.txt @@ -1,4 +1,4 @@ keep -old one -old two +new one tail ` func renderCommitDiff(t *testing.T, layout diffLayout, mutate func([]diffFile)) string { t.Helper() files := parseDiff(layoutPatch) if mutate != nil { mutate(files) } if layout.Split { splitFiles(files) } var sb strings.Builder if err := web.Render(&sb, "commit.html", struct { repoPage SHA, ShortSHA, AuthorName, AuthorEmail, AuthorUser, CommitterEmail, Date, Message string Parents []string Sig sigView Checks []store.CommitStatus DiffFiles []diffFile DiffTruncated bool Layout diffLayout }{repoPage: testRepoPage(), SHA: strings.Repeat("a", 40), ShortSHA: "aaaaaaaaaa", DiffFiles: files, Layout: layout}); err != nil { t.Fatalf("render: %v", err) } return sb.String() } func TestSplitRowsPairDeletionsWithAdditions(t *testing.T) { files := parseDiff(layoutPatch) splitFiles(files) var pairs []splitRow for _, r := range files[0].Rows { if r.Kind == "pair" { pairs = append(pairs, r) } } if len(pairs) != 4 { t.Fatalf("want 4 pair rows, got %d", len(pairs)) } if pairs[1].Old == nil || pairs[1].New == nil || pairs[1].Old.OldLine != 2 || pairs[1].New.NewLine != 2 { t.Errorf("first change row does not pair old 2 with new 2: %+v", pairs[1]) } if pairs[2].Old == nil || pairs[2].Old.OldLine != 3 || pairs[2].New != nil { t.Errorf("surplus deletion is not left-only: %+v", pairs[2]) } } func TestDiffRendersUnified(t *testing.T) { out := renderCommitDiff(t, diffLayout{UnifiedURL: "/x?layout=unified", SplitURL: "/x?layout=split"}, nil) if strings.Contains(out, `difftable split`) { t.Error("unified layout rendered the split table") } if !strings.Contains(out, `split`) { t.Errorf("no link to the split layout:\n%s", out) } } func TestDiffRendersSplit(t *testing.T) { out := renderCommitDiff(t, diffLayout{Split: true, UnifiedURL: "/x?layout=unified", SplitURL: "/x?layout=split"}, nil) if !strings.Contains(out, `class="difftable split"`) { t.Fatalf("no split table:\n%s", out) } for _, want := range []string{`id="f0-o2"`, `id="f0-o3"`, `id="f0-n2"`, `id="f0-n1"`, `unified`} { if !strings.Contains(out, want) { t.Errorf("split output lacks %s", want) } } if strings.Count(out, `id="f0-n1"`) != 1 { t.Error("a context line's id appears more than once") } if !strings.Contains(out, `class="ln none"`) { t.Error("the surplus deletion has no empty right side") } } // Each column's line number links its own side, and the layout override // travels with the link. func TestSplitCommentLinksKeepSides(t *testing.T) { files := parseDiff(layoutPatch) splitFiles(files) var sb strings.Builder rp := testRepoPage() rp.Viewer = "alice" err := web.Render(&sb, "mr.html", mrPageData{repoPage: rp, MR: testMR("open"), View: "diff", DiffFiles: files, Layout: diffLayout{Split: true, Carry: "split"}}) if err != nil { t.Fatalf("render: %v", err) } out := sb.String() for _, want := range []string{ `cpath=a.txt&cline=2&cside=old&layout=split#compose`, `cpath=a.txt&cline=2&cside=new&layout=split#compose`, } { if !strings.Contains(out, want) { i := strings.Index(out, "cpath") t.Errorf("lacks %s; sample %q", want, out[max(i-50, 0):min(i+150, len(out))]) } } } func TestDiffLayoutForQueryOverridesAccount(t *testing.T) { st, err := store.Open(":memory:") if err != nil { t.Fatal(err) } defer st.Close() if err := st.MigrateUp(); err != nil { t.Fatal(err) } uid, _ := st.CreateUser("alice", false) cfg := config.Default() cfg.Web.Mode = "accounts" s := New(cfg, st, nil) ck := sessionCookieFor(t, s, st, uid) get := func(target string) diffLayout { req := httptest.NewRequest("GET", target, nil) req.AddCookie(ck) return s.diffLayoutFor(req) } if get("/o/r/commit/abc").Split { t.Error("a new account defaults to split") } if err := st.SetDiffLayout(uid, "split"); err != nil { t.Fatal(err) } if l := get("/o/r/commit/abc"); !l.Split || l.Carry != "" { t.Errorf("account setting ignored: %+v", l) } if l := get("/o/r/commit/abc?layout=unified"); l.Split || l.Carry != "unified" { t.Errorf("?layout=unified did not override: %+v", l) } l := get("/o/r/compare?base=main&head=x") if !strings.Contains(l.UnifiedURL, "base=main") || !strings.Contains(l.UnifiedURL, "layout=unified") { t.Errorf("switch link dropped the query: %s", l.UnifiedURL) } anon := httptest.NewRequest("GET", "/o/r/commit/abc?layout=split", nil) if !s.diffLayoutFor(anon).Split { t.Error("?layout=split ignored for a signed-out reader") } } func splitPairs(patch string) []splitRow { files := parseDiff(patch) splitFiles(files) var out []splitRow for _, r := range files[0].Rows { out = append(out, r) } return out } const noNL = `\ No newline at end of file` func TestSplitNoNewlineMarkerPairs(t *testing.T) { head := "diff --git a/a.txt b/a.txt\n--- a/a.txt\n+++ b/a.txt\n@@ -1 +1 @@\n" cases := map[string]struct { patch, oldNote, newNote string }{ "newline added": {head + "-foo\n" + noNL + "\n+foo\n", noNL, ""}, "newline removed": {head + "-foo\n+foo\n" + noNL + "\n", "", noNL}, "both lack": {head + "-foo\n" + noNL + "\n+bar\n" + noNL + "\n", noNL, noNL}, } for name, c := range cases { rows := splitPairs(c.patch) if len(rows) != 2 || rows[1].Kind != "pair" || rows[1].Old == nil || rows[1].New == nil { t.Errorf("%s: want a hunk and one paired row, got %+v", name, rows) continue } if rows[1].OldNote != c.oldNote || rows[1].NewNote != c.newNote { t.Errorf("%s: notes %q %q", name, rows[1].OldNote, rows[1].NewNote) } } } func TestSplitEmptyCellsAreHiddenFromAssistiveTech(t *testing.T) { out := renderCommitDiff(t, diffLayout{Split: true, UnifiedURL: "/x", SplitURL: "/y"}, nil) if !strings.Contains(out, `class="ln none" aria-hidden="true"`) || !strings.Contains(out, `class="src none" aria-hidden="true"`) { t.Error("empty cells are not aria-hidden") } }