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>
This commit is contained in:
207
frontend/src/lib/document/DocumentMetadataDrawer.svelte.test.ts
Normal file
207
frontend/src/lib/document/DocumentMetadataDrawer.svelte.test.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user