Compare commits
2 Commits
281934529e
...
2dd73cf594
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd73cf594 | ||
|
|
53038dea68 |
@@ -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([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
94
frontend/src/lib/components/LanguageSwitcher.svelte.spec.ts
Normal file
94
frontend/src/lib/components/LanguageSwitcher.svelte.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import LanguageSwitcher from './LanguageSwitcher.svelte';
|
||||
|
||||
const mockSetLocale = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('$lib/paraglide/runtime', () => ({
|
||||
getLocale: vi.fn(() => 'de'),
|
||||
setLocale: mockSetLocale
|
||||
}));
|
||||
|
||||
beforeEach(() => mockSetLocale.mockClear());
|
||||
afterEach(cleanup);
|
||||
|
||||
// ─── inverted=true (dark background) ──────────────────────────────────────
|
||||
|
||||
describe('LanguageSwitcher – inverted=true', () => {
|
||||
it('active locale button has text-white and font-bold', async () => {
|
||||
render(LanguageSwitcher, { inverted: true });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'DE' }).element();
|
||||
|
||||
expect(el.className).toMatch(/\btext-white\b/);
|
||||
expect(el.className).toMatch(/\bfont-bold\b/);
|
||||
});
|
||||
|
||||
it('inactive locale buttons have text-white/70', async () => {
|
||||
render(LanguageSwitcher, { inverted: true });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'EN' }).element();
|
||||
|
||||
expect(el.className).toMatch(/text-white\/70/);
|
||||
});
|
||||
|
||||
it('inactive locale buttons do not have font-bold', async () => {
|
||||
render(LanguageSwitcher, { inverted: true });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'EN' }).element();
|
||||
|
||||
expect(el.className).not.toMatch(/\bfont-bold\b/);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── inverted=false (light background) ─────────────────────────────────────
|
||||
|
||||
describe('LanguageSwitcher – inverted=false', () => {
|
||||
it('active locale button has text-ink and font-bold', async () => {
|
||||
render(LanguageSwitcher, { inverted: false });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'DE' }).element();
|
||||
|
||||
expect(el.className).toMatch(/\btext-ink\b/);
|
||||
expect(el.className).toMatch(/\bfont-bold\b/);
|
||||
});
|
||||
|
||||
it('inactive locale buttons have text-ink-3', async () => {
|
||||
render(LanguageSwitcher, { inverted: false });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'EN' }).element();
|
||||
|
||||
expect(el.className).toMatch(/\btext-ink-3\b/);
|
||||
});
|
||||
|
||||
it('inactive locale buttons do not have text-white', async () => {
|
||||
render(LanguageSwitcher, { inverted: false });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'EN' }).element();
|
||||
|
||||
expect(el.className).not.toMatch(/\btext-white\b/);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── locale switching ──────────────────────────────────────────────────────
|
||||
|
||||
describe('LanguageSwitcher – locale switching', () => {
|
||||
it('calls setLocale with en when EN button is clicked', async () => {
|
||||
render(LanguageSwitcher, { inverted: false });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'EN' }).element();
|
||||
el.click();
|
||||
|
||||
expect(mockSetLocale).toHaveBeenCalledWith('en');
|
||||
});
|
||||
|
||||
it('calls setLocale with es when ES button is clicked', async () => {
|
||||
render(LanguageSwitcher, { inverted: false });
|
||||
|
||||
const el = await page.getByRole('button', { name: 'ES' }).element();
|
||||
el.click();
|
||||
|
||||
expect(mockSetLocale).toHaveBeenCalledWith('es');
|
||||
});
|
||||
});
|
||||
@@ -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()}
|
||||
</a>
|
||||
@@ -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()}
|
||||
</a>
|
||||
@@ -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()}
|
||||
</a>
|
||||
@@ -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()}
|
||||
</a>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
||||
|
||||
const locales = ['DE', 'EN', 'ES'] as const;
|
||||
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
|
||||
const activeLocale = $derived(getLocale().toUpperCase());
|
||||
import LanguageSwitcher from '$lib/components/LanguageSwitcher.svelte';
|
||||
</script>
|
||||
|
||||
<header class="bg-brand-navy">
|
||||
@@ -16,18 +12,7 @@ const activeLocale = $derived(getLocale().toUpperCase());
|
||||
>
|
||||
</a>
|
||||
<div class="flex items-center gap-1">
|
||||
{#each locales as locale (locale)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => setLocale(localeMap[locale])}
|
||||
class="px-1.5 py-1 font-sans text-xs tracking-widest transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent
|
||||
{activeLocale === locale
|
||||
? 'font-bold text-white'
|
||||
: 'font-normal text-white/55 hover:text-white/85'}"
|
||||
>
|
||||
{locale}
|
||||
</button>
|
||||
{/each}
|
||||
<LanguageSwitcher inverted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user