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:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user