test(coverage): drive browser tests to 80% on all metrics (#496) #505

Merged
marcel merged 189 commits from feat/issue-496-browser-coverage-tests into main 2026-05-11 21:50:39 +02:00
Showing only changes of commit 9598ec32a7 - Show all commits

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');
});
});