Six categories of breakage:
1. date.ts — add formatGermanDateInput(raw: string): string as a pure
function covering both digit-stream auto-dot and manual-dot-with-padding
modes. Refactor handleGermanDateInput to delegate to it. Fixes 16 failures
in date.spec.ts where the function was imported but didn't exist.
2. Admin layout specs (groups/tags/users) — $effect fires on initial mount
with manualCollapse=false, so the spy captured 'false' before the click's
effect ran. Fix: move spy setup after render(), add await setTimeout(0) to
flush Svelte effects before asserting.
3. DashboardMentions — component now renders a persistent
"Benachrichtigungsverlauf ansehen" link, making getByRole('link') strict-
mode violations. Fix: scope link queries to the actor name, and check
absence of the actor link (not all links) in the no-documentId test.
4. Conversations page — empty-state copy changed from "Wählen Sie zwei
Personen aus" to "Korrespondenz durchsuchen". Update the test.
5. Login page — AuthHeader adds a second aria-label="Familienarchiv" link.
Use .first() to avoid strict-mode violation.
6. Persons page — alias is rendered with German quotation marks „…" not
straight quotes "…". Update the test string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||
import { cleanup, render } from 'vitest-browser-svelte';
|
||
import { page } from 'vitest/browser';
|
||
import Page from './+page.svelte';
|
||
|
||
const tick = () => new Promise((r) => setTimeout(r, 0));
|
||
|
||
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||
|
||
const makePerson = (overrides = {}) => ({
|
||
id: '1',
|
||
firstName: 'Max',
|
||
lastName: 'Mustermann',
|
||
documentCount: 0,
|
||
...overrides
|
||
});
|
||
|
||
const defaultStats = { totalPersons: 0, totalDocuments: 0 };
|
||
const emptyData = {
|
||
user: undefined,
|
||
canWrite: true,
|
||
canAnnotate: false,
|
||
q: '',
|
||
persons: [],
|
||
stats: defaultStats
|
||
};
|
||
const dataWithPersons = {
|
||
...emptyData,
|
||
persons: [makePerson()],
|
||
stats: { totalPersons: 1, totalDocuments: 3 }
|
||
};
|
||
|
||
afterEach(cleanup);
|
||
|
||
// ─── Rendering ────────────────────────────────────────────────────────────────
|
||
|
||
describe('Persons page – rendering', () => {
|
||
it('renders the search input', async () => {
|
||
render(Page, { data: emptyData });
|
||
await expect.element(page.getByRole('textbox')).toBeInTheDocument();
|
||
});
|
||
|
||
it('pre-fills the search input from data.q', async () => {
|
||
render(Page, { data: { ...emptyData, q: 'Müller' } });
|
||
await expect.element(page.getByRole('textbox')).toHaveValue('Müller');
|
||
});
|
||
|
||
it('shows empty state when no persons', async () => {
|
||
render(Page, { data: emptyData });
|
||
await expect.element(page.getByText('Keine Personen gefunden')).toBeInTheDocument();
|
||
});
|
||
|
||
it('renders person cards', async () => {
|
||
render(Page, { data: dataWithPersons });
|
||
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
|
||
});
|
||
|
||
it('links person card to detail page', async () => {
|
||
render(Page, { data: dataWithPersons });
|
||
await expect
|
||
.element(page.getByRole('link', { name: /Max Mustermann/ }))
|
||
.toHaveAttribute('href', '/persons/1');
|
||
});
|
||
|
||
it('shows alias in italic when provided', async () => {
|
||
render(Page, { data: { ...emptyData, persons: [makePerson({ alias: 'Maxi' })] } });
|
||
await expect.element(page.getByText('„Maxi"')).toBeInTheDocument();
|
||
});
|
||
|
||
it('shows life date range when birthYear is provided', async () => {
|
||
render(Page, { data: { ...emptyData, persons: [makePerson({ birthYear: 1900 })] } });
|
||
await expect.element(page.getByText('* 1900')).toBeInTheDocument();
|
||
});
|
||
|
||
it('shows stats bar with person and document counts', async () => {
|
||
render(Page, { data: dataWithPersons });
|
||
await expect.element(page.getByText(/1 Person/)).toBeInTheDocument();
|
||
await expect.element(page.getByText(/3 Dokumente/)).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
// ─── Keystroke preservation (issue #34) ──────────────────────────────────────
|
||
|
||
describe('Persons page – search input keystroke preservation', () => {
|
||
it('does not overwrite the search input while the user is focused and stale data arrives', async () => {
|
||
const { rerender } = render(Page, { data: emptyData });
|
||
|
||
const input = page.getByRole('textbox');
|
||
|
||
// User types "abc" — input is focused
|
||
await input.click();
|
||
await input.fill('abc');
|
||
|
||
// Simulate a navigation completing with stale data (q='a') while the user is still typing
|
||
await rerender({ data: { ...emptyData, q: 'a' } });
|
||
await tick();
|
||
|
||
// Input must still show what the user typed, not the stale URL value
|
||
await expect.element(input).toHaveValue('abc');
|
||
});
|
||
});
|