diff --git a/frontend/src/routes/search/DisambiguationPicker.svelte b/frontend/src/routes/search/DisambiguationPicker.svelte new file mode 100644 index 00000000..b99fea10 --- /dev/null +++ b/frontend/src/routes/search/DisambiguationPicker.svelte @@ -0,0 +1,86 @@ + + + + +
open && closePicker()}> + + + {#if open} + + {/if} +
diff --git a/frontend/src/routes/search/DisambiguationPicker.svelte.spec.ts b/frontend/src/routes/search/DisambiguationPicker.svelte.spec.ts new file mode 100644 index 00000000..5b87a996 --- /dev/null +++ b/frontend/src/routes/search/DisambiguationPicker.svelte.spec.ts @@ -0,0 +1,71 @@ +import { describe, expect, it, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DisambiguationPicker from './DisambiguationPicker.svelte'; +import type { components } from '$lib/generated/api'; + +type PersonHint = components['schemas']['PersonHint']; + +afterEach(() => cleanup()); + +const persons: PersonHint[] = [ + { id: 'w1', displayName: 'Walter Raddatz' }, + { id: 'w2', displayName: 'Walter Müller' } +]; + +function pressEscape() { + (document.activeElement as HTMLElement).dispatchEvent( + new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }) + ); +} + +describe('DisambiguationPicker', () => { + it('opens the picker and shows a select option per ambiguous person', async () => { + render(DisambiguationPicker, { persons, onSelect: vi.fn() }); + await page.getByRole('button', { name: /Mehrere Personen gefunden/ }).click(); + await expect + .element(page.getByRole('button', { name: 'Walter Raddatz auswählen' })) + .toBeInTheDocument(); + await expect + .element(page.getByRole('button', { name: 'Walter Müller auswählen' })) + .toBeInTheDocument(); + }); + + it('moves focus into the picker list on open', async () => { + render(DisambiguationPicker, { persons, onSelect: vi.fn() }); + await page.getByRole('button', { name: /Mehrere Personen gefunden/ }).click(); + await expect + .element(page.getByRole('button', { name: 'Walter Raddatz auswählen' })) + .toHaveFocus(); + }); + + it('returns focus to the trigger when closed with Escape', async () => { + render(DisambiguationPicker, { persons, onSelect: vi.fn() }); + const trigger = page.getByRole('button', { name: /Mehrere Personen gefunden/ }); + await trigger.click(); + await expect + .element(page.getByRole('button', { name: 'Walter Raddatz auswählen' })) + .toHaveFocus(); + pressEscape(); + await expect.element(trigger).toHaveFocus(); + }); + + it('does not call onSelect when dismissed without choosing', async () => { + const onSelect = vi.fn(); + render(DisambiguationPicker, { persons, onSelect }); + await page.getByRole('button', { name: /Mehrere Personen gefunden/ }).click(); + await expect + .element(page.getByRole('button', { name: 'Walter Raddatz auswählen' })) + .toHaveFocus(); + pressEscape(); + expect(onSelect).not.toHaveBeenCalled(); + }); + + it('calls onSelect with the chosen person', async () => { + const onSelect = vi.fn(); + render(DisambiguationPicker, { persons, onSelect }); + await page.getByRole('button', { name: /Mehrere Personen gefunden/ }).click(); + await page.getByRole('button', { name: 'Walter Müller auswählen' }).click(); + expect(onSelect).toHaveBeenCalledWith(persons[1]); + }); +});