test(tag): expand TagParentPicker keyboard + excludeIds coverage

ArrowUp wrap-around, Escape close, Enter without selection no-op,
keydown without dropdown no-throw, Enter with active selection
selects, excludeIds filter works, parentId fallback as subtitle.

7 new tests covering ~12 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-10 08:21:52 +02:00
committed by marcel
parent aa0c91cf76
commit 2bb290ebe8

View File

@@ -199,4 +199,125 @@ describe('TagParentPicker parent name subtitle', () => {
// Only the tag name should appear (no subtitle)
await expect.element(page.getByRole('option', { name: 'Haus' })).toBeInTheDocument();
});
it('shows the parentId as subtitle when allTags omits the parent', async () => {
mockFetchWithTags([{ id: 't2', name: 'Keller', parentId: 'unknown-parent-id' }]);
render(TagParentPicker, { name: 'parentId', allTags: [] });
const input = page.getByRole('combobox');
await input.fill('K');
await vi.advanceTimersByTimeAsync(300);
// When parent not found in allTags, fallback shows the parentId itself
await expect.element(page.getByText('unknown-parent-id')).toBeInTheDocument();
});
});
describe('TagParentPicker keyboard navigation', () => {
it('ArrowUp wraps around to last option', async () => {
mockFetchWithTags([
{ id: 't1', name: 'Haus' },
{ id: 't2', name: 'Garten' }
]);
render(TagParentPicker, { name: 'parentId' });
const input = page.getByRole('combobox');
await input.fill('a');
await vi.advanceTimersByTimeAsync(300);
const el = await input.element();
// Without prior arrow-down, ArrowUp from -1 wraps via modular arithmetic
el.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true, cancelable: true })
);
await vi.advanceTimersByTimeAsync(0);
expect(el.getAttribute('aria-activedescendant')).toBeTruthy();
});
it('Escape closes the dropdown', async () => {
mockFetchWithTags([{ id: 't1', name: 'Haus' }]);
render(TagParentPicker, { name: 'parentId' });
const input = page.getByRole('combobox');
await input.fill('H');
await vi.advanceTimersByTimeAsync(300);
const el = await input.element();
el.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })
);
await vi.advanceTimersByTimeAsync(0);
// Listbox should be gone
expect(document.querySelector('[role="listbox"]')).toBeNull();
});
it('Enter without active selection does nothing', async () => {
mockFetchWithTags([{ id: 't1', name: 'Haus' }]);
render(TagParentPicker, { name: 'parentId' });
const input = page.getByRole('combobox');
await input.fill('H');
await vi.advanceTimersByTimeAsync(300);
const el = await input.element();
el.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true })
);
await vi.advanceTimersByTimeAsync(0);
// Hidden input should still be empty
expect(hiddenInput('parentId')?.value).toBe('');
});
it('keydown with no active dropdown is a no-op', async () => {
render(TagParentPicker, { name: 'parentId' });
const input = page.getByRole('combobox');
const el = await input.element();
expect(() =>
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
).not.toThrow();
});
it('Enter with active selection selects the highlighted tag', async () => {
mockFetchWithTags([
{ id: 't1', name: 'Haus' },
{ id: 't2', name: 'Garten' }
]);
render(TagParentPicker, { name: 'parentId' });
const input = page.getByRole('combobox');
await input.fill('a');
await vi.advanceTimersByTimeAsync(300);
const el = await input.element();
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
await vi.advanceTimersByTimeAsync(0);
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
await vi.advanceTimersByTimeAsync(0);
// Some tag selected
expect(hiddenInput('parentId')?.value).toBeTruthy();
});
});
describe('TagParentPicker excludeIds filter', () => {
it('filters out tags whose id is in excludeIds', async () => {
mockFetchWithTags([
{ id: 't1', name: 'Haus' },
{ id: 't2', name: 'Keller' }
]);
render(TagParentPicker, { name: 'parentId', excludeIds: ['t1'] });
const input = page.getByRole('combobox');
await input.fill('a');
await vi.advanceTimersByTimeAsync(300);
// Only Keller should be visible
const options = document.querySelectorAll('[role="option"]');
expect(options.length).toBe(1);
expect(options[0].textContent).toContain('Keller');
});
});