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>
165 lines
6.7 KiB
TypeScript
165 lines
6.7 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';
|
||
|
||
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||
|
||
afterEach(cleanup);
|
||
|
||
// ─── Test data ────────────────────────────────────────────────────────────────
|
||
|
||
const baseData = {
|
||
user: undefined,
|
||
canWrite: true,
|
||
canAnnotate: false,
|
||
documents: [],
|
||
initialValues: { senderName: '', receiverName: '' },
|
||
filters: { senderId: '', receiverId: '', from: '', to: '', dir: 'DESC' as const }
|
||
};
|
||
|
||
const withPersons = {
|
||
...baseData,
|
||
filters: { ...baseData.filters, senderId: 'p1', receiverId: 'p2' }
|
||
};
|
||
|
||
const makeDoc = (overrides: Record<string, unknown> = {}) => ({
|
||
id: 'd1',
|
||
title: 'Testbrief',
|
||
originalFilename: 'testbrief.pdf',
|
||
status: 'UPLOADED' as const,
|
||
documentDate: '1923-04-12',
|
||
location: 'Berlin',
|
||
sender: { id: 'p1', firstName: 'Hans', lastName: 'Müller' },
|
||
receivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt' }],
|
||
tags: [],
|
||
transcription: undefined,
|
||
filePath: undefined,
|
||
createdAt: '1923-04-12T00:00:00Z',
|
||
updatedAt: '1923-04-12T00:00:00Z',
|
||
...overrides
|
||
});
|
||
|
||
const withDocs = {
|
||
...withPersons,
|
||
documents: [makeDoc()]
|
||
};
|
||
|
||
// ─── Empty state ──────────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – empty state', () => {
|
||
it('shows the empty-state heading when no persons are selected', async () => {
|
||
render(Page, { data: baseData });
|
||
await expect.element(page.getByText(/Korrespondenz durchsuchen/i)).toBeInTheDocument();
|
||
});
|
||
|
||
it('hides the swap button when no persons are selected', async () => {
|
||
render(Page, { data: baseData });
|
||
// Button is always in the DOM (holds grid column width on desktop) but made invisible
|
||
await expect.element(page.getByTestId('conv-swap-btn')).toHaveClass('invisible');
|
||
});
|
||
|
||
it('does not show the new document link when no persons are selected', async () => {
|
||
render(Page, { data: baseData });
|
||
await expect.element(page.getByTestId('conv-new-doc-link')).not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
// ─── No results ───────────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – no results', () => {
|
||
it('shows "no documents found" when both persons are selected but there are no documents', async () => {
|
||
render(Page, { data: withPersons });
|
||
await expect.element(page.getByText(/Keine Dokumente gefunden/i)).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
// ─── Swap button ──────────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – swap button', () => {
|
||
it('shows the swap button when both persons are selected', async () => {
|
||
render(Page, { data: withPersons });
|
||
await expect.element(page.getByTestId('conv-swap-btn')).not.toHaveClass('invisible');
|
||
});
|
||
|
||
it('calls goto with swapped sender and receiver when clicked', async () => {
|
||
const { goto } = await import('$app/navigation');
|
||
vi.mocked(goto).mockClear();
|
||
render(Page, { data: withPersons });
|
||
document.querySelector<HTMLElement>('[data-testid="conv-swap-btn"]')!.click();
|
||
expect(goto).toHaveBeenCalledWith(expect.stringContaining('senderId=p2'), expect.anything());
|
||
expect(goto).toHaveBeenCalledWith(expect.stringContaining('receiverId=p1'), expect.anything());
|
||
});
|
||
});
|
||
|
||
// ─── Summary ──────────────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – summary', () => {
|
||
it('shows document count and year range when documents are loaded', async () => {
|
||
const data = {
|
||
...withPersons,
|
||
documents: [
|
||
makeDoc({ documentDate: '1923-04-12' }),
|
||
makeDoc({ id: 'd2', documentDate: '1965-08-03' })
|
||
]
|
||
};
|
||
render(Page, { data });
|
||
const summary = page.getByTestId('conv-summary');
|
||
await expect.element(summary).toHaveTextContent('2');
|
||
await expect.element(summary).toHaveTextContent('1923');
|
||
await expect.element(summary).toHaveTextContent('1965');
|
||
});
|
||
});
|
||
|
||
// ─── Year dividers ────────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – year dividers', () => {
|
||
it('renders a year divider for the first document', async () => {
|
||
render(Page, { data: withDocs });
|
||
await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
|
||
});
|
||
|
||
it('renders a divider for each new year in the document list', async () => {
|
||
const data = {
|
||
...withPersons,
|
||
documents: [
|
||
makeDoc({ documentDate: '1923-04-12' }),
|
||
makeDoc({ id: 'd2', documentDate: '1965-08-03' })
|
||
]
|
||
};
|
||
render(Page, { data });
|
||
await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
|
||
await expect.element(page.getByTestId('year-divider').nth(1)).toHaveTextContent('1965');
|
||
});
|
||
|
||
it('does not render a second divider for documents from the same year', async () => {
|
||
const data = {
|
||
...withPersons,
|
||
documents: [
|
||
makeDoc({ documentDate: '1923-04-12' }),
|
||
makeDoc({ id: 'd2', documentDate: '1923-09-01' })
|
||
]
|
||
};
|
||
render(Page, { data });
|
||
// Only one divider for 1923; 1965 divider should not appear
|
||
await expect.element(page.getByTestId('year-divider').first()).toHaveTextContent('1923');
|
||
await expect.element(page.getByTestId('year-divider').nth(1)).not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
// ─── New document link ────────────────────────────────────────────────────────
|
||
|
||
describe('Conversations page – new document link', () => {
|
||
it('shows the link with correct href for a write user', async () => {
|
||
render(Page, { data: { ...withDocs, canWrite: true } });
|
||
const link = page.getByTestId('conv-new-doc-link');
|
||
await expect.element(link).toBeInTheDocument();
|
||
await expect.element(link).toHaveAttribute('href', '/documents/new?senderId=p1&receiverId=p2');
|
||
});
|
||
|
||
it('hides the link for a read-only user', async () => {
|
||
render(Page, { data: { ...withDocs, canWrite: false } });
|
||
await expect.element(page.getByTestId('conv-new-doc-link')).not.toBeInTheDocument();
|
||
});
|
||
});
|