import { describe, it, expect, afterEach } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import * as m from '$lib/paraglide/messages.js'; import LetterBucket from './LetterBucket.svelte'; import { makeEntry } from './test-factories'; import type { LetterBucket as Bucket } from './timelineGrouping'; afterEach(() => cleanup()); const eventBucket: Bucket = { key: 'event:e1', kind: 'event', title: 'Briefe von der Front', color: null, letters: [makeEntry({ documentId: 'a' }), makeEntry({ documentId: 'b' })] }; const tagBucket: Bucket = { key: 'tag:t1', kind: 'tag', title: 'Krieg', color: 'sienna', letters: [makeEntry({ documentId: 'c', rootTagName: 'Krieg', rootTagColor: 'sienna' })] }; describe('LetterBucket — Ereignis mode (REQ-003/006/014)', () => { it('shows the event title and the cluster count', () => { render(LetterBucket, { bucket: eventBucket, mode: 'event' }); expect(document.body.textContent).toContain('Briefe von der Front'); expect(document.querySelector('[data-testid="bucket-count"]')?.textContent).toContain('2'); }); it('renders its letters as .lcard.ev event cards (REQ-014)', () => { render(LetterBucket, { bucket: eventBucket, mode: 'event' }); expect(document.querySelectorAll('a.lcard.ev')).toHaveLength(2); }); it('uses the localized "Weitere Briefe" label and plain cards for the fallback bucket (REQ-006)', () => { const fb: Bucket = { key: '__fallback__', kind: 'fallback', color: null, letters: [makeEntry({ documentId: 'x' })] }; render(LetterBucket, { bucket: fb, mode: 'event' }); expect(document.body.textContent).toContain(m.timeline_bucket_other_letters()); // fallback letters are not clustered under a curated event → plain card, never .lcard.ev expect(document.querySelector('a.ev')).toBeNull(); }); }); describe('LetterBucket — Thema mode (REQ-004/007/015/017)', () => { it('renders a tinted bucket-header chip carrying the root-tag name (REQ-015)', () => { render(LetterBucket, { bucket: tagBucket, mode: 'thema' }); const chip = document.querySelector('[data-testid="bucket-header-chip"]'); expect(chip?.textContent).toContain('Krieg'); }); it('suppresses the per-letter tag chip inside its own root-tag bucket (REQ-017)', () => { render(LetterBucket, { bucket: tagBucket, mode: 'thema' }); expect(document.querySelector('[data-testid="tag-chip"]')).toBeNull(); }); it('uses the localized "Ohne Thema" label for the untagged fallback bucket (REQ-007)', () => { const fb: Bucket = { key: '__fallback__', kind: 'fallback', color: null, letters: [makeEntry({ documentId: 'y', rootTagName: undefined })] }; render(LetterBucket, { bucket: fb, mode: 'thema' }); expect(document.body.textContent).toContain(m.timeline_bucket_no_topic()); }); }); const manyLetters = (n: number) => Array.from({ length: n }, (_, i) => makeEntry({ documentId: `d${i}`, eventDate: `1916-0${(i % 9) + 1}-01` }) ); describe('LetterBucket — density + containment (#827)', () => { it('collapses an oversized bucket to the density strip instead of flooding cards', () => { const bucket: Bucket = { key: 'tag:t1', kind: 'tag', title: 'Sonstiges', color: null, letters: manyLetters(10) }; render(LetterBucket, { bucket, mode: 'thema', year: 1916 }); expect(document.querySelector('[data-testid="strip-expand"]')).not.toBeNull(); // not ten individual cards dumped into the timeline expect(document.querySelectorAll('a.lcard')).toHaveLength(0); }); it('renders compact cards for a small bucket (no strip)', () => { const bucket: Bucket = { key: 'tag:t1', kind: 'tag', title: 'Tod', color: null, letters: manyLetters(2) }; render(LetterBucket, { bucket, mode: 'thema', year: 1916 }); expect(document.querySelector('[data-testid="strip-expand"]')).toBeNull(); expect(document.querySelectorAll('a.lcard.compact')).toHaveLength(2); }); it('omits the header when nested — the event pill above is the header', () => { const bucket: Bucket = { key: 'event:e1', kind: 'event', title: 'Ein gewaltiger Stadtbrand', color: null, letters: manyLetters(1) }; render(LetterBucket, { bucket, mode: 'event', nested: true, year: 1916 }); expect(document.querySelector('[data-testid="bucket-count"]')).toBeNull(); expect(document.body.textContent).not.toContain('Ein gewaltiger Stadtbrand'); // still the event-letter variant, just headerless under its pill expect(document.querySelector('a.lcard.ev')).not.toBeNull(); }); it('binds a tag bucket together with a coloured left rail from its token', () => { const bucket: Bucket = { key: 'tag:t1', kind: 'tag', title: 'Krieg', color: 'sienna', letters: manyLetters(1) }; render(LetterBucket, { bucket, mode: 'thema', year: 1916 }); const section = document.querySelector('[data-testid="letter-bucket"]') as HTMLElement; expect(section.getAttribute('style') ?? '').toContain('var(--c-tag-sienna)'); }); });