import { test, expect, type Page } from '@playwright/test'; const mangaId = '33333333-3333-3333-3333-333333333333'; const chapter1Id = 'c1111111-3333-3333-3333-333333333333'; const chapter2Id = 'c2222222-3333-3333-3333-333333333333'; const chapter3Id = 'c3333333-3333-3333-3333-333333333333'; const mangaFixture = { id: mangaId, title: 'Vinland Saga', author: 'Makoto Yukimura', description: null, cover_image_path: null, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z' }; const chaptersFixture = [ { id: chapter1Id, manga_id: mangaId, number: 1, title: 'Somewhere, Not Here', page_count: 1, created_at: '2026-01-01T00:00:00Z' }, { id: chapter2Id, manga_id: mangaId, number: 2, title: null, page_count: 1, created_at: '2026-01-02T00:00:00Z' }, { id: chapter3Id, manga_id: mangaId, number: 3, title: 'Sword Dance', page_count: 1, created_at: '2026-01-03T00:00:00Z' } ]; function pageFixture(chapterId: string) { return [ { id: `p1111111-${chapterId.slice(1, 8)}-3333-3333-333333333333`, chapter_id: chapterId, page_number: 1, storage_key: `mangas/${mangaId}/chapters/${chapterId}/pages/0001.png`, content_type: 'image/png' } ]; } async function mockReaderApis(page: Page) { // Force public mode so the layout doesn't bounce anonymous visitors // to /login (the dev backend on this machine runs with // PRIVATE_MODE=true, which the layout's universal load respects). await page.route('**/api/v1/auth/config', (route) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ self_register_enabled: true, private_mode: false }) }) ); await page.route('**/api/v1/auth/me', (route) => route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ error: { code: 'unauthenticated', message: '' } }) }) ); await page.route('**/api/v1/auth/me/preferences', (route) => route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ error: { code: 'unauthenticated', message: '' } }) }) ); await page.route('**/api/v1/me/bookmarks*', (route) => route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ error: { code: 'unauthenticated', message: '' } }) }) ); await page.route(`**/api/v1/mangas/${mangaId}`, (route) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mangaFixture) }) ); await page.route(new RegExp(`/api/v1/mangas/${mangaId}/chapters(\\?.*)?$`), (route) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ items: chaptersFixture, page: { limit: 200, offset: 0, total: chaptersFixture.length } }) }) ); for (const c of chaptersFixture) { await page.route(`**/api/v1/mangas/${mangaId}/chapters/${c.id}`, (route) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(c) }) ); await page.route( `**/api/v1/mangas/${mangaId}/chapters/${c.id}/pages`, (route) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ pages: pageFixture(c.id) }) }) ); } const png = Buffer.from( '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d49444154789c63000100000005000158a3b62a0000000049454e44ae426082', 'hex' ); await page.route('**/api/v1/files/**', (route) => route.fulfill({ status: 200, contentType: 'image/png', body: png }) ); } test('reader chapter select lists every chapter with the manga-detail-style label', async ({ page }) => { await mockReaderApis(page); await page.goto(`/manga/${mangaId}/chapter/${chapter2Id}`); const select = page.getByTestId('reader-chapter-select'); await expect(select).toBeVisible(); // The current chapter is preselected. await expect(select).toHaveValue(chapter2Id); // Each chapter rendered as "Ch. N — Title" (or "Ch. N" when title is null), // in ascending number order — matching the prev/next sort. const labels = await select.locator('option').allTextContents(); expect(labels.map((l) => l.trim())).toEqual([ 'Ch. 1 — Somewhere, Not Here', 'Ch. 2', 'Ch. 3 — Sword Dance' ]); }); test('choosing a chapter from the select navigates to that chapter', async ({ page }) => { await mockReaderApis(page); await page.goto(`/manga/${mangaId}/chapter/${chapter1Id}`); await expect(page.getByTestId('reader-chapter-select')).toHaveValue(chapter1Id); await page.getByTestId('reader-chapter-select').selectOption(chapter3Id); await expect(page).toHaveURL( new RegExp(`/manga/${mangaId}/chapter/${chapter3Id}$`) ); await expect(page.getByTestId('reader-chapter-select')).toHaveValue(chapter3Id); });