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 06c11963e9 - Show all commits

View File

@@ -0,0 +1,106 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import MentionDropdown from './MentionDropdown.svelte';
afterEach(cleanup);
const makePerson = (id: string, name: string, overrides: Record<string, unknown> = {}) => ({
id,
firstName: name.split(' ')[0] ?? null,
lastName: name.split(' ').slice(1).join(' ') || name,
displayName: name,
birthYear: null as number | null,
deathYear: null as number | null,
...overrides
});
const baseModel = (overrides: Record<string, unknown> = {}) => ({
items: [] as ReturnType<typeof makePerson>[],
command: vi.fn(),
clientRect: () => new DOMRect(100, 100, 0, 24),
...overrides
});
describe('MentionDropdown', () => {
it('renders the listbox with the mention label', async () => {
render(MentionDropdown, { props: { model: baseModel() } });
await expect.element(page.getByRole('listbox', { name: /person verlinken/i })).toBeVisible();
});
it('renders the empty placeholder when items is empty', async () => {
render(MentionDropdown, { props: { model: baseModel() } });
await expect.element(page.getByText('Keine Personen gefunden')).toBeVisible();
});
it('shows the create-new escape hatch link in the empty state', async () => {
render(MentionDropdown, { props: { model: baseModel() } });
const link = (await page
.getByRole('link', { name: /neue person anlegen/i })
.element()) as HTMLAnchorElement;
expect(link.href).toContain('/persons/new');
expect(link.target).toBe('_blank');
expect(link.rel).toContain('noopener');
});
it('renders one option per item when populated', async () => {
render(MentionDropdown, {
props: {
model: baseModel({
items: [makePerson('p1', 'Anna Schmidt'), makePerson('p2', 'Bert Meier')]
})
}
});
await expect.element(page.getByText('Anna Schmidt')).toBeVisible();
await expect.element(page.getByText('Bert Meier')).toBeVisible();
});
it('marks the first item as aria-selected by default', async () => {
render(MentionDropdown, {
props: { model: baseModel({ items: [makePerson('p1', 'Anna Schmidt')] }) }
});
const option = document.querySelector('[role="option"]');
expect(option?.getAttribute('aria-selected')).toBe('true');
});
it('renders the life-date range when birthYear or deathYear is present', async () => {
render(MentionDropdown, {
props: {
model: baseModel({
items: [makePerson('p1', 'Anna', { birthYear: 1899, deathYear: 1972 })]
})
}
});
await expect.element(page.getByText(/1899/)).toBeVisible();
});
it('falls back to a default position when clientRect returns null', async () => {
render(MentionDropdown, {
props: {
model: baseModel({ clientRect: () => null })
}
});
const dropdown = document.querySelector('[role="listbox"]') as HTMLElement;
expect(dropdown.style.left).toBe('0px');
});
it('positions itself based on the clientRect callback', async () => {
render(MentionDropdown, {
props: {
model: baseModel({
clientRect: () => new DOMRect(123, 200, 50, 24)
})
}
});
const dropdown = document.querySelector('[role="listbox"]') as HTMLElement;
expect(dropdown.style.left).toBe('123px');
});
});