diff --git a/frontend/src/lib/timeline/YearBand.svelte b/frontend/src/lib/timeline/YearBand.svelte new file mode 100644 index 00000000..005e6e7e --- /dev/null +++ b/frontend/src/lib/timeline/YearBand.svelte @@ -0,0 +1,108 @@ + + +
+

+ {year.year} +

+ +
+ {#each rows as row (row.t === 'strip' ? `strip-${year.year}` : entryKey(row.entry))} + {#if row.t === 'event'} + {#if row.entry.type === 'HISTORICAL'} + + {:else} + + {/if} + {:else if row.t === 'letter'} +
+ +
+ {:else} + + {/if} + {/each} +
+
+ + diff --git a/frontend/src/lib/timeline/YearBand.svelte.spec.ts b/frontend/src/lib/timeline/YearBand.svelte.spec.ts new file mode 100644 index 00000000..5d43a6e5 --- /dev/null +++ b/frontend/src/lib/timeline/YearBand.svelte.spec.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import YearBand from './YearBand.svelte'; +import { makeEntry, makeYear } from './test-factories'; + +afterEach(() => cleanup()); + +function manyLetters(year: number, count: number) { + return Array.from({ length: count }, (_, i) => + makeEntry({ eventDate: `${year}-01-10`, documentId: `doc-${i}` }) + ); +} + +describe('YearBand', () => { + it('renders a section with a sticky h2 at top:4rem showing the year (REQ-006)', () => { + render(YearBand, { year: makeYear(1914, [makeEntry()]) }); + const section = document.querySelector('section'); + expect(section).not.toBeNull(); + const h2 = section?.querySelector('h2'); + expect(h2?.textContent).toContain('1914'); + const cs = getComputedStyle(h2 as HTMLElement); + expect(cs.position).toBe('sticky'); + expect(cs.top).toBe('64px'); + }); + + it('renders each letter as a card when the band holds <= 12 letters (REQ-011)', () => { + render(YearBand, { year: makeYear(1909, manyLetters(1909, 3)) }); + expect(document.querySelectorAll('a')).toHaveLength(3); + expect(document.querySelector('[data-testid="strip-expand"]')).toBeNull(); + }); + + it('renders a single strip when the band holds > 12 letters (REQ-012)', () => { + render(YearBand, { year: makeYear(1915, manyLetters(1915, 30)) }); + expect(document.querySelector('[data-testid="strip-expand"]')).not.toBeNull(); + // collapsed: no individual letter links yet + expect(document.querySelectorAll('a')).toHaveLength(0); + }); + + it('renders entries in DTO order — DAY-precision letter above a YEAR-precision letter (REQ-003)', () => { + const dayLetter = makeEntry({ + precision: 'DAY', + eventDate: '1923-04-12', + title: 'Tagesgenau', + documentId: 'day' + }); + const yearLetter = makeEntry({ + precision: 'YEAR', + eventDate: '1923-01-01', + title: 'Nur Jahr', + documentId: 'year' + }); + render(YearBand, { year: makeYear(1923, [dayLetter, yearLetter]) }); + const links = Array.from(document.querySelectorAll('a')); + expect(links[0].getAttribute('href')).toBe('/documents/day'); + expect(links[1].getAttribute('href')).toBe('/documents/year'); + }); + + it('renders an EVENT as a pill and a HISTORICAL event as a band', () => { + const pill = makeEntry({ + kind: 'EVENT', + derived: true, + derivedType: 'MARRIAGE', + title: 'Heirat', + senderName: '', + receiverName: '', + documentId: undefined + }); + const band = makeEntry({ + kind: 'EVENT', + derived: false, + type: 'HISTORICAL', + precision: 'RANGE', + eventDate: '1914-01-01', + eventDateEnd: '1918-12-31', + title: 'Erster Weltkrieg', + senderName: '', + receiverName: '', + documentId: undefined + }); + render(YearBand, { year: makeYear(1914, [pill, band]) }); + expect(document.body.textContent).toContain('Heirat'); + expect(document.querySelector('[data-testid="world-range"]')).not.toBeNull(); + }); +});