feat(timeline): render letter buckets in TimelineView/YearBand

Thread groupingMode through TimelineView → YearBand. TimelineView resolves the
event lookup once over the filtered view (so Ereignis clusters never reference a
filtered-out event). In non-Datum modes YearBand keeps its event pills/world-bands
identical (REQ-001) and replaces the loose letters with per-year LetterBuckets
(REQ-002/003/004); Datum keeps the original card/strip path. The undated bucket is
unchanged in every mode.

Refs #827
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-15 10:52:48 +02:00
parent bc22b2d4c9
commit 8be4b40e54
3 changed files with 128 additions and 6 deletions

View File

@@ -165,3 +165,60 @@ describe('YearBand', () => {
}
});
});
describe('YearBand — grouping modes (#827)', () => {
it('keeps individual letter cards and no buckets in Datum mode (default)', () => {
render(YearBand, { year: makeYear(1915, manyLetters(1915, 3)) });
expect(document.querySelector('[data-testid="letter-bucket"]')).toBeNull();
expect(document.querySelectorAll('a')).toHaveLength(3);
});
it('clusters loose letters under their linked event in Ereignis mode (REQ-002/003)', () => {
const a = makeEntry({ documentId: 'a', linkedEventId: 'e1', eventDate: '1915-03-01' });
const b = makeEntry({ documentId: 'b', linkedEventId: 'e1', eventDate: '1915-04-01' });
render(YearBand, {
year: makeYear(1915, [a, b]),
groupingMode: 'event',
eventLookup: new Map([['e1', 'Briefe von der Front']])
});
expect(document.querySelectorAll('[data-testid="letter-bucket"]')).toHaveLength(1);
expect(document.body.textContent).toContain('Briefe von der Front');
// no alternating individual letter rows in grouped mode
expect(document.querySelector('.letter-row')).toBeNull();
});
it('still renders the event world-band in Ereignis mode (REQ-001)', () => {
const band = makeEntry({
kind: 'EVENT',
type: 'HISTORICAL',
precision: 'RANGE',
eventDate: '1914-01-01',
eventDateEnd: '1918-12-31',
title: 'Erster Weltkrieg',
senderName: '',
receiverName: '',
documentId: undefined
});
const letter = makeEntry({ documentId: 'a', linkedEventId: 'e1', eventDate: '1914-05-01' });
render(YearBand, {
year: makeYear(1914, [band, letter]),
groupingMode: 'event',
eventLookup: new Map([['e1', 'Front']])
});
expect(document.querySelector('[data-testid="world-range"]')).not.toBeNull();
expect(document.querySelector('[data-testid="letter-bucket"]')).not.toBeNull();
});
it('buckets loose letters under their root tag in Thema mode (REQ-004)', () => {
const a = makeEntry({
documentId: 'a',
rootTagId: 't1',
rootTagName: 'Krieg',
rootTagColor: 'sienna',
eventDate: '1915-03-01'
});
render(YearBand, { year: makeYear(1915, [a]), groupingMode: 'thema', eventLookup: new Map() });
const chip = document.querySelector('[data-testid="bucket-header-chip"]');
expect(chip?.textContent).toContain('Krieg');
});
});