Files
familienarchiv/frontend/src/routes/geschichten/page.svelte.spec.ts
Marcel 9b6d8fbef1 fix(geschichten): bump filter pills to 44px touch target
Senior-author persona requires 44px minimum touch targets on every
interactive control. The /geschichten filter row had three pills
(All / chip / + Person wählen) at h-9 (36px), missing the rule that
the toolbar already follows. Bumped all three to h-11.

Test added in page.svelte.spec.ts asserts the className contains
h-11 on every pill variant.

Addresses Leonie's iteration-3 concern #6 on PR #382.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 09:03:55 +02:00

124 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
});
it('renders all filter pills with a 44px touch target (h-11)', async () => {
render(Page, {
data: makeData({
personFilters: [person('a', 'Anna A')] as PageData['personFilters']
})
});
// All three pill variants must use h-11 (44px) per the senior-author touch-target rule
const all = page.getByRole('button', { name: 'Alle' });
const chip = page.getByRole('button', { name: /Anna A aus Filter entfernen/ });
const add = page.getByRole('button', { name: /Person wählen/ });
const allEl = (await all.element()) as HTMLElement;
const chipEl = (await chip.element()) as HTMLElement;
const addEl = (await add.element()) as HTMLElement;
expect(allEl.className).toContain('h-11');
expect(chipEl.className).toContain('h-11');
expect(addEl.className).toContain('h-11');
});
});