e2e.file-editing.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 DATA_DIR,
7 ADMIN_PASS,
8 setupTestEnv,
9 spawnServer,
10 killServer,
11 login,
12 seedRepo,
13 getHeadCommit,
14 gitOutput,
15} from './helpers.ts';
16
17let browser: Browser;
18let server: Awaited<ReturnType<typeof spawnServer>>;
19
20beforeAll(async () => {
21 await setupTestEnv();
22 server = await spawnServer();
23 browser = await chromium.launch();
24});
25
26afterAll(async () => {
27 await browser.close();
28 await killServer(server);
29});
30
31async function loggedInContext(username = 'admin', password = ADMIN_PASS) {
32 const ctx = await browser.newContext();
33 const page = await ctx.newPage();
34 await login(page, username, password);
35 await page.close();
36 return ctx;
37}
38
39// ─── File editing ─────────────────────────────────────────────────────────────
40
41describe('file editing', () => {
42 let adminCtx: BrowserContext;
43
44 beforeAll(async () => {
45 adminCtx = await loggedInContext();
46 // Create a dedicated repo so edits don't interfere with other tests
47 const page = await adminCtx.newPage();
48 try {
49 await page.goto(`${BASE}/new`);
50 await page.fill('[name=name]', 'edit-repo');
51 await page.click('form[action="/new"] button[type=submit]');
52 await page.waitForURL(`${BASE}/edit-repo`);
53 } finally { await page.close(); }
54 await seedRepo('edit-repo');
55 });
56
57 afterAll(async () => { await adminCtx.close(); });
58
59 test('Edit button appears on text file blob when viewing a branch as admin', async () => {
60 const page = await adminCtx.newPage();
61 try {
62 await page.goto(`${BASE}/edit-repo/blob/main/index.js`);
63 const editBtn = page.locator('a[href*="/edit/main/index.js"]');
64 expect(await editBtn.isVisible()).toBe(true);
65 expect(await editBtn.textContent()).toBe('Edit');
66 } finally { await page.close(); }
67 });
68
69 test('Edit button does not appear when viewing a commit SHA', async () => {
70 const sha = await getHeadCommit('edit-repo');
71 const page = await adminCtx.newPage();
72 try {
73 await page.goto(`${BASE}/edit-repo/blob/${sha}/index.js`);
74 expect(await page.locator('a[href*="/edit/"]').count()).toBe(0);
75 } finally { await page.close(); }
76 });
77
78 test('Edit button does not appear for unauthenticated visitors', async () => {
79 const ctx = await browser.newContext();
80 const page = await ctx.newPage();
81 try {
82 await page.goto(`${BASE}/edit-repo/blob/main/index.js`);
83 expect(await page.locator('a[href*="/edit/main/"]').count()).toBe(0);
84 } finally {
85 await page.close();
86 await ctx.close();
87 }
88 });
89
90 test('edit page loads with file content pre-filled', async () => {
91 const page = await adminCtx.newPage();
92 try {
93 await page.goto(`${BASE}/edit-repo/edit/main/index.js`);
94 expect(await page.locator('.file-blob-name').textContent()).toBe('index.js');
95 const content = await page.locator('textarea[name=content]').inputValue();
96 expect(content).toContain('hello');
97 const msg = await page.locator('textarea[name=message]').inputValue();
98 expect(msg).toBe('Edited index.js');
99 } finally { await page.close(); }
100 });
101
102 test('edit page shows which branch will be committed to', async () => {
103 const page = await adminCtx.newPage();
104 try {
105 await page.goto(`${BASE}/edit-repo/edit/main/index.js`);
106 expect(await page.content()).toContain('main');
107 } finally { await page.close(); }
108 });
109
110 test('edit page returns 404 for non-branch ref', async () => {
111 const sha = await getHeadCommit('edit-repo');
112 const page = await adminCtx.newPage();
113 try {
114 const resp = await page.request.get(`${BASE}/edit-repo/edit/${sha}/index.js`);
115 expect(resp.status()).toBe(404);
116 } finally { await page.close(); }
117 });
118
119 test('submitting edit creates a new commit and redirects to blob view', async () => {
120 const page = await adminCtx.newPage();
121 try {
122 await page.goto(`${BASE}/edit-repo/edit/main/index.js`);
123 await page.fill('textarea[name=content]', 'console.log("edited");\n');
124 await page.fill('textarea[name=message]', 'Update index.js via web editor');
125 await page.locator('.form-actions button[type=submit]').click();
126 await page.waitForURL(/\/edit-repo\/commit\/[0-9a-f]{40}/);
127 // The commit detail view should show the commit message
128 expect(await page.content()).toContain('Update index.js via web editor');
129 } finally { await page.close(); }
130 });
131
132 test('edit commit has a gpgsig header (is signed)', async () => {
133 const repoDir = `${process.cwd()}/${DATA_DIR}/repos/edit-repo.git`;
134 const hash = gitOutput(['log', '--format=%H', '--grep=Update index.js via web editor', '-1'], repoDir);
135 expect(hash).toBeTruthy();
136 const obj = gitOutput(['cat-file', '-p', hash], repoDir);
137 expect(obj).toContain('gpgsig');
138 });
139
140 test('edit commit shows verified badge in commit log', async () => {
141 const page = await adminCtx.newPage();
142 try {
143 await page.goto(`${BASE}/edit-repo/commits/main`);
144 const item = page.locator('.commit-item').filter({ hasText: 'Update index.js via web editor' });
145 expect(await item.locator('.sig-badge.verified').isVisible()).toBe(true);
146 } finally { await page.close(); }
147 });
148
149 test('GET edit page returns 404 for non-branch ref', async () => {
150 const sha = await getHeadCommit('edit-repo');
151 const page = await adminCtx.newPage();
152 try {
153 const resp = await page.request.get(`${BASE}/edit-repo/edit/${sha}/index.js`);
154 expect(resp.status()).toBe(404);
155 } finally { await page.close(); }
156 });
157});
158