import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; vi.mock('$app/navigation', () => ({ goto: vi.fn() })); vi.mock('$app/state', () => ({ navigating: { to: null } })); import Page from './+page.svelte'; import type { PageData } from './$types'; afterEach(() => { cleanup(); vi.clearAllMocks(); }); function person(id: string, displayName: string) { return { id, firstName: displayName.split(' ')[0] ?? displayName, lastName: displayName.split(' ').slice(1).join(' ') || 'X', displayName, personType: 'PERSON' }; } function makeData(overrides: Partial = {}): PageData { return { geschichten: [], personFilters: [], documentFilter: null, canBlogWrite: false, ...overrides } as unknown as PageData; } describe('geschichten page — multi-person filter chips', () => { it('renders one chip per person in personFilters', async () => { render(Page, { data: makeData({ personFilters: [person('a', 'Anna A'), person('b', 'Bertha B')] as PageData['personFilters'] }) }); await expect .element(page.getByRole('button', { name: /Anna A aus Filter entfernen/ })) .toBeVisible(); await expect .element(page.getByRole('button', { name: /Bertha B aus Filter entfernen/ })) .toBeVisible(); }); it('renders the "All" pill in pressed state when no filters are active', async () => { render(Page, { data: makeData() }); await expect .element(page.getByRole('button', { name: 'Alle' })) .toHaveAttribute('aria-pressed', 'true'); }); it('renders the "All" pill in unpressed state when at least one filter is active', async () => { render(Page, { data: makeData({ personFilters: [person('a', 'Anna A')] as PageData['personFilters'] }) }); await expect .element(page.getByRole('button', { name: 'Alle' })) .toHaveAttribute('aria-pressed', 'false'); }); it('clicking × on a chip removes only that person from the URL', async () => { const { goto } = await import('$app/navigation'); vi.mocked(goto).mockClear(); // Seed window.location so the chip-removal logic builds the new URL deterministically. const originalHref = window.location.href; window.history.replaceState({}, '', '/geschichten?personId=a&personId=b'); render(Page, { data: makeData({ personFilters: [person('a', 'Anna A'), person('b', 'Bertha B')] as PageData['personFilters'] }) }); await page.getByRole('button', { name: /Anna A aus Filter entfernen/ }).click(); expect(goto).toHaveBeenCalledOnce(); const url = vi.mocked(goto).mock.calls[0][0] as string; expect(url).toContain('personId=b'); expect(url).not.toContain('personId=a'); window.history.replaceState({}, '', originalHref); }); it('shows the "+ Person wählen" button even when filters are already active', async () => { render(Page, { data: makeData({ personFilters: [person('a', 'Anna A')] as PageData['personFilters'] }) }); await expect.element(page.getByRole('button', { name: /Person wählen/ })).toBeVisible(); }); it('renders all filter pills with a 44px touch target (h-11)', async () => { render(Page, { data: makeData({ personFilters: [person('a', 'Anna A')] as PageData['personFilters'] }) }); // All three pill variants must use h-11 (44px) per the senior-author touch-target rule const all = page.getByRole('button', { name: 'Alle' }); const chip = page.getByRole('button', { name: /Anna A aus Filter entfernen/ }); const add = page.getByRole('button', { name: /Person wählen/ }); const allEl = (await all.element()) as HTMLElement; const chipEl = (await chip.element()) as HTMLElement; const addEl = (await add.element()) as HTMLElement; expect(allEl.className).toContain('h-11'); expect(chipEl.className).toContain('h-11'); expect(addEl.className).toContain('h-11'); }); });