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 = {}) => ({ 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 = {}) => ({ items: [] as ReturnType[], 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'); }); });