diff --git a/internal/app/comments_test.go b/internal/app/comments_test.go index 03190aa5..fa66c453 100644 --- a/internal/app/comments_test.go +++ b/internal/app/comments_test.go @@ -122,7 +122,7 @@ func TestCommentGroupCommentsLikeGh(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } run := func(args ...string) error { out.Reset() diagnostics.Reset() diff --git a/internal/app/discussion_search_test.go b/internal/app/discussion_search_test.go index ae206be9..83699c4d 100644 --- a/internal/app/discussion_search_test.go +++ b/internal/app/discussion_search_test.go @@ -81,7 +81,7 @@ func TestSearchTables(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) plainOutput(t, deps) deps.Streams.OutTTY = true diff --git a/internal/app/discussion_view_test.go b/internal/app/discussion_view_test.go index 9ea30cc5..c5c8d9da 100644 --- a/internal/app/discussion_view_test.go +++ b/internal/app/discussion_view_test.go @@ -104,16 +104,12 @@ func TestPullViewCommentsIncludeReviews(t *testing.T) { case "/api/v1/repos/alice/project/issues/4/comments": _, _ = w.Write([]byte(`[{"body":"Looks close","user":{"login":"carol"},"created_at":"2026-01-02T00:00:00Z"}]`)) case "/api/v1/repos/alice/project/pulls/4/reviews": - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[ + writeFirstPage(w, r, `[ {"id":1,"body":"Please rename","state":"REQUEST_CHANGES","user":{"login":"alice"},"submitted_at":"2026-01-01T12:00:00Z","comments_count":1}, {"id":2,"body":"","state":"APPROVED","user":{"login":"dave"},"submitted_at":"2026-01-03T00:00:00Z"}, {"id":3,"body":"Draft","state":"PENDING","user":{"login":"erin"},"submitted_at":"2026-01-03T00:00:00Z"}, {"id":4,"body":"Great","state":"APPROVED","user":{"login":"alice"},"submitted_at":"2026-01-03T06:00:00Z"} - ]`)) + ]`) case "/api/v1/repos/alice/project/pulls/4/reviews/1/comments": _, _ = w.Write([]byte(`[{"id":9,"body":"Typo here","path":"main.go","position":12,"user":{"login":"alice"},"created_at":"2026-01-01T12:00:00Z"}]`)) default: diff --git a/internal/app/helpers_test.go b/internal/app/helpers_test.go index e7f05111..4b0f8a76 100644 --- a/internal/app/helpers_test.go +++ b/internal/app/helpers_test.go @@ -19,6 +19,7 @@ import ( "regexp" "strings" "testing" + "time" "gitea.dev/tea/internal/auth" "gitea.dev/tea/internal/clierr" @@ -336,3 +337,14 @@ func plainOutput(t *testing.T, deps Dependencies) { return nil })) } + +// writeFirstPage answers the first page of a listing with page and every later page with []. +func writeFirstPage(w http.ResponseWriter, r *http.Request, page string) { + if r.URL.Query().Get("page") != "1" { + page = `[]` + } + _, _ = w.Write([]byte(page)) +} + +// testNow is the clock of tests that show relative times. +var testNow = time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) diff --git a/internal/app/human_output_test.go b/internal/app/human_output_test.go index ae58c20a..620f92db 100644 --- a/internal/app/human_output_test.go +++ b/internal/app/human_output_test.go @@ -151,7 +151,7 @@ func TestMultiItemHumanOutputPagesOnce(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/v1/repos/alice/project/milestones": - writeMilestones(w, r, `[{"id":3,"title":"v1"}]`) + writeFirstPage(w, r, `[{"id":3,"title":"v1"}]`) case "/api/v1/repos/alice/project/issues": _, _ = w.Write([]byte("[" + issue(1) + "," + issue(2) + "]")) case "/api/v1/repos/alice/project/issues/1", "/api/v1/repos/alice/project/issues/2": @@ -223,31 +223,22 @@ func TestListsFitTheTerminalAndSplitWhenPiped(t *testing.T) { `{"number":5,"title":"支持中文路径 (support CJK paths)","state":"open","user":{"login":""}}]`)) })) defer server.Close() - for group, want := range map[string][2]string{ - "issue": { - "6\tA very long issue title that keeps going and going to see how list output handles wrapping\t\topen\tissue\talice\t\n" + - "5\t支持中文路径 (support CJK paths)\t\topen\tissue\t\t\n", - "Showing 2 of 2 open issues in alice/project\n\n" + - "ID TITLE LABELS AUTHOR UPDATED\n" + - "#6 A very long issue ti… alice\n" + - "#5 支持中文路径 (suppor…\n", - }, - } { - t.Run(group, func(t *testing.T) { - deps, out, _ := serverDeps(t, server.URL) - args := []string{group, "list", "-R", "alice/project"} - require.NoError(t, Execute(context.Background(), deps, args)) - require.Equal(t, want[0], out.String()) + deps, out, _ := serverDeps(t, server.URL) + args := []string{"issue", "list", "-R", "alice/project"} + require.NoError(t, Execute(context.Background(), deps, args)) + require.Equal(t, "6\tA very long issue title that keeps going and going to see how list output handles wrapping\t\topen\tissue\talice\t\n"+ + "5\t支持中文路径 (support CJK paths)\t\topen\tissue\t\t\n", out.String()) - out.Reset() - deps.Streams.OutTTY, deps.Streams.Width = true, 50 - deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) - require.NoError(t, Execute(context.Background(), deps, args)) - require.Equal(t, want[1], stripLinks(out.String())) - for _, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { - require.LessOrEqual(t, runewidth.StringWidth(stripLinks(line)), 50, line) - } - }) + out.Reset() + deps.Streams.OutTTY, deps.Streams.Width = true, 50 + deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) + require.NoError(t, Execute(context.Background(), deps, args)) + require.Equal(t, "Showing 2 of 2 open issues in alice/project\n\n"+ + "ID TITLE LABELS AUTHOR UPDATED\n"+ + "#6 A very long issue ti… alice\n"+ + "#5 支持中文路径 (suppor…\n", stripLinks(out.String())) + for _, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { + require.LessOrEqual(t, runewidth.StringWidth(stripLinks(line)), 50, line) } } @@ -265,7 +256,7 @@ func stripLinks(s string) string { // outputApplication returns an application over deps and a command writing to its streams. func outputApplication(deps Dependencies, json string) (*application, *cobra.Command) { - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } a := &application{deps: deps, options: options{json: json}} cmd := &cobra.Command{Use: "list"} cmd.SetOut(deps.Streams.Out) @@ -335,7 +326,7 @@ func TestWriteView(t *testing.T) { view := func(item listed) terminal.View { return terminal.View{ Title: item.Title, ID: fmt.Sprintf("#%d", item.Number), Badges: []terminal.Cell{{Text: "Open", Color: "green"}}, - Meta: "alice opened " + terminal.RelativeTime(time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC), item.Updated), Body: "Steps", Kind: "issue", URL: "https://example.org/1", + Meta: "alice opened " + terminal.RelativeTime(testNow, item.Updated), Body: "Steps", Kind: "issue", URL: "https://example.org/1", } } item := listed{1, "Login fails", time.Date(2026, 10, 11, 18, 0, 0, 0, time.UTC)} diff --git a/internal/app/issue_attachments_test.go b/internal/app/issue_attachments_test.go index f1590387..86c37c07 100644 --- a/internal/app/issue_attachments_test.go +++ b/internal/app/issue_attachments_test.go @@ -105,7 +105,7 @@ func TestAttachmentTablesDownloadsAndDoneLines(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) plainOutput(t, deps) run := func(tty bool, args ...string) error { diff --git a/internal/app/issue_edit_form_test.go b/internal/app/issue_edit_form_test.go index 0f1eb02b..93f6a032 100644 --- a/internal/app/issue_edit_form_test.go +++ b/internal/app/issue_edit_form_test.go @@ -82,11 +82,7 @@ func TestIssueEditAddAndRemoveLabels(t *testing.T) { case r.URL.Path == "/api/v1/orgs/alice": w.WriteHeader(http.StatusNotFound) case r.URL.Path == "/api/v1/repos/alice/project/labels": - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"id":1,"name":"bug"},{"id":2,"name":"docs"},{"id":3,"name":"help"}]`)) + writeFirstPage(w, r, `[{"id":1,"name":"bug"},{"id":2,"name":"docs"},{"id":3,"name":"help"}]`) case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/alice/project/issues/7": _, _ = w.Write([]byte(`{"number":7,"labels":[{"id":1,"name":"bug"},{"id":2,"name":"docs"}]}`)) case r.Method == http.MethodPut && r.URL.Path == "/api/v1/repos/alice/project/issues/7/labels": diff --git a/internal/app/issue_filters_test.go b/internal/app/issue_filters_test.go index 41c8f7a1..960d75f4 100644 --- a/internal/app/issue_filters_test.go +++ b/internal/app/issue_filters_test.go @@ -6,7 +6,6 @@ package app import ( "context" "net/http" - "regexp" "strconv" "testing" "time" @@ -107,7 +106,7 @@ func TestIssueListTables(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } args := []string{"issue", "list", "-R", "alice/project", "-L", "3"} require.NoError(t, Execute(context.Background(), deps, args)) require.Equal(t, "7\tShort\tbug, priority/high\topen\tissue\talice\t2026-10-11T18:00:00Z\n"+ @@ -131,7 +130,7 @@ func TestIssueListTables(t *testing.T) { out.Reset() deps.Streams.OutTTY = true require.NoError(t, Execute(context.Background(), deps, append(append(args, test.args...), "--no-input"))) - require.Equal(t, test.want, stripLinks(stripColors(out.String()))) + require.Equal(t, test.want, stripLinks(withoutANSI(out.String()))) } require.Contains(t, out.String(), "\x1b]8;;https://example.org/i/7\x1b\\\x1b[32m#7\x1b[0m\x1b]8;;\x1b\\") require.Contains(t, out.String(), "\x1b[35mmerged\x1b[0m") @@ -158,11 +157,6 @@ func TestIssueListWebOpensTheFilteredPage(t *testing.T) { require.Equal(t, server.URL+"/alice/project/issues?q=login+bug&state=closed", browser.resource) } -// stripColors removes SGR color sequences from rendered output. -func stripColors(s string) string { - return regexp.MustCompile("\x1b\\[[0-9;]*m").ReplaceAllString(s, "") -} - func TestIssueListKindFilter(t *testing.T) { var seen string server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/app/issue_inputs_test.go b/internal/app/issue_inputs_test.go index 084c7a10..7a8b4b8a 100644 --- a/internal/app/issue_inputs_test.go +++ b/internal/app/issue_inputs_test.go @@ -17,7 +17,7 @@ func TestIssueCreationMetadataAndReferenceClearing(t *testing.T) { calls := 0 server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/v1/repos/owner/repo/milestones" { - writeMilestones(w, r, `[{"id":4,"title":"Release"}]`) + writeFirstPage(w, r, `[{"id":4,"title":"Release"}]`) return } calls++ diff --git a/internal/app/label_options_test.go b/internal/app/label_options_test.go index 4f896302..d9cc51be 100644 --- a/internal/app/label_options_test.go +++ b/internal/app/label_options_test.go @@ -96,11 +96,7 @@ func TestLabelsByName(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "GET /api/v1/repos/alice/project/labels": - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(labels)) + writeFirstPage(w, r, labels) default: changed = append(changed, r.Method+" "+r.URL.Path+" "+requestFields(t, r, "name", "color", "description")) if r.Method == http.MethodDelete { diff --git a/internal/app/milestone_bulk_test.go b/internal/app/milestone_bulk_test.go index c8f6cf30..4beb470c 100644 --- a/internal/app/milestone_bulk_test.go +++ b/internal/app/milestone_bulk_test.go @@ -18,7 +18,7 @@ func TestBulkMilestoneStateContinuesAfterFailureAndKeepsJSONClean(t *testing.T) mutations := []string{} server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/v1/repos/alice/project/milestones" { - writeMilestones(w, r, `[{"id":9,"title":"v9"},{"id":10,"title":"v10"},{"id":11,"title":"v11"}]`) + writeFirstPage(w, r, `[{"id":9,"title":"v9"},{"id":10,"title":"v10"},{"id":11,"title":"v11"}]`) return } id := strings.TrimPrefix(r.URL.Path, "/api/v1/repos/alice/project/milestones/") diff --git a/internal/app/milestone_create_form_test.go b/internal/app/milestone_create_form_test.go index 23b76f38..8e8e0092 100644 --- a/internal/app/milestone_create_form_test.go +++ b/internal/app/milestone_create_form_test.go @@ -74,7 +74,7 @@ func TestMilestoneTablesViewsAndDoneLines(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "GET /api/v1/repos/alice/project/milestones": - writeMilestones(w, r, `[{"id":5,"title":"later","state":"open"},{"id":4,"title":"v0.9","state":"closed","closed_issues":3},`+ + writeFirstPage(w, r, `[{"id":5,"title":"later","state":"open"},{"id":4,"title":"v0.9","state":"closed","closed_issues":3},`+ `{"id":3,"title":"v1.0","state":"open","description":"First release","due_on":"2026-12-31T00:00:00Z","open_issues":3,"closed_issues":1}]`) case "GET /api/v1/repos/alice/project/issues/1", "GET /api/v1/repos/alice/project/issues/2": _, _ = w.Write([]byte(`{"number":1,"milestone":{"id":4,"title":"v0.9"}}`)) @@ -88,7 +88,7 @@ func TestMilestoneTablesViewsAndDoneLines(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) plainOutput(t, deps) run := func(tty bool, args ...string) { @@ -132,7 +132,7 @@ func TestMilestoneEditFormStartsFromTheMilestone(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "GET /api/v1/repos/alice/project/milestones": - writeMilestones(w, r, `[{"id":3,"title":"v1.0","description":"First","due_on":"2026-12-31T00:00:00Z"}]`) + writeFirstPage(w, r, `[{"id":3,"title":"v1.0","description":"First","due_on":"2026-12-31T00:00:00Z"}]`) case "PATCH /api/v1/repos/alice/project/milestones/3": require.NoError(t, json.NewDecoder(r.Body).Decode(&edited)) _, _ = w.Write([]byte(`{"id":3,"title":"v1.0.0"}`)) diff --git a/internal/app/milestone_description_test.go b/internal/app/milestone_description_test.go index f1ba4e56..e397e43a 100644 --- a/internal/app/milestone_description_test.go +++ b/internal/app/milestone_description_test.go @@ -44,7 +44,7 @@ func TestMilestoneEditEditorSeedsDescription(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/alice/project/milestones": - writeMilestones(w, r, `[{"id":4,"title":"Ship","description":"current notes","state":"open"}]`) + writeFirstPage(w, r, `[{"id":4,"title":"Ship","description":"current notes","state":"open"}]`) case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/alice/project/milestones/4": _, _ = w.Write([]byte(`{"id":4,"title":"Ship","description":"current notes","state":"open"}`)) case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/repos/alice/project/milestones/4": diff --git a/internal/app/milestone_names_test.go b/internal/app/milestone_names_test.go index a43bec2e..2e999c06 100644 --- a/internal/app/milestone_names_test.go +++ b/internal/app/milestone_names_test.go @@ -62,7 +62,7 @@ func TestMilestoneDeleteReturnsJSONSnapshotOnlyAfterSuccess(t *testing.T) { calls = append(calls, r.Method) if r.Method == http.MethodGet { require.Equal(t, "/api/v1/repos/alice/project/milestones", r.URL.Path) - writeMilestones(w, r, `[{"id":9,"title":"Release one","state":"open"}]`) + writeFirstPage(w, r, `[{"id":9,"title":"Release one","state":"open"}]`) return } require.Equal(t, "/api/v1/repos/alice/project/milestones/9", r.URL.Path) @@ -118,14 +118,6 @@ func TestMilestoneIssueCommandsSelectByName(t *testing.T) { require.ErrorContains(t, err, "milestone title or ID cannot be empty") } -// writeMilestones answers a milestone listing with page, a JSON array, as its first and only page. -func writeMilestones(w http.ResponseWriter, r *http.Request, page string) { - if r.URL.Query().Get("page") != "1" { - page = `[]` - } - _, _ = w.Write([]byte(page)) -} - // payloadField returns the JSON request body's field name as text. func payloadField(t *testing.T, r *http.Request, name string) string { t.Helper() @@ -139,7 +131,7 @@ func TestMilestonesAcceptTitlesOrIDs(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "GET /api/v1/repos/alice/project/milestones": - writeMilestones(w, r, `[{"id":3,"title":"v1.0","state":"open"},{"id":4,"title":"2026","state":"closed"},{"id":5,"title":"v2.0"}]`) + writeFirstPage(w, r, `[{"id":3,"title":"v1.0","state":"open"},{"id":4,"title":"2026","state":"closed"},{"id":5,"title":"v2.0"}]`) case "GET /api/v1/repos/alice/project/milestones/3", "GET /api/v1/repos/alice/project/milestones/4": _, _ = w.Write([]byte(`{"id":3,"title":"v1.0","state":"open"}`)) case "PATCH /api/v1/repos/alice/project/milestones/3", "PATCH /api/v1/repos/alice/project/milestones/4": diff --git a/internal/app/notifications_test.go b/internal/app/notifications_test.go index 1e8c0028..8c63a8e9 100644 --- a/internal/app/notifications_test.go +++ b/internal/app/notifications_test.go @@ -68,7 +68,7 @@ func TestNotificationsListTheInboxByDefault(t *testing.T) { } deps, out, _ := serverDeps(t, server.URL) deps.Streams.OutTTY, deps.Streams.Width = true, 100 - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } require.NoError(t, Execute(context.Background(), deps, []string{"notification", "list"})) require.Equal(t, `Showing 2 of 2 unread or pinned notifications in your inbox diff --git a/internal/app/pull_mutations_test.go b/internal/app/pull_mutations_test.go index 63552cba..e93223aa 100644 --- a/internal/app/pull_mutations_test.go +++ b/internal/app/pull_mutations_test.go @@ -48,11 +48,7 @@ func TestPullDiffColorsHeadersAndHunks(t *testing.T) { func TestPullDiffNameOnly(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "/api/v1/repos/alice/project/pulls/2/files", r.URL.Path) - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"filename":"one.txt"},{"filename":"docs/two.md"}]`)) + writeFirstPage(w, r, `[{"filename":"one.txt"},{"filename":"docs/two.md"}]`) })) defer server.Close() deps, out, _ := serverDeps(t, server.URL) diff --git a/internal/app/ref_test.go b/internal/app/ref_test.go index ad79b4c5..33dbe2de 100644 --- a/internal/app/ref_test.go +++ b/internal/app/ref_test.go @@ -64,17 +64,13 @@ func refApplication(t *testing.T, gitResults map[string]string) (*application, s t.Helper() server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "/api/v1/repos/alice/project/pulls", r.URL.Path) - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[ + writeFirstPage(w, r, `[ {"number":3,"head":{"ref":"feat/one","repo":{"full_name":"alice/project"}}}, {"number":4,"head":{"ref":"feat/two","repo":{"full_name":"alice/project"}}}, {"number":17,"head":{"ref":"fix","repo":{"full_name":"bob/project"}}}, {"number":20,"head":{"ref":"dup","repo":{"full_name":"alice/project"}}}, {"number":21,"head":{"ref":"dup","repo":{"full_name":"carol/project"}}} - ]`)) + ]`) })) t.Cleanup(server.Close) deps, _, _ := testDeps(t) diff --git a/internal/app/repo_mutations_test.go b/internal/app/repo_mutations_test.go index a496afe2..fc8d1cb3 100644 --- a/internal/app/repo_mutations_test.go +++ b/internal/app/repo_mutations_test.go @@ -151,11 +151,7 @@ func TestRepoForkReportsAnExistingFork(t *testing.T) { w.WriteHeader(http.StatusConflict) _, _ = w.Write([]byte(`{"message":"repository is already forked by user [uname: alice, repo path: bob/project, fork path: alice/project-fork]"}`)) case "GET /api/v1/repos/bob/project/forks": - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"full_name":"carol/project","owner":{"login":"carol"}},{"full_name":"alice/project-fork","owner":{"login":"alice"}}]`)) + writeFirstPage(w, r, `[{"full_name":"carol/project","owner":{"login":"carol"}},{"full_name":"alice/project-fork","owner":{"login":"alice"}}]`) case "GET /api/v1/repos/alice/project-fork": _, _ = w.Write([]byte(`{"full_name":"alice/project-fork","fork":true,"html_url":"https://gitea.test/alice/project-fork","parent":{"full_name":"bob/project"}}`)) default: diff --git a/internal/app/repos_test.go b/internal/app/repos_test.go index 6805d8d5..e6d6491b 100644 --- a/internal/app/repos_test.go +++ b/internal/app/repos_test.go @@ -21,7 +21,7 @@ func repoTTYDeps(t *testing.T, serverURL string) (Dependencies, *bytes.Buffer, * deps, out, diagnostics := serverDeps(t, serverURL) deps.Getenv = serverEnv(serverURL, map[string]string{"TERM": "dumb"}) deps.Streams.OutTTY, deps.Streams.Width = true, 100 - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } return deps, out, diagnostics } diff --git a/internal/app/time_test.go b/internal/app/time_test.go index 25b4557f..c3a3f85f 100644 --- a/internal/app/time_test.go +++ b/internal/app/time_test.go @@ -64,12 +64,8 @@ func TestTimeTablesAndDoneLines(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "GET /api/v1/repos/alice/project/times", "GET /api/v1/repos/alice/project/issues/1/times": - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"id":8,"time":5400,"created":"2026-10-11T18:00:00Z","user_name":"alice","issue":{"number":1,"title":"Login fails","repository":{"full_name":"alice/project"}}},` + - `{"id":9,"time":900,"created":"2026-10-11T19:00:00Z","user_name":"bob","issue":{"number":2,"title":"Docs"}}]`)) + writeFirstPage(w, r, `[{"id":8,"time":5400,"created":"2026-10-11T18:00:00Z","user_name":"alice","issue":{"number":1,"title":"Login fails","repository":{"full_name":"alice/project"}}},`+ + `{"id":9,"time":900,"created":"2026-10-11T19:00:00Z","user_name":"bob","issue":{"number":2,"title":"Docs"}}]`) case "POST /api/v1/repos/alice/project/issues/1/times": changed = append(changed, r.Method+" "+r.URL.Path) _, _ = w.Write([]byte(`{"id":10,"time":5100,"user_name":"alice","issue":{"number":1,"title":"Login fails"}}`)) @@ -80,7 +76,7 @@ func TestTimeTablesAndDoneLines(t *testing.T) { })) defer server.Close() deps, out, diagnostics := serverDeps(t, server.URL) - deps.Now = func() time.Time { return time.Date(2026, 10, 11, 20, 0, 0, 0, time.UTC) } + deps.Now = func() time.Time { return testNow } deps.Getenv = serverEnv(server.URL, map[string]string{"NO_COLOR": "1"}) deps.Streams.OutTTY = true run := func(args ...string) error { diff --git a/internal/app/wiki_test.go b/internal/app/wiki_test.go index 496532db..f88d6190 100644 --- a/internal/app/wiki_test.go +++ b/internal/app/wiki_test.go @@ -34,11 +34,7 @@ func TestWikiEditRenamesAndKeepsTheContent(t *testing.T) { func TestWikiListLimitsPagesOnTheClient(t *testing.T) { server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"title":"Getting Started","last_commit":{"author":{"name":"alice","date":"2026-10-11T18:00:00Z"}}},{"title":"Home"}]`)) + writeFirstPage(w, r, `[{"title":"Getting Started","last_commit":{"author":{"name":"alice","date":"2026-10-11T18:00:00Z"}}},{"title":"Home"}]`) })) defer server.Close() deps, out, _ := repoTTYDeps(t, server.URL) diff --git a/internal/services/helpers_test.go b/internal/services/helpers_test.go index a37a59fc..cef6907d 100644 --- a/internal/services/helpers_test.go +++ b/internal/services/helpers_test.go @@ -75,3 +75,11 @@ func TestNameOrIDPrefersIDsThenExactThenFoldedNames(t *testing.T) { require.Equal(t, tc.want, got.id, tc.raw) } } + +// writeFirstPage answers the first page of a listing with page and every later page with []. +func writeFirstPage(w http.ResponseWriter, r *http.Request, page string) { + if r.URL.Query().Get("page") != "1" { + page = `[]` + } + _, _ = w.Write([]byte(page)) +} diff --git a/internal/services/wiki_test.go b/internal/services/wiki_test.go index af7cb2d7..66151711 100644 --- a/internal/services/wiki_test.go +++ b/internal/services/wiki_test.go @@ -76,11 +76,7 @@ func TestWikiListAndRevisionsMapCommits(t *testing.T) { switch r.URL.Path { case "/api/v1/repos/alice/project/wiki/pages": assert.Equal(t, "50", r.URL.Query().Get("limit")) - if r.URL.Query().Get("page") != "1" { - _, _ = w.Write([]byte(`[]`)) - return - } - _, _ = w.Write([]byte(`[{"title":"Other"},{"title":"Home","html_url":"https://gitea.test/wiki/Home","last_commit":{"sha":"abc","author":{"name":"Alice","email":"author@example.test","date":"2026-10-04T00:00:00Z"}}},{"title":"Draft"}]`)) + writeFirstPage(w, r, `[{"title":"Other"},{"title":"Home","html_url":"https://gitea.test/wiki/Home","last_commit":{"sha":"abc","author":{"name":"Alice","email":"author@example.test","date":"2026-10-04T00:00:00Z"}}},{"title":"Draft"}]`) case "/api/v1/repos/alice/project/wiki/revisions/Home": assert.Equal(t, "3", r.URL.Query().Get("page")) _, _ = w.Write([]byte(`{"commits":[{"sha":"abc","message":"Edit","author":{"name":"Alice","email":"author@example.test","date":"2026-10-04T00:00:00Z"}},{"sha":"def","author":null}],"count":2}`))