feat(timeline): wire the layer filter into /zeitstrahl
The route holds the three layer toggles in $state, binds them into TimelineFilters, and derives a client-side filtered view of the SSR-loaded timeline that it passes to TimelineView — no goto, no URL param, no extra fetch. When the active toggles leave nothing visible it renders a calm filtered-empty message plus a one-click reset below the still-open filter bar, never a blank page and never the generic "no events" state. The meta-line keeps counting the unfiltered timeline (D1 known limitation). Refs #780 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import Page from './+page.svelte';
|
||||
import { makeEntry, makeYear, makeTimelineDTO } from '$lib/timeline/test-factories';
|
||||
@@ -111,3 +112,88 @@ describe('/zeitstrahl page', () => {
|
||||
expect(sub?.textContent).not.toContain(m.timeline_events_count({ count: 1 }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('/zeitstrahl layer filter (#780)', () => {
|
||||
const letter = (title: string, documentId: string) => makeEntry({ documentId, title });
|
||||
const historical = (title: string) =>
|
||||
makeEntry({
|
||||
kind: 'EVENT',
|
||||
type: 'HISTORICAL',
|
||||
derived: false,
|
||||
eventId: 'h1',
|
||||
documentId: undefined,
|
||||
title,
|
||||
senderName: '',
|
||||
receiverName: ''
|
||||
});
|
||||
const personal = (title: string) =>
|
||||
makeEntry({
|
||||
kind: 'EVENT',
|
||||
type: 'PERSONAL',
|
||||
derived: false,
|
||||
eventId: 'p1',
|
||||
documentId: undefined,
|
||||
title,
|
||||
senderName: '',
|
||||
receiverName: ''
|
||||
});
|
||||
|
||||
const mixed = () =>
|
||||
makeTimelineDTO({
|
||||
years: [
|
||||
makeYear(1915, [
|
||||
letter('Brief Eins', 'd1'),
|
||||
historical('Erster Weltkrieg'),
|
||||
personal('Umzug nach Berlin')
|
||||
])
|
||||
]
|
||||
});
|
||||
|
||||
async function openBar() {
|
||||
await page.getByTestId('timeline-filter-trigger').click();
|
||||
}
|
||||
|
||||
it('hides letter cards when the Letters layer is off and restores them, with no fetch (REQ-005/002)', async () => {
|
||||
render(Page, { data: pageData(mixed()) });
|
||||
await expect.element(page.getByText('Brief Eins')).toBeVisible();
|
||||
await openBar();
|
||||
await page.getByTestId('timeline-filter-letters').click();
|
||||
await expect.poll(() => page.getByText('Brief Eins').query()).toBeNull();
|
||||
await expect.element(page.getByText('Umzug nach Berlin')).toBeVisible();
|
||||
await page.getByTestId('timeline-filter-letters').click();
|
||||
await expect.element(page.getByText('Brief Eins')).toBeVisible();
|
||||
});
|
||||
|
||||
it('hides historical event cards when the Historical layer is off (REQ-004)', async () => {
|
||||
render(Page, { data: pageData(mixed()) });
|
||||
await openBar();
|
||||
await page.getByTestId('timeline-filter-historical').click();
|
||||
await expect.poll(() => page.getByText('Erster Weltkrieg').query()).toBeNull();
|
||||
await expect.element(page.getByText('Brief Eins')).toBeVisible();
|
||||
await expect.element(page.getByText('Umzug nach Berlin')).toBeVisible();
|
||||
});
|
||||
|
||||
it('hides personal event cards when the Personal layer is off (REQ-003)', async () => {
|
||||
render(Page, { data: pageData(mixed()) });
|
||||
await openBar();
|
||||
await page.getByTestId('timeline-filter-personal').click();
|
||||
await expect.poll(() => page.getByText('Umzug nach Berlin').query()).toBeNull();
|
||||
await expect.element(page.getByText('Brief Eins')).toBeVisible();
|
||||
await expect.element(page.getByText('Erster Weltkrieg')).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows the filtered-empty message + reset below the open bar when all layers are off (REQ-006)', async () => {
|
||||
render(Page, { data: pageData(mixed()) });
|
||||
await openBar();
|
||||
await page.getByTestId('timeline-filter-personal').click();
|
||||
await page.getByTestId('timeline-filter-historical').click();
|
||||
await page.getByTestId('timeline-filter-letters').click();
|
||||
await expect.element(page.getByText(m.timeline_filter_empty_state())).toBeVisible();
|
||||
await expect.element(page.getByTestId('timeline-filter-empty-reset')).toBeVisible();
|
||||
// the generic TimelineView empty state is never what shows for a filtered-empty view
|
||||
expect(page.getByText(m.timeline_empty_state()).query()).toBeNull();
|
||||
// the one-click reset restores every layer
|
||||
await page.getByTestId('timeline-filter-empty-reset').click();
|
||||
await expect.element(page.getByText('Brief Eins')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user