e2e.pagination.test.ts
Raw
1import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2import { chromium } from 'playwright';
3import type { Browser, BrowserContext } from 'playwright';
4import {
5 BASE,
6 ADMIN_PASS,
7 setupTestEnv,
8 spawnServer,
9 killServer,
10 login,
11} from './helpers.ts';
12
13let browser: Browser;
14let server: Awaited<ReturnType<typeof spawnServer>>;
15
16beforeAll(async () => {
17 await setupTestEnv();
18 server = await spawnServer();
19 browser = await chromium.launch();
20});
21
22afterAll(async () => {
23 await browser.close();
24 await killServer(server);
25});
26
27async function loggedInContext(username = 'admin', password = ADMIN_PASS) {
28 const ctx = await browser.newContext();
29 const page = await ctx.newPage();
30 await login(page, username, password);
31 await page.close();
32 return ctx;
33}
34
35// Helper: create N issues in a repo using the server API (no browser rendering)
36async function bulkCreateIssues(ctx: BrowserContext, repo: string, count: number) {
37 for (let i = 1; i <= count; i++) {
38 await ctx.request.fetch(`${BASE}/${repo}/issues`, {
39 method: 'POST',
40 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
41 data: `title=Issue+number+${i}&body=`,
42 maxRedirects: 0,
43 }).catch(() => {}); // 302 redirect throws; that's fine
44 }
45}
46
47// Helper: create N patches in a repo using the server API
48async function bulkCreatePatches(ctx: BrowserContext, repo: string, count: number) {
49 const VALID_PATCH = [
50 'From a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 Mon Sep 17 00:00:00 2001',
51 'From: Test User <test@example.com>',
52 'Date: Mon, 01 Jan 2024 12:00:00 +0000',
53 'Subject: [PATCH] Add f.txt',
54 '',
55 '---',
56 'diff --git a/f.txt b/f.txt',
57 'new file mode 100644',
58 'index 0000000..9daeafb',
59 '--- /dev/null',
60 '+++ b/f.txt',
61 '@@ -0,0 +1 @@',
62 '+x',
63 '',
64 ].join('\n');
65
66 for (let i = 1; i <= count; i++) {
67 await ctx.request.fetch(`${BASE}/${repo}/patches`, {
68 method: 'POST',
69 multipart: {
70 title: `Patch number ${i}`,
71 patch_file: { name: 'bulk.patch', mimeType: 'text/plain', buffer: Buffer.from(VALID_PATCH) },
72 },
73 maxRedirects: 0,
74 }).catch(() => {});
75 }
76}
77
78// ─── Pagination ───────────────────────────────────────────────────────────────
79
80describe('pagination', () => {
81 let adminCtx: BrowserContext;
82
83 beforeAll(async () => {
84 adminCtx = await loggedInContext();
85
86 // Create a repo dedicated to pagination testing
87 const page = await adminCtx.newPage();
88 try {
89 await page.goto(`${BASE}/new`);
90 await page.fill('[name=name]', 'paged-repo');
91 await page.click('form[action="/new"] button[type=submit]');
92 await page.waitForURL(`${BASE}/paged-repo`);
93 } finally { await page.close(); }
94
95 // Create 21 issues via the API (triggers page 2 at 20 per page)
96 await bulkCreateIssues(adminCtx, 'paged-repo', 21);
97
98 // Create 21 patches via browser (patch upload requires multipart)
99 await bulkCreatePatches(adminCtx, 'paged-repo', 21);
100 });
101
102 afterAll(async () => { await adminCtx.close(); });
103
104 // ── Repo list pagination ──────────────────────────────────────────────────
105
106 test('repo list page 1 shows repos and no pagination when few repos', async () => {
107 // With only a handful of test repos (< 20), there should be no pagination nav
108 const page = await adminCtx.newPage();
109 try {
110 await page.goto(BASE);
111 // Repos are shown
112 expect(await page.locator('.repo-name').count()).toBeGreaterThan(0);
113 } finally { await page.close(); }
114 });
115
116 // ── Issue pagination ──────────────────────────────────────────────────────
117
118 test('issue list page 1 shows at most 20 items', async () => {
119 const page = await adminCtx.newPage();
120 try {
121 await page.goto(`${BASE}/paged-repo/issues`);
122 expect(await page.locator('.issue-item').count()).toBeLessThanOrEqual(20);
123 } finally { await page.close(); }
124 });
125
126 test('issue list pagination nav appears when more than 20 issues', async () => {
127 const page = await adminCtx.newPage();
128 try {
129 await page.goto(`${BASE}/paged-repo/issues`);
130 expect(await page.locator('.pagination').isVisible()).toBe(true);
131 } finally { await page.close(); }
132 });
133
134 test('issue list page 2 shows remaining issues', async () => {
135 const page = await adminCtx.newPage();
136 try {
137 await page.goto(`${BASE}/paged-repo/issues?page=2`);
138 const count = await page.locator('.issue-item').count();
139 expect(count).toBeGreaterThan(0);
140 expect(count).toBeLessThanOrEqual(20);
141 } finally { await page.close(); }
142 });
143
144 test('issue list page 2 prev link goes to page 1', async () => {
145 const page = await adminCtx.newPage();
146 try {
147 await page.goto(`${BASE}/paged-repo/issues?page=2`);
148 const prevHref = await page.locator('.pagination-prev .pagination-btn').getAttribute('href');
149 expect(prevHref).toContain('page=1');
150 } finally { await page.close(); }
151 });
152
153 test('issue list page 1 next link goes to page 2', async () => {
154 const page = await adminCtx.newPage();
155 try {
156 await page.goto(`${BASE}/paged-repo/issues`);
157 const nextHref = await page.locator('.pagination-next .pagination-btn').getAttribute('href');
158 expect(nextHref).toContain('page=2');
159 } finally { await page.close(); }
160 });
161
162 // ── Patch pagination ──────────────────────────────────────────────────────
163
164 test('patch list page 1 shows at most 20 items', async () => {
165 const page = await adminCtx.newPage();
166 try {
167 await page.goto(`${BASE}/paged-repo/patches`);
168 expect(await page.locator('.issue-item').count()).toBeLessThanOrEqual(20);
169 } finally { await page.close(); }
170 });
171
172 test('patch list pagination nav appears when more than 20 patches', async () => {
173 const page = await adminCtx.newPage();
174 try {
175 await page.goto(`${BASE}/paged-repo/patches`);
176 expect(await page.locator('.pagination').isVisible()).toBe(true);
177 } finally { await page.close(); }
178 });
179
180 test('patch list page 2 shows remaining patches', async () => {
181 const page = await adminCtx.newPage();
182 try {
183 await page.goto(`${BASE}/paged-repo/patches?page=2`);
184 const count = await page.locator('.issue-item').count();
185 expect(count).toBeGreaterThan(0);
186 } finally { await page.close(); }
187 });
188
189 // ── Commit log pagination ─────────────────────────────────────────────────
190
191 test('commit log with few commits shows no cursor nav', async () => {
192 // paged-repo has no commits (empty repo) — there's no commit log to paginate
193 // We verify the page loads without pagination controls
194 const page = await adminCtx.newPage();
195 try {
196 await page.goto(`${BASE}/paged-repo`);
197 expect(await page.locator('.commit-cursor-nav').count()).toBe(0);
198 } finally { await page.close(); }
199 });
200});
201