Files
familienarchiv/frontend/src/routes/persons/[id]/PersonDocumentList.svelte.test.ts
Marcel 1f3c18f898 test(persons): cover PersonDocumentList and persons/new page
PersonDocumentList: empty/populated, year-range derivation across
no-date/single-year/multi-year inputs, sort toggle visibility (>1 doc),
sort-direction round trip, preview-limit + show-more expansion,
title→originalFilename fallback, no-date and no-location branches.

persons/new: PERSON vs INSTITUTION/GROUP visibility matrix
(firstName/alias/life-year fields toggle), lastName label switching
between Vorname/Nachname/Name, form-error banner, prior-form hydration,
cancel link href, fallback to PERSON for unknown personType.

24 tests across two files, hitting the 32+28 = 60 branches at the top
of the issue's leverage list.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00

183 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import PersonDocumentList from './PersonDocumentList.svelte';
afterEach(cleanup);
const makeDoc = (overrides: Record<string, unknown> = {}) => ({
id: 'd1',
title: 'Brief an Helene',
originalFilename: 'brief.pdf',
documentDate: '1923-04-15',
location: 'Berlin',
status: 'UPLOADED' as string,
contentType: 'application/pdf',
thumbnailUrl: '',
...overrides
});
describe('PersonDocumentList', () => {
it('renders the heading and a count badge', async () => {
render(PersonDocumentList, {
props: { documents: [makeDoc()], heading: 'Gesendet', emptyMessage: 'Keine Dokumente' }
});
await expect.element(page.getByRole('heading', { name: /gesendet/i })).toBeVisible();
await expect.element(page.getByText('1', { exact: true })).toBeVisible();
});
it('renders the empty message when documents is an empty array', async () => {
render(PersonDocumentList, {
props: {
documents: [],
heading: 'Empfangen',
emptyMessage: 'Es liegen keine Dokumente vor.'
}
});
await expect.element(page.getByText('Es liegen keine Dokumente vor.')).toBeVisible();
});
it('hides the year range when no document has a date', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ documentDate: null })],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByText(/^\d{4}\s*\s*\d{4}$/)).not.toBeInTheDocument();
});
it('shows a single year when all documents fall in the same year', async () => {
render(PersonDocumentList, {
props: {
documents: [
makeDoc({ documentDate: '1923-01-01' }),
makeDoc({ id: 'd2', documentDate: '1923-12-30' })
],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByText('1923', { exact: true })).toBeVisible();
});
it('shows a minmax range when documents span multiple years', async () => {
render(PersonDocumentList, {
props: {
documents: [
makeDoc({ id: 'd1', documentDate: '1899-01-01' }),
makeDoc({ id: 'd2', documentDate: '1923-12-31' })
],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByText('1899 1923')).toBeVisible();
});
it('does not render the sort toggle when only one document is present', async () => {
render(PersonDocumentList, {
props: { documents: [makeDoc()], heading: 'X', emptyMessage: 'X' }
});
await expect
.element(page.getByRole('button', { name: /neueste zuerst|älteste zuerst/i }))
.not.toBeInTheDocument();
});
it('renders the sort toggle when at least two documents are present', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ id: 'd1' }), makeDoc({ id: 'd2', documentDate: '1900-01-01' })],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByRole('button', { name: /neueste zuerst/i })).toBeVisible();
});
it('toggles the sort direction label when the sort button is clicked', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ id: 'd1' }), makeDoc({ id: 'd2', documentDate: '1900-01-01' })],
heading: 'X',
emptyMessage: 'X'
}
});
await page.getByRole('button', { name: /neueste zuerst/i }).click();
await expect.element(page.getByRole('button', { name: /älteste zuerst/i })).toBeVisible();
});
it('caps the visible documents at the preview limit and exposes a "show more" button', async () => {
const docs = Array.from({ length: 8 }, (_, i) =>
makeDoc({ id: `d${i}`, title: `Brief ${i + 1}` })
);
render(PersonDocumentList, {
props: { documents: docs, heading: 'X', emptyMessage: 'X' }
});
await expect.element(page.getByText('Brief 1')).toBeVisible();
await expect.element(page.getByText('Brief 6')).not.toBeInTheDocument();
await expect.element(page.getByRole('button', { name: /weitere anzeigen/i })).toBeVisible();
});
it('expands the list to all documents when the "show more" button is clicked', async () => {
const docs = Array.from({ length: 8 }, (_, i) =>
makeDoc({ id: `d${i}`, title: `Brief ${i + 1}` })
);
render(PersonDocumentList, {
props: { documents: docs, heading: 'X', emptyMessage: 'X' }
});
await page.getByRole('button', { name: /weitere anzeigen/i }).click();
await expect.element(page.getByText('Brief 6')).toBeVisible();
});
it('falls back to the originalFilename when title is missing', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ title: null, originalFilename: 'untitled.pdf' })],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByText('untitled.pdf')).toBeVisible();
});
it('renders "Kein Datum" when documentDate is missing', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ documentDate: null })],
heading: 'X',
emptyMessage: 'X'
}
});
await expect.element(page.getByText('Kein Datum')).toBeVisible();
});
it('omits the location separator when location is null', async () => {
render(PersonDocumentList, {
props: {
documents: [makeDoc({ location: null })],
heading: 'X',
emptyMessage: 'X'
}
});
const meta = document.querySelector('.font-sans.text-\\[11px\\]');
expect(meta?.textContent ?? '').not.toMatch(/·/);
});
});