feat(journey-editor): build DocumentPickerDropdown + refactor DocumentMultiSelect
New DocumentPickerDropdown: single-select document search with aria-disabled for already-added items and sr-only "bereits enthalten" hint. DocumentMultiSelect refactored to use createTypeahead, removing raw setTimeout/debounceTimer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page, userEvent } from 'vitest/browser';
|
||||
import DocumentPickerDropdown from './DocumentPickerDropdown.svelte';
|
||||
|
||||
const waitForDebounce = () => new Promise((r) => setTimeout(r, 350));
|
||||
|
||||
const docFactory = (id: string, title: string) => ({
|
||||
id,
|
||||
title,
|
||||
documentDate: '1880-01-01',
|
||||
metaDatePrecision: 'DAY' as const,
|
||||
metaDateEnd: undefined
|
||||
});
|
||||
|
||||
function mockSearchResponse(items: ReturnType<typeof docFactory>[]) {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({ items })
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('DocumentPickerDropdown — empty query guard', () => {
|
||||
it('does not call fetch on empty query', async () => {
|
||||
const fetchMock = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
render(DocumentPickerDropdown, { onSelect: vi.fn() });
|
||||
await userEvent.fill(page.getByRole('combobox'), '');
|
||||
await waitForDebounce();
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DocumentPickerDropdown — already-added indicator', () => {
|
||||
it('shows already-added document as aria-disabled with sr-only hint', async () => {
|
||||
mockSearchResponse([docFactory('d1', 'Brief von Eugenie'), docFactory('d2', 'Brief 2')]);
|
||||
|
||||
render(DocumentPickerDropdown, {
|
||||
alreadyAddedIds: new Set(['d1']),
|
||||
onSelect: vi.fn()
|
||||
});
|
||||
|
||||
await userEvent.fill(page.getByRole('combobox'), 'Brief');
|
||||
await waitForDebounce();
|
||||
|
||||
const disabledOption = page.getByRole('option', { name: /Brief von Eugenie/i });
|
||||
await expect.element(disabledOption).toHaveAttribute('aria-disabled', 'true');
|
||||
// Screen-reader text "bereits enthalten" must be present in the option
|
||||
await expect.element(page.getByText(/bereits enthalten/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DocumentPickerDropdown — selection', () => {
|
||||
it('calls onSelect with the item when a non-disabled option is clicked', async () => {
|
||||
const onSelect = vi.fn();
|
||||
mockSearchResponse([docFactory('d1', 'Brief von Eugenie')]);
|
||||
|
||||
render(DocumentPickerDropdown, { onSelect });
|
||||
|
||||
await userEvent.fill(page.getByRole('combobox'), 'Brief');
|
||||
await waitForDebounce();
|
||||
await userEvent.click(page.getByRole('option', { name: /Brief von Eugenie/i }));
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 'd1' }));
|
||||
});
|
||||
|
||||
it('does not call onSelect when an aria-disabled option is clicked', async () => {
|
||||
const onSelect = vi.fn();
|
||||
mockSearchResponse([docFactory('d1', 'Brief von Eugenie')]);
|
||||
|
||||
render(DocumentPickerDropdown, {
|
||||
alreadyAddedIds: new Set(['d1']),
|
||||
onSelect
|
||||
});
|
||||
|
||||
await userEvent.fill(page.getByRole('combobox'), 'Brief');
|
||||
await waitForDebounce();
|
||||
await userEvent.click(page.getByRole('option', { name: /Brief von Eugenie/i }));
|
||||
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user