import { describe, it, expect, afterEach } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import TimelineView from './TimelineView.svelte'; import { makeEntry, makeYear, makeTimelineDTO } from './test-factories'; import type { GroupingMode } from './timelineGrouping'; afterEach(() => cleanup()); const worldBand = (title: string) => makeEntry({ kind: 'EVENT', type: 'HISTORICAL', derived: false, precision: 'RANGE', eventDate: '1914-01-01', eventDateEnd: '1918-12-31', eventId: 'h1', title, senderName: '', receiverName: '', documentId: undefined }); const eventPill = (title: string) => makeEntry({ kind: 'EVENT', type: 'PERSONAL', derived: false, eventId: 'p1', title, senderName: '', receiverName: '', documentId: undefined }); // A signature of the axis-fixed event layer: the curated/world-band titles, the world-range // marker count, and the event-pill count — everything REQ-001 requires to stay constant when // only the loose letters re-bundle. (No pixel-diff harness in the repo; this is the structural // equivalent — the event-layer DOM is byte-for-byte built from the same entries in every mode.) function eventLayerSignature(): string { const body = document.body.textContent ?? ''; return JSON.stringify({ weltkrieg: body.includes('Erster Weltkrieg'), hochzeit: body.includes('Hochzeit'), worldRange: document.querySelectorAll('[data-testid="world-range"]').length }); } // Brief A links to the curated event p1 (Hochzeit), not the world band — so the world band // stays letterless and renders as a plain band in every mode (REQ-001). Under the #827 redesign // a curated event WITH letters becomes its cluster card's header, so the signature tracks the // stable layer: the letterless world band's marker count and the two titles, which all survive // regardless of whether Hochzeit renders as a pill (Datum) or a card header (grouped). const mixed = () => makeTimelineDTO({ years: [ makeYear(1915, [ worldBand('Erster Weltkrieg'), eventPill('Hochzeit'), makeEntry({ documentId: 'a', title: 'Brief A', linkedEventId: 'p1' }), makeEntry({ documentId: 'b', title: 'Brief B', rootTagId: 't1', rootTagName: 'Krieg', rootTagColor: 'sienna' }) ]) ] }); function signatureFor(mode: GroupingMode): string { render(TimelineView, { timeline: mixed(), groupingMode: mode }); const sig = eventLayerSignature(); cleanup(); return sig; } describe('TimelineView event layer (REQ-001)', () => { it('renders the event pills and world-bands identically across all three grouping modes', () => { const dateSig = signatureFor('date'); const eventSig = signatureFor('event'); const themaSig = signatureFor('thema'); expect(eventSig).toBe(dateSig); expect(themaSig).toBe(dateSig); // sanity: the world-band actually rendered, so the assertion is not vacuously equal on "" expect(dateSig).toContain('"worldRange":1'); }); it('regroups only the loose letters — buckets appear off Datum, not in it', () => { render(TimelineView, { timeline: mixed(), groupingMode: 'date' }); expect(document.querySelector('[data-testid="letter-bucket"]')).toBeNull(); cleanup(); render(TimelineView, { timeline: mixed(), groupingMode: 'event' }); expect(document.querySelector('[data-testid="letter-bucket"]')).not.toBeNull(); }); });