diff --git a/frontend/e2e/header.spec.ts b/frontend/e2e/header.spec.ts index bda4cee3..8317c742 100644 --- a/frontend/e2e/header.spec.ts +++ b/frontend/e2e/header.spec.ts @@ -1,19 +1,29 @@ import { test, expect } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +// #012851 — brand-navy, defined as --c-primary in layout.css const BRAND_NAVY = 'rgb(1, 40, 81)'; test.describe('Header — brand-navy background', () => { test('header background is brand-navy in light mode', async ({ page }) => { await page.goto('/'); - await page.waitForSelector('[data-hydrated]'); + await expect(page.getByRole('navigation')).toBeVisible(); const bg = await page.locator('header').evaluate((el) => getComputedStyle(el).backgroundColor); expect(bg).toBe(BRAND_NAVY); }); + test('header passes accessibility audit in light mode', async ({ page }) => { + await page.goto('/'); + await expect(page.getByRole('navigation')).toBeVisible(); + + const results = await new AxeBuilder({ page }).include('header').analyze(); + expect(results.violations).toEqual([]); + }); + test('header background stays brand-navy after switching to dark mode', async ({ page }) => { await page.goto('/'); - await page.waitForSelector('[data-hydrated]'); + await expect(page.getByRole('navigation')).toBeVisible(); await page .getByRole('banner') @@ -25,12 +35,40 @@ test.describe('Header — brand-navy background', () => { expect(bg).toBe(BRAND_NAVY); }); + test('header passes accessibility audit in dark mode', async ({ page }) => { + await page.goto('/'); + await expect(page.getByRole('navigation')).toBeVisible(); + + await page + .getByRole('banner') + .getByRole('button', { name: /dark mode/i }) + .click(); + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); + + const results = await new AxeBuilder({ page }).include('header').analyze(); + expect(results.violations).toEqual([]); + }); + test('logo text is visible at 375px viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }); await page.goto('/'); await expect(page.getByRole('banner').getByText('Familienarchiv')).toBeVisible(); }); + + test('hamburger menu opens on tablet viewport (768px)', async ({ page }) => { + await page.setViewportSize({ width: 768, height: 1024 }); + await page.goto('/'); + await expect(page.getByRole('navigation')).toBeVisible(); + + const hamburger = page.getByRole('button', { name: /menü öffnen/i }); + await expect(hamburger).toBeVisible(); + await hamburger.click(); + + await expect( + page.getByRole('navigation', { name: /mobile/i }).or(page.locator('#mobile-nav')) + ).toBeVisible(); + }); }); test.describe('Login page — AuthHeader', () => { @@ -47,4 +85,12 @@ test.describe('Login page — AuthHeader', () => { await expect(header.getByRole('button', { name: 'DE' })).toBeVisible(); }); + + test('login page header passes accessibility audit', async ({ page }) => { + await page.goto('/login'); + await expect(page.locator('header')).toBeVisible(); + + const results = await new AxeBuilder({ page }).include('header').analyze(); + expect(results.violations).toEqual([]); + }); }); diff --git a/frontend/src/lib/components/LanguageSwitcher.svelte b/frontend/src/lib/components/LanguageSwitcher.svelte index 45bc5885..314fb54c 100644 --- a/frontend/src/lib/components/LanguageSwitcher.svelte +++ b/frontend/src/lib/components/LanguageSwitcher.svelte @@ -18,7 +18,7 @@ const activeLocale = $derived(getLocale().toUpperCase()); ? 'font-bold text-white' : 'font-bold text-ink' : inverted - ? 'font-normal text-white/55 hover:text-white/85' + ? 'font-normal text-white/70 hover:text-white' : 'font-normal text-ink-3 hover:text-ink'}" > {locale} diff --git a/frontend/src/routes/AppNav.svelte b/frontend/src/routes/AppNav.svelte index 7744ca3f..32fce2e3 100644 --- a/frontend/src/routes/AppNav.svelte +++ b/frontend/src/routes/AppNav.svelte @@ -44,7 +44,7 @@ function handleOverlayKeydown(event: KeyboardEvent) { class="my-2 inline-flex items-center px-3 font-sans text-xs font-bold tracking-widest uppercase transition-colors focus:outline-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-accent {page.url.pathname === '/' || page.url.pathname.startsWith('/documents') ? 'border-b-2 border-accent text-white' - : 'text-white/55 hover:text-white/85'}" + : 'text-white/70 hover:text-white'}" > {m.nav_documents()} @@ -54,7 +54,7 @@ function handleOverlayKeydown(event: KeyboardEvent) { class="my-2 inline-flex items-center px-3 font-sans text-xs font-bold tracking-widest uppercase transition-colors focus:outline-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-accent {page.url.pathname.startsWith('/persons') ? 'border-b-2 border-accent text-white' - : 'text-white/55 hover:text-white/85'}" + : 'text-white/70 hover:text-white'}" > {m.nav_persons()} @@ -64,7 +64,7 @@ function handleOverlayKeydown(event: KeyboardEvent) { class="my-2 inline-flex items-center px-3 font-sans text-xs font-bold tracking-widest uppercase transition-colors focus:outline-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-accent {page.url.pathname.startsWith('/korrespondenz') ? 'border-b-2 border-accent text-white' - : 'text-white/55 hover:text-white/85'}" + : 'text-white/70 hover:text-white'}" > {m.nav_conversations()} @@ -74,7 +74,7 @@ function handleOverlayKeydown(event: KeyboardEvent) { class="my-2 inline-flex items-center px-3 font-sans text-xs font-bold tracking-widest uppercase transition-colors focus:outline-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-accent {page.url.pathname.startsWith('/admin') ? 'border-b-2 border-accent text-white' - : 'text-white/55 hover:text-white/85'}" + : 'text-white/70 hover:text-white'}" > {m.nav_admin()} diff --git a/frontend/src/routes/AuthHeader.svelte b/frontend/src/routes/AuthHeader.svelte index 1e6a34a9..6e8aafd6 100644 --- a/frontend/src/routes/AuthHeader.svelte +++ b/frontend/src/routes/AuthHeader.svelte @@ -1,9 +1,5 @@
@@ -16,18 +12,7 @@ const activeLocale = $derived(getLocale().toUpperCase()); >
- {#each locales as locale (locale)} - - {/each} +