Files
familienarchiv/frontend/src/routes/persons/page.svelte.spec.ts
Marcel f6634f1d00 fix(tests): fix Svelte 5 event delegation not firing via Playwright locator clicks
Replace Playwright locator .click() calls with native DOM element.click()
for all tests that trigger Svelte 5 delegated onclick handlers ($.delegated).
Playwright's CDP-based synthetic events don't propagate through Svelte 5's
document-level handle_event_propagation delegation mechanism, while native
DOM .click() does.

Also replace locator.click() with element.focus() for onfocus handler tests,
and add cleanup() to afterEach in all spec files missing it to prevent test
pollution between runs. Fix TagInput.svelte to use untrack() when reading
bindable state after an await to avoid track_reactivity_loss errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 12:34:56 +01:00

73 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import Page from './+page.svelte';
const tick = () => new Promise((r) => setTimeout(r, 0));
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
const makePerson = (overrides = {}) => ({
id: '1',
firstName: 'Max',
lastName: 'Mustermann',
...overrides
});
const emptyData = { user: undefined, canWrite: true, q: '', persons: [] };
const dataWithPersons = { ...emptyData, persons: [makePerson()] };
afterEach(cleanup);
// ─── Rendering ────────────────────────────────────────────────────────────────
describe('Persons page rendering', () => {
it('renders the search input', async () => {
render(Page, { data: emptyData });
await expect.element(page.getByRole('textbox')).toBeInTheDocument();
});
it('pre-fills the search input from data.q', async () => {
render(Page, { data: { ...emptyData, q: 'Müller' } });
await expect.element(page.getByRole('textbox')).toHaveValue('Müller');
});
it('shows empty state when no persons', async () => {
render(Page, { data: emptyData });
await expect.element(page.getByText('Keine Personen gefunden')).toBeInTheDocument();
});
it('renders person cards', async () => {
render(Page, { data: dataWithPersons });
await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument();
});
it('links person card to detail page', async () => {
render(Page, { data: dataWithPersons });
await expect
.element(page.getByRole('link', { name: /Max Mustermann/ }))
.toHaveAttribute('href', '/persons/1');
});
});
// ─── Keystroke preservation (issue #34) ──────────────────────────────────────
describe('Persons page search input keystroke preservation', () => {
it('does not overwrite the search input while the user is focused and stale data arrives', async () => {
const { rerender } = render(Page, { data: emptyData });
const input = page.getByRole('textbox');
// User types "abc" — input is focused
await input.click();
await input.fill('abc');
// Simulate a navigation completing with stale data (q='a') while the user is still typing
await rerender({ data: { ...emptyData, q: 'a' } });
await tick();
// Input must still show what the user typed, not the stale URL value
await expect.element(input).toHaveValue('abc');
});
});