internal/httpd/ownerpage_test.go
190 lines · 6484 bytes
9 symbols in this file
1package httpd
2
3import (
4 "html/template"
5 "strings"
6 "testing"
7
8 "gitbay.org/gitbay/internal/control"
9 "gitbay.org/gitbay/internal/store"
10 "gitbay.org/gitbay/internal/web"
11)
12
13func TestProfileTabFromPath(t *testing.T) {
14 for path, want := range map[string]string{
15 "/cmc": "about",
16 "/cmc/-/repositories": "repos",
17 "/cmc/-/bookmarks": "bookmarks",
18 "/cmc/-/snippets": "snippets",
19 "/cmc/-/people": "people",
20 "/krz": "about",
21 "/cmc/-/snippets/abcd": "about",
22 } {
23 if got := profileTab(path); got != want {
24 t.Errorf("profileTab(%q) = %q, want %q", path, got, want)
25 }
26 }
27}
28
29// The profile is sections rather than one stack (#242): the bar reads
30// About, Repositories, Bookmarks, Snippets, and each holds one thing.
31func TestOwnerPageTabOrder(t *testing.T) {
32 d := ownerFixture()
33 d.Self, d.Snippets = true, 2
34 out := renderOwner(t, "about", d)
35 order := []string{
36 `href="/cmc">About`,
37 `href="/cmc/-/repositories">Repositories`,
38 `href="/cmc/-/bookmarks">Bookmarks`,
39 `href="/cmc/-/snippets">Snippets`,
40 }
41 at := -1
42 for _, want := range order {
43 i := strings.Index(out, want)
44 if i < 0 {
45 t.Fatalf("tab bar missing %q:\n%s", want, out)
46 }
47 if i < at {
48 t.Errorf("tab %q is out of order", want)
49 }
50 at = i
51 }
52}
53
54// The graph moved inside About, with a log of the newest events under
55// it — not the whole history, which is what the atom feed is for.
56func TestOwnerPageAboutCarriesTheGraphAndLog(t *testing.T) {
57 d := ownerFixture()
58 d.Log = []control.FeedLine{{Actor: "cmc", Verb: "opened issue", Ref: "#12", Repo: "krz/gitbay", URL: "/krz/gitbay/issues/12"}}
59 out := renderOwner(t, "about", d)
60 for _, want := range []string{"Christian Cleberg", "actgraph", "opened issue", "#12", "/cmc/activity.atom"} {
61 if !strings.Contains(out, want) {
62 t.Errorf("about tab missing %q:\n%s", want, out)
63 }
64 }
65 if strings.Contains(out, "reminiscecleberg.com") {
66 t.Errorf("the about tab carries the repository list:\n%s", out)
67 }
68 // A profile with no About file still has the graph, so the tab is
69 // never empty and never a 404.
70 d.AboutHTML = ""
71 if out := renderOwner(t, "about", d); !strings.Contains(out, "actgraph") {
72 t.Errorf("a profile with no About file loses its graph:\n%s", out)
73 }
74 // Nothing to log reads as nothing, not as an empty block.
75 d.Log = nil
76 if out := renderOwner(t, "about", d); !strings.Contains(out, "Nothing yet.") {
77 t.Errorf("an empty log says nothing:\n%s", out)
78 }
79}
80
81func TestOwnerPageSectionsAreSeparate(t *testing.T) {
82 d := ownerFixture()
83 d.Self, d.Snippets = true, 1
84 d.Bookmarks = []control.BookmarkOut{{Path: "krz/hutch", Description: "a SourceHut client", Visibility: "public"}}
85 d.SnippetRows = []snippetRow{{store.Snippet{PublicID: "ab12", Description: "a shell one-liner", Visibility: "public"}, "run.sh"}}
86
87 repos := renderOwner(t, "repos", d)
88 if !strings.Contains(repos, "reminiscecleberg.com") || strings.Contains(repos, "actgraph") {
89 t.Errorf("repos tab is not the repository list alone:\n%s", repos)
90 }
91 marks := renderOwner(t, "bookmarks", d)
92 if !strings.Contains(marks, "krz/hutch") || strings.Contains(marks, "reminiscecleberg.com") {
93 t.Errorf("bookmarks tab is not the bookmark list alone:\n%s", marks)
94 }
95 // The snippet list renders here rather than on a page of its own.
96 snips := renderOwner(t, "snippets", d)
97 for _, want := range []string{"a shell one-liner", "run.sh", `href="/cmc/-/snippets/ab12"`, "new snippet"} {
98 if !strings.Contains(snips, want) {
99 t.Errorf("snippets tab missing %q:\n%s", want, snips)
100 }
101 }
102 if !strings.Contains(snips, `href="/cmc/-/repositories">Repositories`) {
103 t.Errorf("the snippets tab lost the profile's tab bar:\n%s", snips)
104 }
105}
106
107// Bookmarks are the viewer's own: repo bookmarks takes no owner and
108// lists the caller's. The tab is not offered on anyone else's profile.
109func TestOwnerPageBookmarksTabIsSelfOnly(t *testing.T) {
110 if out := renderOwner(t, "about", ownerFixture()); strings.Contains(out, "/-/bookmarks") {
111 t.Errorf("a stranger's profile offers a bookmarks tab:\n%s", out)
112 }
113 d := ownerFixture()
114 d.Self = true
115 if out := renderOwner(t, "about", d); !strings.Contains(out, "/-/bookmarks") {
116 t.Errorf("own profile has no bookmarks tab:\n%s", out)
117 }
118}
119
120func TestOwnerPagePeopleTabHoldsTheAdminPanel(t *testing.T) {
121 d := ownerFixture()
122 d.Kind = "org"
123 d.CanAdmin = true
124 d.Members = []control.ProfileMember{{Name: "cmc", Role: "admin"}}
125
126 about := renderOwner(t, "about", d)
127 if !strings.Contains(about, `href="/cmc/-/people"`) {
128 t.Errorf("an admin gets no people tab:\n%s", about)
129 }
130 if strings.Contains(about, "Create a team") {
131 t.Errorf("the admin forms still sit on another tab:\n%s", about)
132 }
133 // An org has no snippets, so it is not offered the tab.
134 if strings.Contains(about, "/-/snippets") {
135 t.Errorf("an org offers a snippets tab:\n%s", about)
136 }
137 people := renderOwner(t, "people", d)
138 for _, want := range []string{"Create a team", "member-add", "org-rename"} {
139 if !strings.Contains(people, want) {
140 t.Errorf("people tab missing %q:\n%s", want, people)
141 }
142 }
143}
144
145type ownerFixtureData struct {
146 Kind string
147 AboutHTML template.HTML
148 CanAdmin bool
149 Self bool
150 Snippets int
151 Members []control.ProfileMember
152 Log []control.FeedLine
153 Bookmarks []control.BookmarkOut
154 SnippetRows []snippetRow
155}
156
157func ownerFixture() ownerFixtureData {
158 return ownerFixtureData{
159 Kind: "user",
160 AboutHTML: template.HTML("<p><strong>Christian Cleberg</strong></p>"),
161 }
162}
163
164func renderOwner(t *testing.T, tab string, d ownerFixtureData) string {
165 t.Helper()
166 var sb strings.Builder
167 err := web.Render(&sb, "owner.html", ownerPage{
168 basePage: basePage{Site: "gitbay"},
169 Owner: "cmc",
170 Kind: d.Kind,
171 Tab: tab,
172 Profile: store.Profile{Description: "Org-Mode · Self-Hosting · Privacy"},
173 AboutHTML: d.AboutHTML,
174 Repos: []profileRepoRow{{control.ProfileRepo{Path: "cmc/reminiscecleberg.com", Description: "Personal placeholder site."}}},
175 Members: d.Members,
176 Activity: []activityWeek{{Month: "Sep", Days: []activityDay{{Date: "2026-09-20", Count: 3, Level: 2}}}},
177 ActivityTotal: 6088,
178 Log: d.Log,
179 Bookmarks: d.Bookmarks,
180 SnippetRows: d.SnippetRows,
181 CanAdmin: d.CanAdmin,
182 Self: d.Self,
183 Snippets: d.Snippets,
184 Feed: "/cmc/activity.atom",
185 })
186 if err != nil {
187 t.Fatalf("render: %v", err)
188 }
189 return sb.String()
190}