fix(persons): prevent stale navigation from clobbering focused search input
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 2m12s
CI / Backend Unit Tests (pull_request) Successful in 1m58s
CI / E2E Tests (pull_request) Successful in 17m40s
CI / Unit & Component Tests (push) Successful in 1m58s
CI / Backend Unit Tests (push) Successful in 1m59s
CI / E2E Tests (push) Successful in 14m56s

The persons list search input used value={data.q || ''} bound directly to
server data, so every navigation completion would reset it to the URL value
mid-typing, dropping keystrokes just like issue #34 on the home page.

Apply the same focus-guard fix: introduce local `q` state, a `qFocused`
flag, and a guarded $effect that only syncs URL → state when the input is
not focused. Adds a regression test matching the home-page pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #44.
This commit is contained in:
Marcel
2026-03-20 21:54:56 +01:00
parent 513a7290b0
commit da0d5495d0
2 changed files with 84 additions and 4 deletions

View File

@@ -0,0 +1,70 @@
import { describe, expect, it, vi } from 'vitest';
import { 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()] };
// ─── 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');
});
});