/** * Dashboard enrichment-list-block (issue #296) — full upload → banner → CTA journey, * plus axe sweep in light and dark mode at 320 / 768 / 1440 viewports. * * The uploaded PDFs are deleted in afterEach so this spec does not pollute * the dev DB between runs. */ import AxeBuilder from '@axe-core/playwright'; import { test, expect, type Page } from '@playwright/test'; import { execSync } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const VIEWPORTS = [ { name: '320', width: 320, height: 720 }, { name: '768', width: 768, height: 1024 }, { name: '1440', width: 1440, height: 900 } ]; const psql = (sql: string) => execSync( `docker exec archive-db psql -U archive_user family_archive_db -c "${sql.replace(/"/g, '\\"')}"` ); test.afterEach(() => { // Remove any document whose filename matches the seeded sentinel — keeps the // DB clean for subsequent test runs. psql(`DELETE FROM documents WHERE original_filename IN ('minimal.pdf', 'minimal2.pdf');`); }); async function uploadViaDropZone(page: Page, files: string[]) { const inputLocator = page.locator('input[type="file"][accept*="pdf"]'); await inputLocator.first().setInputFiles(files); } test.describe('Dashboard enrichment block — upload → banner → CTA', () => { test('banner appears after upload and CTA navigates to /enrich', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-hydrated]'); const fixturePath = (name: string) => path.join(__dirname, 'fixtures', name); await uploadViaDropZone(page, [fixturePath('minimal.pdf'), fixturePath('minimal2.pdf')]); const banner = page.getByRole('status').filter({ hasText: /hochgeladen/ }); await expect(banner).toBeVisible(); await expect(banner).toContainText(/2 Dokumente/); await banner.getByRole('link', { name: /ergänzen/i }).click(); await expect(page).toHaveURL(/\/enrich(\/|$)/); }); }); test.describe('Dashboard enrichment block — axe sweep', () => { for (const viewport of VIEWPORTS) { for (const scheme of ['light', 'dark'] as const) { test(`no wcag2a/wcag2aa violations at ${viewport.name}px (${scheme})`, async ({ browser }) => { const context = await browser.newContext({ colorScheme: scheme, viewport: { width: viewport.width, height: viewport.height }, storageState: path.join(__dirname, '.auth/user.json') }); const page = await context.newPage(); await page.goto('/'); await page.waitForSelector('[data-hydrated]'); const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze(); if (results.violations.length > 0) { console.log( `Violations on dashboard @ ${viewport.name}px ${scheme}:\n` + results.violations.map((v) => `[${v.impact}] ${v.id}`).join('\n') ); } expect(results.violations).toEqual([]); await context.close(); }); } } });