e2e.file-editing.test.ts
| 1 | import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; |
| 2 | import { chromium } from 'playwright'; |
| 3 | import type { Browser, BrowserContext } from 'playwright'; |
| 4 | import { |
| 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 | |
| 17 | let browser: Browser; |
| 18 | let server: Awaited<ReturnType<typeof spawnServer>>; |
| 19 | |
| 20 | beforeAll(async () => { |
| 21 | await setupTestEnv(); |
| 22 | server = await spawnServer(); |
| 23 | browser = await chromium.launch(); |
| 24 | }); |
| 25 | |
| 26 | afterAll(async () => { |
| 27 | await browser.close(); |
| 28 | await killServer(server); |
| 29 | }); |
| 30 | |
| 31 | async 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 | |
| 41 | describe('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 |