Some checks failed
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / Unit & Component Tests (pull_request) Failing after 4m10s
CI / OCR Service Tests (pull_request) Successful in 35s
CI / Backend Unit Tests (pull_request) Failing after 3m33s
- rounded, px-4 py-6, shadow-sm, gap-4 — matches overview card sizing - hover: left accent border + shadow-md (matches overview hover) - avatar: h-12 w-12, font-bold (djb2 palette colors kept) - name: font-bold, group-hover:underline - doc count: neutral bg-muted chip instead of mint pill Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
135 lines
5.0 KiB
TypeScript
135 lines
5.0 KiB
TypeScript
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();
|
|
});
|
|
});
|