Adds 5 new components for the permission-gated reader layout: - ReaderStatsStrip: stat tiles (documents / persons / stories) linking to list pages - ReaderPersonChips: top-N persons by doc count with avatar + name - ReaderDraftsModule: blog draft list for BLOG_WRITE users - ReaderRecentDocs: 5 most-recently-updated docs with Neu/Aktualisiert badge - ReaderRecentStories: 3 latest published stories with 150-char HTML-stripped excerpt Each component ships with a vitest-browser spec covering the key assertions. Avatar color/initials logic is inlined to satisfy $lib/shared → $lib/person boundary rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.1 KiB
TypeScript
67 lines
2.1 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 chip 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('shows document count in each chip', async () => {
|
|
render(ReaderPersonChips, { persons: [person1] });
|
|
const chip = page.getByRole('link', { name: /Anna Müller/ });
|
|
await expect.element(chip).toBeInTheDocument();
|
|
const text = ((await chip.element()) as HTMLElement).textContent;
|
|
expect(text).toContain('23');
|
|
});
|
|
|
|
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('renders empty state without chips when persons array is empty', async () => {
|
|
render(ReaderPersonChips, { persons: [] });
|
|
const chips = page.getByRole('link', { name: /Müller|Schmidt/ });
|
|
await expect.element(chips).not.toBeInTheDocument();
|
|
});
|
|
});
|