feat(geschichten): chip-row UI for multi-person AND filter
The /geschichten list page now renders one removable chip per active person filter and lets users add more via the existing typeahead. The URL uses repeated ?personId= params (matching the documents tag filter), which the regenerated API client passes straight through to the backend's new array-bound endpoint. New translation keys cover the chip remove aria-label, the AND hint shown while picking, and the multi-person empty state.
This commit is contained in:
102
frontend/src/routes/geschichten/page.svelte.spec.ts
Normal file
102
frontend/src/routes/geschichten/page.svelte.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
|
||||
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||||
vi.mock('$app/state', () => ({ navigating: { to: null } }));
|
||||
|
||||
import Page from './+page.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function person(id: string, displayName: string) {
|
||||
return {
|
||||
id,
|
||||
firstName: displayName.split(' ')[0] ?? displayName,
|
||||
lastName: displayName.split(' ').slice(1).join(' ') || 'X',
|
||||
displayName,
|
||||
personType: 'PERSON'
|
||||
};
|
||||
}
|
||||
|
||||
function makeData(overrides: Partial<PageData> = {}): PageData {
|
||||
return {
|
||||
geschichten: [],
|
||||
personFilters: [],
|
||||
documentFilter: null,
|
||||
canBlogWrite: false,
|
||||
...overrides
|
||||
} as unknown as PageData;
|
||||
}
|
||||
|
||||
describe('geschichten page — multi-person filter chips', () => {
|
||||
it('renders one chip per person in personFilters', async () => {
|
||||
render(Page, {
|
||||
data: makeData({
|
||||
personFilters: [person('a', 'Anna A'), person('b', 'Bertha B')] as PageData['personFilters']
|
||||
})
|
||||
});
|
||||
|
||||
await expect
|
||||
.element(page.getByRole('button', { name: /Anna A aus Filter entfernen/ }))
|
||||
.toBeVisible();
|
||||
await expect
|
||||
.element(page.getByRole('button', { name: /Bertha B aus Filter entfernen/ }))
|
||||
.toBeVisible();
|
||||
});
|
||||
|
||||
it('renders the "All" pill in pressed state when no filters are active', async () => {
|
||||
render(Page, { data: makeData() });
|
||||
await expect
|
||||
.element(page.getByRole('button', { name: 'Alle' }))
|
||||
.toHaveAttribute('aria-pressed', 'true');
|
||||
});
|
||||
|
||||
it('renders the "All" pill in unpressed state when at least one filter is active', async () => {
|
||||
render(Page, {
|
||||
data: makeData({
|
||||
personFilters: [person('a', 'Anna A')] as PageData['personFilters']
|
||||
})
|
||||
});
|
||||
await expect
|
||||
.element(page.getByRole('button', { name: 'Alle' }))
|
||||
.toHaveAttribute('aria-pressed', 'false');
|
||||
});
|
||||
|
||||
it('clicking × on a chip removes only that person from the URL', async () => {
|
||||
const { goto } = await import('$app/navigation');
|
||||
vi.mocked(goto).mockClear();
|
||||
|
||||
// Seed window.location so the chip-removal logic builds the new URL deterministically.
|
||||
const originalHref = window.location.href;
|
||||
window.history.replaceState({}, '', '/geschichten?personId=a&personId=b');
|
||||
|
||||
render(Page, {
|
||||
data: makeData({
|
||||
personFilters: [person('a', 'Anna A'), person('b', 'Bertha B')] as PageData['personFilters']
|
||||
})
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /Anna A aus Filter entfernen/ }).click();
|
||||
|
||||
expect(goto).toHaveBeenCalledOnce();
|
||||
const url = vi.mocked(goto).mock.calls[0][0] as string;
|
||||
expect(url).toContain('personId=b');
|
||||
expect(url).not.toContain('personId=a');
|
||||
|
||||
window.history.replaceState({}, '', originalHref);
|
||||
});
|
||||
|
||||
it('shows the "+ Person wählen" button even when filters are already active', async () => {
|
||||
render(Page, {
|
||||
data: makeData({
|
||||
personFilters: [person('a', 'Anna A')] as PageData['personFilters']
|
||||
})
|
||||
});
|
||||
await expect.element(page.getByRole('button', { name: /Person wählen/ })).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user