Commit 584dc8d6ee

584dc8d6ee359da9453cae2c698d0dc04d5b8150

parent: 0d343c917d

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 21:45 UTC

web: one empty-state register — no CLI commands, no 'yet' on finished items

Ref #270

Layout: unified · split

internal/httpd/buildpages_test.go +63
@@ -57,6 +57,69 @@ func TestBuildsPageRendersCommandOutput(t *testing.T) {
5757 }
5858}
5959
60// A writer sees the instruction to add CI; a reader without push access
61// sees only the fact, since the instruction is not theirs to act on
62// (#270).
63func TestBuildsEmptyStateOmitsInstructionForReaders(t *testing.T) {
64 var sb strings.Builder
65 filter := buildFilter{}
66 err := web.Render(&sb, "builds.html", struct {
67 repoPage
68 Builds []control.BuildOut
69 Jobs []control.JobOut
70 Runs []buildRun
71 Filter buildFilter
72 Facets []facetGroup
73 Refs []string
74 Older string
75 CanWrite bool
76 Notice string
77 }{
78 testRepoPage(), nil, nil, nil, filter, nil, nil, "", false, "",
79 })
80 if err != nil {
81 t.Fatalf("render: %v", err)
82 }
83 out := sb.String()
84 if !strings.Contains(out, "no builds") {
85 t.Error(`missing "no builds"`)
86 }
87 if strings.Contains(out, "ci.yml") {
88 t.Error("a reader without push access should not see the push instruction")
89 }
90}
91
92// A writer with zero builds gets the one instruction the page can give
93// them (#270).
94func TestBuildsEmptyStateShowsInstructionForWriters(t *testing.T) {
95 var sb strings.Builder
96 filter := buildFilter{}
97 err := web.Render(&sb, "builds.html", struct {
98 repoPage
99 Builds []control.BuildOut
100 Jobs []control.JobOut
101 Runs []buildRun
102 Filter buildFilter
103 Facets []facetGroup
104 Refs []string
105 Older string
106 CanWrite bool
107 Notice string
108 }{
109 testRepoPage(), nil, nil, nil, filter, nil, nil, "", true, "",
110 })
111 if err != nil {
112 t.Fatalf("render: %v", err)
113 }
114 out := sb.String()
115 if !strings.Contains(out, "no builds") {
116 t.Error(`missing "no builds"`)
117 }
118 if !strings.Contains(out, "push a commit with a .gitbay/ci.yml") {
119 t.Error("a writer with no builds should see the push instruction")
120 }
121}
122
60123func TestBuildPageRendersCommandOutput(t *testing.T) {
61124 var sb strings.Builder
62125 err := web.Render(&sb, "build.html", buildView{
internal/httpd/mrpage_test.go +60
@@ -83,6 +83,66 @@ func testMR(state string) store.MR {
8383 HeadSHA: "ff6271a9d4570cd46f169091637a9d2e40ad5c2b"}
8484}
8585
86// A finished merge request states an empty label list as a fact, not a
87// promise something is still coming (#270).
88func TestMRPageLabelsNoYetOnFinishedState(t *testing.T) {
89 var sb strings.Builder
90 if err := web.Render(&sb, "mr.html", mrPageData{
91 repoPage: testRepoPage(), MR: testMR("merged"), View: "conversation",
92 }); err != nil {
93 t.Fatalf("render: %v", err)
94 }
95 if !strings.Contains(sb.String(), "no labels") {
96 t.Error(`merged MR with no labels should read "no labels", not "none yet"`)
97 }
98}
99
100// An open MR can still gain labels, so its empty state keeps "none yet"
101// rather than the finished-item wording (#270).
102func TestMRPageLabelsNoneYetOnOpenState(t *testing.T) {
103 var sb strings.Builder
104 if err := web.Render(&sb, "mr.html", mrPageData{
105 repoPage: testRepoPage(), MR: testMR("open"), View: "conversation",
106 }); err != nil {
107 t.Fatalf("render: %v", err)
108 }
109 out := sb.String()
110 if !strings.Contains(out, "none yet") {
111 t.Error(`open MR with no labels should read "none yet"`)
112 }
113 if strings.Contains(out, "no labels") {
114 t.Error(`open MR with no labels should not read "no labels"`)
115 }
116}
117
118// A closed MR is finished the same way a merged one is (#270).
119func TestMRPageLabelsNoLabelsOnClosedState(t *testing.T) {
120 var sb strings.Builder
121 if err := web.Render(&sb, "mr.html", mrPageData{
122 repoPage: testRepoPage(), MR: testMR("closed"), View: "conversation",
123 }); err != nil {
124 t.Fatalf("render: %v", err)
125 }
126 if !strings.Contains(sb.String(), "no labels") {
127 t.Error(`closed MR with no labels should read "no labels", not "none yet"`)
128 }
129}
130
131func TestMRPageReviewersEmptyStateDropsNobody(t *testing.T) {
132 var sb strings.Builder
133 if err := web.Render(&sb, "mr.html", mrPageData{
134 repoPage: testRepoPage(), MR: testMR("open"), View: "conversation",
135 }); err != nil {
136 t.Fatalf("render: %v", err)
137 }
138 if strings.Contains(sb.String(), "nobody yet") {
139 t.Error(`reviewers empty state should read "no reviewers"`)
140 }
141 if !strings.Contains(sb.String(), "no reviewers") {
142 t.Error(`missing "no reviewers"`)
143 }
144}
145
86146// The header states what happened to the MR: who opened, merged, or closed
87147// it, and when. A merge or close with no stamp (imports, pre-0029 merges)
88148// names the state without claiming a byline it cannot back up.
internal/web/templates/builds.html +1 −1
@@ -50,7 +50,7 @@
5050 {{range .Builds}}<a class="chip check-{{.Status}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/builds/{{.Number}}">{{.Job}}</a>{{end}}
5151 </div>
5252</li>
53{{else}}<li class="empty">no builds — push a commit with a <code>.gitbay/ci.yml</code></li>{{end}}
53{{else}}<li class="empty">no builds{{if .CanWrite}} — push a commit with a .gitbay/ci.yml{{end}}</li>{{end}}
5454</ul>
5555{{if .Older}}<p class="pager"><a href="{{.Older}}">older →</a></p>{{end}}
5656</div>
internal/web/templates/dashboard.html +1 −1
@@ -26,7 +26,7 @@
2626 <ul class="pins">
2727 {{range .Pins}}<li><a href="/{{.Owner}}/{{.Name}}"><span class="owner">{{.Owner}}/</span>{{.Name}}</a><span class="n" title="{{.Issues}} open issue{{if ne .Issues 1}}s{{end}}, {{.MRs}} open merge request{{if ne .MRs 1}}s{{end}}{{with .Build}}, last build {{.}}{{end}}"><b{{if .Issues}} class="wants"{{end}}>{{.Issues}} <span class="vh">open issues</span></b> <b{{if .MRs}} class="wants"{{end}}>{{.MRs}} <span class="vh">open merge requests</span></b> <span class="dot{{if eq .Build "success"}} ok{{else if or (eq .Build "failure") (eq .Build "error")}} bad{{else if or (eq .Build "pending") (eq .Build "running")}} pend{{end}}"><span class="vh">{{with .Build}}last build {{.}}{{else}}no builds{{end}}</span></span></span></li>
2828 {{end}}</ul>
29 {{else}}<p class="none">Nothing pinned yet. Press Pin on a repository.</p>{{end}}
29 {{else}}<p class="none">nothing pinned — press Pin on a repository you visit</p>{{end}}
3030</aside>
3131
3232<section class="dashmain">
internal/web/templates/mr.html +2 −2
@@ -144,7 +144,7 @@
144144 <div class="grp">
145145 <h2>Reviewers</h2>
146146 {{if .MR.ReviewRequests}}<p class="row">{{range .MR.ReviewRequests}}<a href="/{{.}}">{{.}}</a> {{end}}</p>
147 {{else}}<p class="none">nobody yet</p>{{end}}
147 {{else}}<p class="none">no reviewers</p>{{end}}
148148 {{if and .CanWrite (or (eq .MR.State "open") (eq .MR.State "source_gone"))}}
149149 <form method="post" action="{{$base}}/review-request" class="actions">
150150 <input type="text" name="add" aria-label="Add reviewers" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
@@ -175,7 +175,7 @@
175175 <div class="grp">
176176 <h2>Labels</h2>
177177 {{if .MR.Labels}}<p class="row">{{range .MR.Labels}}<a class="chip label" style="{{index $.LabelColors .}}" href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs?label={{.}}">{{.}}</a> {{end}}</p>
178 {{else}}<p class="none">none yet</p>{{end}}
178 {{else}}<p class="none">{{if or (eq .MR.State "merged") (eq .MR.State "closed")}}no labels{{else}}none yet{{end}}</p>{{end}}
179179 {{if .CanWrite}}
180180 <form method="post" action="{{$base}}/label" class="actions">
181181 <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
internal/web/templates/mrs.html +1 −1
@@ -26,7 +26,7 @@
2626 </div>
2727</li>
2828{{else}}{{if .Query}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}merge requests matching “{{.Query}}”</li>
29{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}merge requests — open one with <code>gitbay mr create {{.Repo.OwnerName}}/{{.Repo.Name}} --source ... --target {{.Repo.DefaultBranch}}</code></li>{{end}}{{end}}
29{{else}}<li class="empty">no {{if ne .State "all"}}{{.State}} {{end}}merge requests</li>{{end}}{{end}}
3030</ul>
3131{{if .Older}}<p class="pager"><a href="{{.Older}}">older →</a></p>{{end}}
3232</div>
internal/web/templates/notifications.html +1 −1
@@ -18,6 +18,6 @@
1818 </div>
1919 {{if not .Read}}<form method="post" action="/notifications" class="inline"><input type="hidden" name="id" value="{{.ID}}"><button type="submit" class="btn">Mark read</button></form>{{end}}
2020</li>
21{{else}}<li class="empty">{{if .All}}nothing here yet{{else}}nothing unread — <a href="/notifications?all=1">show all</a>{{end}}</li>{{end}}
21{{else}}<li class="empty">{{if .All}}nothing to show{{else}}no unread notifications — <a href="/notifications?all=1">show all</a>{{end}}</li>{{end}}
2222</ul>
2323{{end}}