feat(documents): wire TimelineDensityFilter into /documents/+page (#385)
Mounts the timeline above the result count, hidden on mobile via \`hidden sm:block\` (defense-in-depth — +page.ts already gates the fetch). The component's onchange callback updates local from/to and triggers the existing search reload, so timeline selection composes with the SearchFilterBar's other filters via AND semantics for free. 3 new page-level integration tests cover: widget renders when density present, hides when null, and bar click navigates with correct from/to URL params. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import SearchFilterBar from '../SearchFilterBar.svelte';
|
|||||||
import DocumentList from '../DocumentList.svelte';
|
import DocumentList from '../DocumentList.svelte';
|
||||||
import Pagination from '$lib/shared/primitives/Pagination.svelte';
|
import Pagination from '$lib/shared/primitives/Pagination.svelte';
|
||||||
import BulkSelectionBar from '$lib/document/BulkSelectionBar.svelte';
|
import BulkSelectionBar from '$lib/document/BulkSelectionBar.svelte';
|
||||||
|
import TimelineDensityFilter from '$lib/document/TimelineDensityFilter.svelte';
|
||||||
import { bulkSelectionStore } from '$lib/document/bulkSelection.svelte';
|
import { bulkSelectionStore } from '$lib/document/bulkSelection.svelte';
|
||||||
import { getErrorMessage, parseBackendError } from '$lib/shared/errors';
|
import { getErrorMessage, parseBackendError } from '$lib/shared/errors';
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
@@ -234,6 +235,21 @@ $effect(() => {
|
|||||||
onblur={() => (qFocused = false)}
|
onblur={() => (qFocused = false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div class="mt-3 mb-4 hidden sm:block">
|
||||||
|
<TimelineDensityFilter
|
||||||
|
density={data.density}
|
||||||
|
minDate={data.minDate}
|
||||||
|
maxDate={data.maxDate}
|
||||||
|
from={from}
|
||||||
|
to={to}
|
||||||
|
onchange={(event) => {
|
||||||
|
from = event.from;
|
||||||
|
to = event.to;
|
||||||
|
triggerSearch();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 flex items-center justify-between gap-4">
|
<div class="mb-3 flex items-center justify-between gap-4">
|
||||||
<p class="font-sans text-base text-ink-2">
|
<p class="font-sans text-base text-ink-2">
|
||||||
{#if data.totalElements > 0}{m.docs_result_count({ count: data.totalElements })}{/if}
|
{#if data.totalElements > 0}{m.docs_result_count({ count: data.totalElements })}{/if}
|
||||||
|
|||||||
@@ -135,3 +135,45 @@ describe('documents page — URL building', () => {
|
|||||||
expect(url).not.toContain('page=');
|
expect(url).not.toContain('page=');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Timeline density widget wiring (#385) ────────────────────────────────────
|
||||||
|
|
||||||
|
describe('documents page — timeline density widget', () => {
|
||||||
|
it('renders the timeline widget when density data is present', async () => {
|
||||||
|
render(Page, {
|
||||||
|
data: makeData({
|
||||||
|
density: [{ month: '1915-08', count: 3 }],
|
||||||
|
minDate: '1915-08-01',
|
||||||
|
maxDate: '1915-08-31'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.element(page.getByTestId('timeline-density-filter')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the timeline widget when density is null (mobile / calendar view)', async () => {
|
||||||
|
render(Page, { data: makeData({ density: null, minDate: null, maxDate: null }) });
|
||||||
|
expect(document.querySelector('[data-testid="timeline-density-filter"]')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clicking a timeline bar navigates with from/to set to that month boundary', async () => {
|
||||||
|
const { goto } = await import('$app/navigation');
|
||||||
|
vi.mocked(goto).mockClear();
|
||||||
|
|
||||||
|
render(Page, {
|
||||||
|
data: makeData({
|
||||||
|
density: [{ month: '1915-08', count: 3 }],
|
||||||
|
minDate: '1915-08-01',
|
||||||
|
maxDate: '1915-08-31'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLButtonElement;
|
||||||
|
bar.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||||
|
|
||||||
|
expect(goto).toHaveBeenCalledOnce();
|
||||||
|
const [url] = vi.mocked(goto).mock.calls[0];
|
||||||
|
expect(url).toContain('from=1915-08-01');
|
||||||
|
expect(url).toContain('to=1915-08-31');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user