feat(timeline): add the LetterBucket cluster component
Renders one loose-letter cluster for Ereignis/Thema mode (#827): an "✉ <event> · <n>" header over .lcard.ev cards in Ereignis, a tinted BucketHeaderChip over chip-suppressed cards in Thema, and a localized "Weitere Briefe"/"Ohne Thema" header with plain cards for the fallback bucket. Refs #827 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
74
frontend/src/lib/timeline/LetterBucket.svelte.spec.ts
Normal file
74
frontend/src/lib/timeline/LetterBucket.svelte.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
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());
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user