import { describe, it, expect, afterEach } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import ReaderPersonChips from './ReaderPersonChips.svelte'; import type { components } from '$lib/generated/api'; type PersonSummaryDTO = components['schemas']['PersonSummaryDTO']; afterEach(() => { cleanup(); }); const person1: PersonSummaryDTO = { id: 'aaaaaaaa-0000-0000-0000-000000000001', firstName: 'Anna', lastName: 'Müller', displayName: 'Anna Müller', documentCount: 23, personType: 'PERSON', familyMember: false }; const person2: PersonSummaryDTO = { id: 'aaaaaaaa-0000-0000-0000-000000000002', firstName: 'Karl', lastName: 'Schmidt', displayName: 'Karl Schmidt', documentCount: 5, personType: 'PERSON', familyMember: false }; describe('ReaderPersonChips', () => { it('renders a card for each person with correct href', async () => { render(ReaderPersonChips, { persons: [person1, person2] }); const link1 = page.getByRole('link', { name: /Anna Müller/ }); await expect .element(link1) .toHaveAttribute('href', '/persons/aaaaaaaa-0000-0000-0000-000000000001'); const link2 = page.getByRole('link', { name: /Karl Schmidt/ }); await expect .element(link2) .toHaveAttribute('href', '/persons/aaaaaaaa-0000-0000-0000-000000000002'); }); it('person card has min-h-[44px] touch target', async () => { render(ReaderPersonChips, { persons: [person1] }); const link = page.getByRole('link', { name: /Anna Müller/ }); const cls = ((await link.element()) as HTMLElement).className; expect(cls).toMatch(/min-h-\[44px\]/); }); it('doc count renders as neutral chip with bg-muted', async () => { render(ReaderPersonChips, { persons: [person1] }); const link = page.getByRole('link', { name: /Anna Müller/ }); const el = (await link.element()) as HTMLElement; const chip = el.querySelector('[class*="bg-muted"]'); expect(chip).not.toBeNull(); expect(chip!.textContent).toContain('23'); }); it('doc count chip has rounded-full and border-line classes', async () => { render(ReaderPersonChips, { persons: [person1] }); const link = page.getByRole('link', { name: /Anna Müller/ }); const el = (await link.element()) as HTMLElement; const chip = el.querySelector('[class*="bg-muted"]'); expect(chip).not.toBeNull(); expect(chip!.className).toMatch(/rounded-full/); expect(chip!.className).toMatch(/border-line/); }); it('person grid uses grid layout', async () => { render(ReaderPersonChips, { persons: [person1, person2] }); const section = page.getByRole('region'); const el = (await section.element()) as HTMLElement; const grid = el.querySelector('[class*="grid"]'); expect(grid).not.toBeNull(); }); it('wrapper is a section with aria-label', async () => { render(ReaderPersonChips, { persons: [person1] }); const section = page.getByRole('region'); await expect.element(section).toBeInTheDocument(); const label = ((await section.element()) as HTMLElement).getAttribute('aria-label'); expect(label).toBeTruthy(); }); it('renders an "Alle Personen" link to /persons', async () => { render(ReaderPersonChips, { persons: [person1] }); const allLink = page.getByRole('link', { name: /Alle Personen/i }); await expect.element(allLink).toHaveAttribute('href', '/persons'); }); it('"Alle Personen" link has text-link-quiet class', async () => { render(ReaderPersonChips, { persons: [person1] }); const allLink = page.getByRole('link', { name: /Alle Personen/i }); const cls = ((await allLink.element()) as HTMLElement).className; expect(cls).toMatch(/text-link-quiet/); }); it('exposes a focus-visible ring on the "Alle Personen" link', async () => { render(ReaderPersonChips, { persons: [person1] }); const allLink = page.getByRole('link', { name: /Alle Personen/i }); const cls = ((await allLink.element()) as HTMLElement).className; expect(cls).toMatch(/focus-visible:ring-2/); expect(cls).toMatch(/focus-visible:ring-brand-navy/); }); it('meets the 44px touch target on the "Alle Personen" link', async () => { render(ReaderPersonChips, { persons: [person1] }); const allLink = page.getByRole('link', { name: /Alle Personen/i }); const cls = ((await allLink.element()) as HTMLElement).className; expect(cls).toMatch(/min-h-\[44px\]/); }); it('does not render h2 heading', async () => { render(ReaderPersonChips, { persons: [person1] }); const heading = page.getByRole('heading', { level: 2 }); await expect.element(heading).not.toBeInTheDocument(); }); it('renders empty state without person cards when persons array is empty', async () => { render(ReaderPersonChips, { persons: [] }); const chips = page.getByRole('link', { name: /Müller|Schmidt/ }); await expect.element(chips).not.toBeInTheDocument(); }); it('renders an empty-state message when persons array is empty', async () => { render(ReaderPersonChips, { persons: [] }); const message = page.getByText(/Noch keine Personen im Archiv/i); await expect.element(message).toBeInTheDocument(); }); });