feat(timeline): cap grouped clusters at 5 letters with a show-more toggle

Replaces the in-bucket month-density sparkline with a first-5 preview + show-more
/ show-less toggle, the agreed grouped-view pattern. Datum mode keeps the >12
YearLetterStrip.

Refs #827
This commit is contained in:
Marcel
2026-06-15 14:48:10 +02:00
parent 4162cfa916
commit c1dc58c24f
6 changed files with 74 additions and 59 deletions

View File

@@ -1,5 +1,6 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { tick } from 'svelte';
import * as m from '$lib/paraglide/messages.js';
import LetterBucket from './LetterBucket.svelte';
import { makeEntry } from './test-factories';
@@ -78,47 +79,49 @@ const manyLetters = (n: number) =>
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', () => {
describe('LetterBucket — preview cap + show-more (#827 redesign)', () => {
it('shows only the first 5 letters with a show-more toggle when the cluster is larger', () => {
const bucket: Bucket = {
key: 'tag:t1',
kind: 'tag',
title: 'Sonstiges',
color: null,
letters: manyLetters(10)
title: 'Krieg',
color: 'sienna',
letters: manyLetters(8)
};
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);
expect(document.querySelectorAll('a.lcard')).toHaveLength(5);
expect(document.querySelector('[data-testid="bucket-show-more"]')).not.toBeNull();
expect(document.querySelector('[data-testid="strip-expand"]')).toBeNull(); // sparkline gone
});
it('renders compact cards for a small bucket (no strip)', () => {
it('expands to all letters and collapses back on toggle', async () => {
const bucket: Bucket = {
key: 'tag:t1',
kind: 'tag',
title: 'Krieg',
color: 'sienna',
letters: manyLetters(8)
};
render(LetterBucket, { bucket, mode: 'thema', year: 1916 });
(document.querySelector('[data-testid="bucket-show-more"]') as HTMLButtonElement).click();
await tick();
expect(document.querySelectorAll('a.lcard')).toHaveLength(8);
(document.querySelector('[data-testid="bucket-show-more"]') as HTMLButtonElement).click();
await tick();
expect(document.querySelectorAll('a.lcard')).toHaveLength(5);
});
it('shows all letters and no toggle for a small cluster (<= 5)', () => {
const bucket: Bucket = {
key: 'tag:t1',
kind: 'tag',
title: 'Tod',
color: null,
letters: manyLetters(2)
letters: manyLetters(3)
};
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();
expect(document.querySelectorAll('a.lcard')).toHaveLength(3);
expect(document.querySelector('[data-testid="bucket-show-more"]')).toBeNull();
});
it('binds a tag bucket together with a coloured left rail from its token', () => {