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>
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
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');
|
||
});
|
||
});
|