diff --git a/frontend/.gitignore b/frontend/.gitignore index 8617ce82..39081d9a 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -13,6 +13,9 @@ node_modules .DS_Store Thumbs.db +# Leftover directory from branch work +/src.main/ + # Env .env .env.* diff --git a/frontend/src/routes/geschichten/DocumentFilterChip.svelte b/frontend/src/routes/geschichten/DocumentFilterChip.svelte new file mode 100644 index 00000000..4a0583b3 --- /dev/null +++ b/frontend/src/routes/geschichten/DocumentFilterChip.svelte @@ -0,0 +1,35 @@ + + +
+ + {m.geschichten_filter_document_chip()} + + + {chipLabel} + + +
diff --git a/frontend/src/routes/geschichten/DocumentFilterChip.svelte.spec.ts b/frontend/src/routes/geschichten/DocumentFilterChip.svelte.spec.ts new file mode 100644 index 00000000..9b5b6576 --- /dev/null +++ b/frontend/src/routes/geschichten/DocumentFilterChip.svelte.spec.ts @@ -0,0 +1,87 @@ +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() })); + +import DocumentFilterChip from './DocumentFilterChip.svelte'; + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +const VALID_UUID = '11111111-2222-3333-4444-555555555555'; + +describe('DocumentFilterChip', () => { + it('renders the resolved document title inside the chip', async () => { + render(DocumentFilterChip, { + props: { + id: VALID_UUID, + title: 'Brief an Oma', + onremove: vi.fn() + } + }); + + await expect.element(page.getByText(/Brief an Oma/)).toBeVisible(); + }); + + it('renders the prefix label', async () => { + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: 'Brief an Oma', onremove: vi.fn() } + }); + + await expect.element(page.getByText(/Gefiltert nach Brief/)).toBeVisible(); + }); + + it('falls back to short UUID when title is null', async () => { + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: null, onremove: vi.fn() } + }); + + await expect.element(page.getByText(/11111111/)).toBeVisible(); + }); + + it('fires onremove when the remove button is clicked', async () => { + const onremove = vi.fn(); + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: 'Brief an Oma', onremove } + }); + + const btn = (await page + .getByRole('button', { name: /Brief an Oma aus Filter entfernen/ }) + .element()) as HTMLElement; + btn.click(); + + await vi.waitFor(() => expect(onremove).toHaveBeenCalledOnce()); + }); + + it('remove button aria-label references the resolved title', async () => { + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: 'Brief an Oma', onremove: vi.fn() } + }); + + const btn = page.getByRole('button', { name: /Brief an Oma aus Filter entfernen/ }); + await expect.element(btn).toBeVisible(); + }); + + it('title= attribute equals the validated id, not a raw query string', async () => { + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: 'Brief an Oma', onremove: vi.fn() } + }); + + const chip = document.querySelector('[title]'); + expect(chip?.getAttribute('title')).toBe('Brief an Oma'); + }); + + it('remove button has a minimum 44px touch target', async () => { + render(DocumentFilterChip, { + props: { id: VALID_UUID, title: 'Brief an Oma', onremove: vi.fn() } + }); + + const btn = (await page + .getByRole('button', { name: /Brief an Oma aus Filter entfernen/ }) + .element()) as HTMLElement; + expect(btn.className).toMatch(/min-h-\[44px\]|min-h-11/); + }); +});