diff --git a/frontend/e2e/header.spec.ts b/frontend/e2e/header.spec.ts new file mode 100644 index 00000000..8317c742 --- /dev/null +++ b/frontend/e2e/header.spec.ts @@ -0,0 +1,96 @@ +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 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 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 bg = await page.locator('header').evaluate((el) => getComputedStyle(el).backgroundColor); + 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', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('login page has brand-navy header with language switcher', async ({ page }) => { + await page.goto('/login'); + + const header = page.locator('header'); + await expect(header).toBeVisible(); + + const bg = await header.evaluate((el) => getComputedStyle(el).backgroundColor); + expect(bg).toBe(BRAND_NAVY); + + 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 e14029db..314fb54c 100644 --- a/frontend/src/lib/components/LanguageSwitcher.svelte +++ b/frontend/src/lib/components/LanguageSwitcher.svelte @@ -1,6 +1,8 @@ -
-