import { test, expect } from '@playwright/test'; async function fetchVerifyLink(email: string): Promise { for (let i = 0; i < 30; i++) { const res = await fetch('http://localhost:8025/api/v1/messages?limit=30'); const data = await res.json() as { messages: { ID: string; To: { Address: string }[] }[] }; const msg = data.messages.find((m) => m.To.some((t) => t.Address === email)); if (msg) { const body = await (await fetch(`http://localhost:8025/api/v1/message/${msg.ID}`)).json() as { Text: string }; const m = body.Text.match(/https?:\/\/[^\s]+verify-email\?token=[^\s]+/); if (m) return m[0]; } await new Promise((r) => setTimeout(r, 250)); } throw new Error('no verify link for ' + email); } async function registerVerifyLogin(page: import('@playwright/test').Page, name: string, email: string, password: string) { await page.goto('/register'); await page.getByLabel(/Naam/).fill(name); await page.getByLabel(/E-mailadres/).fill(email); await page.getByLabel(/Wachtwoord/).fill(password); await page.getByRole('button', { name: /Account aanmaken/ }).click(); await expect(page.getByText(/bevestigingsmail/i)).toBeVisible({ timeout: 10_000 }); const link = await fetchVerifyLink(email); await page.goto(link); await expect(page.getByRole('link', { name: 'Naar inloggen' })).toBeVisible({ timeout: 10_000 }); await page.goto('/login'); await page.getByLabel(/E-mailadres/).fill(email); await page.getByLabel(/Wachtwoord/).fill(password); await page.getByRole('button', { name: 'Inloggen' }).click(); await expect(page.getByRole('button', { name: 'Account menu' })).toBeVisible({ timeout: 15_000 }); } test('search opens with ⌘K, finds a lesson, navigates to detail', async ({ page }) => { const email = `search+${Date.now()}@example.com`; await registerVerifyLogin(page, 'SearchUser', email, 'secretpass'); await page.goto('/lessons'); await page.getByPlaceholder(/Nieuwe wortel-les/).fill('Aardrijkskunde'); await page.getByRole('button', { name: /Toevoegen/ }).first().click(); await expect(page.getByRole('link', { name: /Aardrijkskunde/ }).first()).toBeVisible(); await page.keyboard.press('Control+K'); const searchInput = page.getByPlaceholder(/Zoek lessen en kaarten/); await expect(searchInput).toBeVisible(); await searchInput.fill('aardrijk'); // Scope to the palette result row so we wait for the debounced search result, // not the background lessons-tree link. const resultRow = page.getByRole('listitem').filter({ hasText: /door SearchUser/ }).first(); await expect(resultRow).toBeVisible({ timeout: 5_000 }); await resultRow.click(); await expect(page).toHaveURL(/\/lessons\/\d+/); await expect(page.getByRole('heading', { name: /Aardrijkskunde/ })).toBeVisible(); }); test('lesson detail page shows start practice', async ({ page }) => { const email = `detail+${Date.now()}@example.com`; await registerVerifyLogin(page, 'DetailUser', email, 'secretpass'); await page.goto('/lessons'); await page.getByPlaceholder(/Nieuwe wortel-les/).fill('Wiskunde-test'); await page.getByRole('button', { name: /Toevoegen/ }).first().click(); await page.getByRole('link', { name: /Wiskunde-test/ }).first().click(); await expect(page.getByRole('heading', { name: /Wiskunde-test/ })).toBeVisible(); await expect(page.getByRole('link', { name: /Start oefenen/ })).toBeVisible(); }); test('stats page renders three sections', async ({ page }) => { const email = `stats+${Date.now()}@example.com`; await registerVerifyLogin(page, 'StatsUser', email, 'secretpass'); await page.goto('/stats'); await expect(page.getByRole('heading', { name: 'Statistieken' })).toBeVisible(); await expect(page.getByText(/Te reviewen/)).toBeVisible(); await expect(page.getByText(/Voortgang per les/)).toBeVisible(); await expect(page.getByText(/Activiteit/)).toBeVisible(); }); test('legacy /admin redirects to /lessons', async ({ page }) => { const email = `legacy+${Date.now()}@example.com`; await registerVerifyLogin(page, 'Legacy', email, 'secretpass'); await page.goto('/admin'); await expect(page).toHaveURL(/\/lessons$/); }); test('practice progress bar updates after answering a card', async ({ page }) => { const email = `progress+${Date.now()}@example.com`; await registerVerifyLogin(page, 'ProgressUser', email, 'secretpass'); await page.goto('/lessons'); await page.getByPlaceholder(/Nieuwe wortel-les/).fill('Progress-test'); await page.getByRole('button', { name: /Toevoegen/ }).first().click(); await page.getByRole('link', { name: /Progress-test/ }).first().click(); // Add two cards so the session does not end on the first answer. await page.getByPlaceholder('Nieuwe vraag').fill('q1'); await page.getByPlaceholder('Antwoord').fill('a1'); await page.getByRole('button', { name: 'Kaart toevoegen' }).click(); // Wait for the add to settle (draft clears) before adding the next card. await expect(page.getByPlaceholder('Nieuwe vraag')).toHaveValue(''); await page.getByPlaceholder('Nieuwe vraag').fill('q2'); await page.getByPlaceholder('Antwoord').fill('a2'); await expect(page.getByRole('button', { name: 'Kaart toevoegen' })).toBeEnabled(); await page.getByRole('button', { name: 'Kaart toevoegen' }).click(); await expect(page.getByPlaceholder('Nieuwe vraag')).toHaveValue(''); await page.getByRole('link', { name: /Start oefenen/ }).click(); await page.getByRole('button', { name: /Start sessie/ }).click(); // Before answering: 0 behandeld await expect(page.getByText('0 behandeld')).toBeVisible(); await page.getByRole('button', { name: 'Toon antwoord' }).click(); await page.getByRole('button', { name: /Goed/ }).click(); // After answering: bar reflects 1 handled + 1 correct + 100% accuracy await expect(page.getByText('1 behandeld')).toBeVisible({ timeout: 5_000 }); await expect(page.getByText('✓ 1')).toBeVisible(); await expect(page.getByText('100% goed')).toBeVisible(); });