Files
familienarchiv/frontend/src/lib/document/DocumentMetadataDrawer.svelte.test.ts
Marcel 909c547e0e test(document): cover DocumentMetadataDrawer column branches
Sixteen tests covering the four-column drawer: details column always
renders, persons column branches (no-persons placeholder vs sender
vs receivers), receiver overflow + show-all toggle, tags column
branches (placeholder vs anchor list with /?tag href encoding),
geschichten column visibility (hidden by default, shown for
canBlogWrite, attach link gated on canBlogWrite + documentId, list
rendering, show-all overflow), inferred-relationship pill on the
single-receiver branch.

Refs #496.

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

208 lines
7.0 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import DocumentMetadataDrawer from './DocumentMetadataDrawer.svelte';
afterEach(cleanup);
const sender = { id: 's1', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' };
const receiver = (id: string, name: string) => ({
id,
firstName: name.split(' ')[0],
lastName: name.split(' ').slice(1).join(' ') || name,
displayName: name
});
const baseProps = {
documentDate: '1923-04-15' as string | null,
location: 'Berlin' as string | null,
status: 'UPLOADED',
sender: null as typeof sender | null,
receivers: [] as ReturnType<typeof receiver>[],
tags: [] as { id: string; name: string }[],
inferredRelationship: null,
geschichten: [] as {
id: string;
title: string;
publishedAt?: string;
author?: { firstName?: string; lastName?: string; email: string };
}[],
documentId: 'doc-1',
canBlogWrite: false
};
describe('DocumentMetadataDrawer', () => {
it('renders the three default section headings', async () => {
render(DocumentMetadataDrawer, { props: baseProps });
await expect.element(page.getByRole('heading', { name: 'Details' })).toBeVisible();
await expect.element(page.getByRole('heading', { name: 'Personen' })).toBeVisible();
await expect.element(page.getByRole('heading', { name: 'Schlagwörter' })).toBeVisible();
});
it('renders the formatted long date when documentDate is provided', async () => {
render(DocumentMetadataDrawer, { props: baseProps });
// formatDate default ('long') format is "15. April 1923" in de-DE.
await expect.element(page.getByText(/1923/)).toBeVisible();
});
it('renders an em-dash when documentDate is null', async () => {
render(DocumentMetadataDrawer, { props: { ...baseProps, documentDate: null } });
// The dash appears in date AND location AND geschichten — multiple matches expected
const dashes = document.querySelectorAll('dd, p');
const dashTexts = Array.from(dashes)
.map((el) => el.textContent?.trim())
.filter((t) => t === '—');
expect(dashTexts.length).toBeGreaterThan(0);
});
it('renders the no-persons placeholder when sender and receivers are empty', async () => {
render(DocumentMetadataDrawer, { props: baseProps });
await expect.element(page.getByText('Keine Personen zugeordnet')).toBeVisible();
});
it('renders the sender and inferred relationship label when both are present', async () => {
render(DocumentMetadataDrawer, {
props: {
...baseProps,
sender,
inferredRelationship: { labelFromA: 'Vater', labelFromB: 'Tochter' }
}
});
await expect.element(page.getByText('Anna Schmidt')).toBeVisible();
});
it('renders the receivers list with up to five visible by default', async () => {
const receivers = Array.from({ length: 7 }, (_, i) => receiver(`r${i}`, `Person ${i}`));
render(DocumentMetadataDrawer, {
props: { ...baseProps, sender, receivers }
});
await expect.element(page.getByText('Person 0')).toBeVisible();
await expect.element(page.getByText('Person 4')).toBeVisible();
await expect.element(page.getByText('Person 5')).not.toBeInTheDocument();
});
it('renders the +N more button when there are more than five receivers', async () => {
const receivers = Array.from({ length: 8 }, (_, i) => receiver(`r${i}`, `Person ${i}`));
render(DocumentMetadataDrawer, {
props: { ...baseProps, sender, receivers }
});
await expect.element(page.getByRole('button', { name: /\+3 weitere/i })).toBeVisible();
});
it('expands the receiver list when the +N more button is clicked', async () => {
const receivers = Array.from({ length: 8 }, (_, i) => receiver(`r${i}`, `Person ${i}`));
render(DocumentMetadataDrawer, {
props: { ...baseProps, sender, receivers }
});
await page.getByRole('button', { name: /\+3 weitere/i }).click();
await expect.element(page.getByText('Person 7')).toBeVisible();
});
it('renders the no-tags placeholder when tags is empty', async () => {
render(DocumentMetadataDrawer, { props: baseProps });
await expect.element(page.getByText('Keine Schlagwörter zugeordnet')).toBeVisible();
});
it('renders one anchor per tag when tags are present', async () => {
render(DocumentMetadataDrawer, {
props: {
...baseProps,
tags: [
{ id: 't1', name: 'Familie' },
{ id: 't2', name: 'Reise' }
]
}
});
await expect
.element(page.getByRole('link', { name: 'Familie' }))
.toHaveAttribute('href', '/?tag=Familie');
await expect
.element(page.getByRole('link', { name: 'Reise' }))
.toHaveAttribute('href', '/?tag=Reise');
});
it('hides the geschichten column when there are no stories and no canBlogWrite', async () => {
render(DocumentMetadataDrawer, { props: baseProps });
await expect
.element(page.getByRole('heading', { name: 'Geschichten' }))
.not.toBeInTheDocument();
});
it('shows the geschichten column when canBlogWrite is true even with no stories', async () => {
render(DocumentMetadataDrawer, { props: { ...baseProps, canBlogWrite: true } });
await expect.element(page.getByRole('heading', { name: 'Geschichten' })).toBeVisible();
});
it('renders the attach link to the new-geschichte route when canBlogWrite + documentId', async () => {
render(DocumentMetadataDrawer, {
props: { ...baseProps, canBlogWrite: true, documentId: 'doc-42' }
});
const links = document.querySelectorAll('a[href*="/geschichten/new?documentId="]');
expect(links.length).toBe(1);
expect((links[0] as HTMLAnchorElement).href).toContain('documentId=doc-42');
});
it('renders the geschichten list when stories are present', async () => {
render(DocumentMetadataDrawer, {
props: {
...baseProps,
geschichten: [
{
id: 'g1',
title: 'Reise nach Berlin',
publishedAt: '2026-04-15T10:00:00Z',
author: { firstName: 'Anna', lastName: 'Schmidt', email: 'anna@x' }
}
]
}
});
await expect.element(page.getByRole('link', { name: /reise nach berlin/i })).toBeVisible();
});
it('renders the show-all geschichten link when there are at least three stories', async () => {
render(DocumentMetadataDrawer, {
props: {
...baseProps,
geschichten: Array.from({ length: 3 }, (_, i) => ({
id: `g${i}`,
title: `Geschichte ${i}`,
publishedAt: '2026-04-15T10:00:00Z',
author: { firstName: 'Anna', lastName: 'Schmidt', email: 'anna@x' }
}))
}
});
await expect.element(page.getByText(/zeige alle|alle/i)).toBeVisible();
});
it('renders the receiver-only inferred relationship pill only when there is exactly one receiver', async () => {
render(DocumentMetadataDrawer, {
props: {
...baseProps,
sender,
receivers: [receiver('r1', 'Bert Meier')],
inferredRelationship: { labelFromA: 'Vater', labelFromB: 'Tochter' }
}
});
// Both labels should be visible — Vater for sender, Tochter for the single receiver
await expect.element(page.getByText(/vater/i)).toBeVisible();
await expect.element(page.getByText(/tochter/i)).toBeVisible();
});
});