e2e.sorting.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// ─── Repo sorting and pinning ─────────────────────────────────────────────────
36
37describe('repo sorting and pinning', () => {
38 let adminCtx: BrowserContext;
39
40 beforeAll(async () => {
41 adminCtx = await loggedInContext();
42 // Create two repos with predictable names: sort-aaa (created first/older),
43 // sort-zzz (created second/newer). This lets us verify both name order and
44 // creation-time order independently.
45 // Also create my-repo which is navigated to in one test.
46 const page = await adminCtx.newPage();
47 try {
48 await page.goto(`${BASE}/new`);
49 await page.fill('[name=name]', 'my-repo');
50 await page.click('form[action="/new"] button[type=submit]');
51 await page.waitForURL(`${BASE}/my-repo`);
52
53 await page.goto(`${BASE}/new`);
54 await page.fill('[name=name]', 'sort-aaa');
55 await page.click('form[action="/new"] button[type=submit]');
56 await page.waitForURL(`${BASE}/sort-aaa`);
57
58 await page.goto(`${BASE}/new`);
59 await page.fill('[name=name]', 'sort-zzz');
60 await page.click('form[action="/new"] button[type=submit]');
61 await page.waitForURL(`${BASE}/sort-zzz`);
62 } finally { await page.close(); }
63 });
64
65 afterAll(async () => { await adminCtx.close(); });
66
67 test('sort dropdown is visible on repo list page', async () => {
68 const page = await adminCtx.newPage();
69 try {
70 await page.goto(BASE);
71 const options = await page.locator('.repo-sort-select option').allTextContents();
72 expect(options.some(t => t.includes('Newest'))).toBe(true);
73 expect(options.some(t => t.includes('Name'))).toBe(true);
74 } finally { await page.close(); }
75 });
76
77 test('newest option is selected by default', async () => {
78 // Use a fresh context to ensure no repo_sort cookie is set.
79 const ctx = await browser.newContext();
80 const page = await ctx.newPage();
81 try {
82 await login(page, 'admin', ADMIN_PASS);
83 await page.goto(BASE);
84 expect(await page.locator('.repo-sort-select').inputValue()).toBe('created');
85 } finally { await ctx.close(); }
86 });
87
88 test('Go button is hidden with JS and works without JS', async () => {
89 const ctx = await browser.newContext({ javaScriptEnabled: false });
90 const page = await ctx.newPage();
91 try {
92 await login(page, 'admin', ADMIN_PASS);
93 await page.goto(BASE);
94 // Go button visible without JS
95 expect(await page.locator('form[action="/sort"] button[type=submit]').isVisible()).toBe(true);
96 // Select name sort and submit via Go button
97 await page.locator('.repo-sort-select').selectOption('name');
98 await page.locator('form[action="/sort"] button[type=submit]').click();
99 await page.waitForURL(BASE + '/');
100 expect(await page.locator('.repo-sort-select').inputValue()).toBe('name');
101 } finally { await ctx.close(); }
102 });
103
104 test('selecting name sort sets cookie and persists on next visit', async () => {
105 const page = await adminCtx.newPage();
106 try {
107 await page.goto(BASE);
108 await Promise.all([
109 page.waitForURL(BASE + '/'),
110 page.locator('.repo-sort-select').selectOption('name'),
111 ]);
112 expect(await page.locator('.repo-sort-select').inputValue()).toBe('name');
113 // Navigate away and back to confirm cookie persists
114 await page.goto(`${BASE}/my-repo`);
115 await page.goto(BASE);
116 expect(await page.locator('.repo-sort-select').inputValue()).toBe('name');
117 } finally {
118 // Reset cookie so subsequent tests start from the default sort.
119 await adminCtx.addCookies([{ name: 'repo_sort', value: 'created', domain: 'localhost', path: '/' }]);
120 await page.close();
121 }
122 });
123
124 test('default sort shows newer repo before older repo', async () => {
125 const page = await adminCtx.newPage();
126 try {
127 await adminCtx.addCookies([{ name: 'repo_sort', value: 'created', domain: 'localhost', path: '/' }]);
128 await page.goto(`${BASE}/?q=sort-`);
129 const names = await page.locator('.repo-name').allTextContents();
130 expect(names.indexOf('sort-zzz')).toBeLessThan(names.indexOf('sort-aaa'));
131 } finally { await page.close(); }
132 });
133
134 test('name sort shows repos in alphabetical order', async () => {
135 const page = await adminCtx.newPage();
136 try {
137 await adminCtx.addCookies([{ name: 'repo_sort', value: 'name', domain: 'localhost', path: '/' }]);
138 await page.goto(`${BASE}/?q=sort-`);
139 const names = await page.locator('.repo-name').allTextContents();
140 expect(names.indexOf('sort-aaa')).toBeLessThan(names.indexOf('sort-zzz'));
141 } finally {
142 await adminCtx.addCookies([{ name: 'repo_sort', value: 'created', domain: 'localhost', path: '/' }]);
143 await page.close();
144 }
145 });
146
147 test('pinning a repo shows pinned badge on list page', async () => {
148 const page = await adminCtx.newPage();
149 try {
150 await page.goto(`${BASE}/sort-aaa/settings`);
151 await page.check('[name=is_pinned]');
152 await page.click('form[action$="/settings"] button[type=submit]');
153 expect(await page.locator('.form-success').isVisible()).toBe(true);
154
155 await page.goto(BASE);
156 const card = page.locator('.repo-card').filter({ hasText: 'sort-aaa' });
157 expect(await card.locator('.badge-pinned').isVisible()).toBe(true);
158 } finally { await page.close(); }
159 });
160
161 test('pinned repo appears before unpinned repos regardless of creation order', async () => {
162 const page = await adminCtx.newPage();
163 try {
164 // sort-aaa is pinned; default (newest) sort would normally show sort-zzz
165 // first since it's newer — but pinned repos float to the top.
166 await page.goto(`${BASE}/?q=sort-`);
167 const names = await page.locator('.repo-name').allTextContents();
168 expect(names.indexOf('sort-aaa')).toBeLessThan(names.indexOf('sort-zzz'));
169 } finally { await page.close(); }
170 });
171
172 test('unpinning a repo removes the pinned badge', async () => {
173 const page = await adminCtx.newPage();
174 try {
175 await page.goto(`${BASE}/sort-aaa/settings`);
176 await page.uncheck('[name=is_pinned]');
177 await page.click('form[action$="/settings"] button[type=submit]');
178 expect(await page.locator('.form-success').isVisible()).toBe(true);
179
180 await page.goto(BASE);
181 const card = page.locator('.repo-card').filter({ hasText: 'sort-aaa' });
182 expect(await card.locator('.badge-pinned').count()).toBe(0);
183 } finally { await page.close(); }
184 });
185});
186