feat(timeline): add YearBand (section + sticky h2, cards vs strip)
One <section> per year with a sticky <h2> at top:4rem (REQ-006). Events render in
DTO order as pills/bands; letters render as individual cards while <= 12 (REQ-011)
or collapse to one density strip above that (REQ-012); DTO order is never re-sorted
(REQ-003). Letters carry an alternating data-side for the centered desktop axis
(REQ-004); single left column on phone (REQ-005). Derived-safe {#each} key.
Refs #779
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
84
frontend/src/lib/timeline/YearBand.svelte.spec.ts
Normal file
84
frontend/src/lib/timeline/YearBand.svelte.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user