internal/httpd/ownerpage_test.go

8dcfa45a8ac03a5ff9c36828274d05acadcf846c
gitbay/internal/httpd/ownerpage_test.go history · blame · raw

190 lines · 6484 bytes

  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}