diff --git a/frontend/src/lib/timeline/LetterCard.svelte b/frontend/src/lib/timeline/LetterCard.svelte new file mode 100644 index 00000000..f5f90302 --- /dev/null +++ b/frontend/src/lib/timeline/LetterCard.svelte @@ -0,0 +1,42 @@ + + + + + {#if entry.title} + {entry.title} + {/if} + + {sender} + + {receiver} + {#if dateLabel} + · {dateLabel} + {/if} + + diff --git a/frontend/src/lib/timeline/LetterCard.svelte.spec.ts b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts new file mode 100644 index 00000000..28df3886 --- /dev/null +++ b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import LetterCard from './LetterCard.svelte'; +import { timelineDateLabel } from './dateLabel'; +import { makeEntry } from './test-factories'; + +afterEach(() => cleanup()); + +const DOC_ID = '22222222-2222-2222-2222-222222222222'; + +describe('LetterCard', () => { + it('renders sender, receiver, and title', () => { + render(LetterCard, { + entry: makeEntry({ senderName: 'Karl', receiverName: 'Elfriede', title: 'Feldpost' }) + }); + expect(document.body.textContent).toContain('Karl'); + expect(document.body.textContent).toContain('Elfriede'); + expect(document.body.textContent).toContain('Feldpost'); + }); + + it('renders the precision date exactly as timelineDateLabel returns (REQ-013)', () => { + const entry = makeEntry({ eventDate: '1915-06-15', precision: 'MONTH' }); + const expected = timelineDateLabel(entry.eventDate, entry.precision, entry.eventDateEnd); + expect(expected).toBeTruthy(); + render(LetterCard, { entry }); + expect(document.body.textContent).toContain(expected as string); + }); + + it('renders no date chip when timelineDateLabel returns null (REQ-013)', () => { + const entry = makeEntry({ precision: 'UNKNOWN', eventDate: undefined }); + render(LetterCard, { entry }); + const chip = document.querySelector('[data-testid="letter-date"]'); + expect(chip).toBeNull(); + }); + + it('shows "Unbekannt" for an empty sender, never a bare arrow (REQ-014)', () => { + render(LetterCard, { entry: makeEntry({ senderName: '', receiverName: 'Elfriede' }) }); + expect(document.body.textContent).toContain('Unbekannt'); + }); + + it('shows "Unbekannt" for an empty receiver (REQ-014)', () => { + render(LetterCard, { entry: makeEntry({ senderName: 'Karl', receiverName: '' }) }); + expect(document.body.textContent).toContain('Unbekannt'); + }); + + it('links to exactly /documents/{documentId} with no target (REQ-023)', () => { + render(LetterCard, { entry: makeEntry({ documentId: DOC_ID }) }); + const link = document.querySelector('a') as HTMLAnchorElement; + expect(link.getAttribute('href')).toBe(`/documents/${DOC_ID}`); + expect(link.hasAttribute('target')).toBe(false); + }); + + it('has a touch target of at least 44px (REQ-020)', () => { + render(LetterCard, { entry: makeEntry() }); + const link = document.querySelector('a') as HTMLAnchorElement; + expect(link.getBoundingClientRect().height).toBeGreaterThanOrEqual(44); + }); +});