import { expect, test } from '@playwright/test'; import { contextForUser } from './helpers'; /** * Pond sidebar pack (issue #26). Runs against the local dev stack (api + * web), no Mailpit needed. Creates its own pages per test via the api and * navigates straight to `/p/:pondSlug/:pageSlug` (or `/p/:pondSlug`). */ const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173'; async function personalPond( context: Awaited>, ): Promise<{ id: string; slug: string }> { const ponds = await context.request.get('/api/v1/ponds'); const pond = (await ponds.json()).find((p: { type: string }) => p.type === 'personal'); return { id: pond.id, slug: pond.slug }; } async function createPage( context: Awaited>, pondId: string, title: string, ): Promise<{ id: string; slug: string }> { const created = await context.request.post(`/api/v1/ponds/${pondId}/pages`, { data: { title }, }); return created.json(); } test('sort modes reorder the sidebar page list correctly', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); // Defensive: a previous failed run may have left this fixture pond on a // non-default sort mode. await context.request.patch(`/api/v1/ponds/${pond.id}`, { data: { sidebarSort: 'alpha' } }); const suffix = Date.now(); await createPage(context, pond.id, `Zebra ${suffix}`); const apple = await createPage(context, pond.id, `Apple ${suffix}`); await createPage(context, pond.id, `Mango ${suffix}`); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${apple.slug}`); const items = page.locator('.sidebar__pages .sidebar__page'); await expect(items.filter({ hasText: `Zebra ${suffix}` })).toBeVisible(); async function orderOf(titles: string[]): Promise { const texts = await items.allTextContents(); return titles.map((title) => texts.findIndex((text) => text === title)); } // Default pond setting is 'alpha': relative order of the three fixture // pages must be ascending regardless of whatever other pages exist. function expectAscending(order: number[]): void { expect(order.every((v, i) => i === 0 || order[i - 1]! < v)).toBe(true); } await expect(async () => { expectAscending(await orderOf([`Apple ${suffix}`, `Mango ${suffix}`, `Zebra ${suffix}`])); }).toPass(); try { await page.getByLabel(/sort pages|seiten sortieren/i).selectOption('created'); await expect(async () => { expectAscending(await orderOf([`Zebra ${suffix}`, `Apple ${suffix}`, `Mango ${suffix}`])); }).toPass(); // Reload: the sort mode persisted server-side, not just in local state. await page.reload(); await expect(page.getByLabel(/sort pages|seiten sortieren/i)).toHaveValue('created'); await expect(async () => { expectAscending(await orderOf([`Zebra ${suffix}`, `Apple ${suffix}`, `Mango ${suffix}`])); }).toPass(); } finally { // Reset so this fixture pond doesn't leak state into other tests/runs. await page.getByLabel(/sort pages|seiten sortieren/i).selectOption('alpha'); } await context.close(); }); test('active page is highlighted in the sidebar', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const suffix = Date.now(); const one = await createPage(context, pond.id, `Active One ${suffix}`); const two = await createPage(context, pond.id, `Active Two ${suffix}`); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${one.slug}`); const activeLink = page.locator('.sidebar__page--active'); await expect(activeLink).toHaveText(`Active One ${suffix}`); await page.goto(`/p/${pond.slug}/${two.slug}`); await expect(page.locator('.sidebar__page--active')).toHaveText(`Active Two ${suffix}`); await context.close(); }); test('new-page flow: button opens a title prompt and the editor opens on create', async ({ browser, }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const suffix = Date.now(); const title = `Created via UI ${suffix}`; const page = await context.newPage(); await page.goto(`/p/${pond.slug}`); await page.getByRole('button', { name: /new page|neue seite/i }).click(); await page.locator('.sidebar').getByLabel(/title|titel/i).fill(title); await page.getByRole('button', { name: /create|erstellen/i }).click(); await expect(page).toHaveURL(new RegExp(`/p/${pond.slug}/.+`)); await expect(page.locator('.editor-page__title')).toHaveValue(title); await expect(page.locator('.sidebar__page--active')).toHaveText(title); await context.close(); }); test('sidebar collapse state persists per user and via the keyboard shortcut', async ({ browser, }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const suffix = Date.now(); const created = await createPage(context, pond.id, `Collapse Test ${suffix}`); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${created.slug}`); const sidebar = page.locator('nav.sidebar'); await expect(sidebar).toHaveAttribute('aria-hidden', 'false'); await page.keyboard.press('ControlOrMeta+Backslash'); await expect(sidebar).toHaveAttribute('aria-hidden', 'true'); await page.reload(); await expect(sidebar).toHaveAttribute('aria-hidden', 'true'); await page.keyboard.press('ControlOrMeta+Backslash'); await expect(sidebar).toHaveAttribute('aria-hidden', 'false'); await context.close(); });