dorfteich/apps/web/e2e/search.spec.ts
Claude Fable 5 2f5d94504d
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m25s
CI / Build container images (pull_request) Successful in 1m9s
CI / Auth e2e pack (pull_request) Successful in 6m47s
CI / Import/export fidelity gate (pull_request) Successful in 54s
search.spec: Fehl-Klick entfernt — Reload IST der Weg zurück in den Lesemodus
Der Spec klickte nach page.reload() den Mode-Toggle „um den Edit-Modus zu
verlassen" — nach dem Reload ist die Seite aber schon im Lesemodus (React-
State resettet), der Klick wechselte also HINEIN. Das passierte jahrelang
folgenlos, weil der Fokus auf dem Toggle-Button blieb und „/" die Suche
öffnete. Seit dem Editor-Auto-Fokus (PR #140) landet der Fokus im Editor
und „/" wird dort zu Text — je nach Ausgang des Rennens gegen den rAF-
verzögerten Fokus mal grün (Läufe 381/385), mal rot (383/387/389/390).
Fix: den Klick streichen; „/" läuft im Lesemodus als globaler Shortcut.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
2026-07-19 21:15:42 +02:00

71 lines
3.2 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { contextForUser } from './helpers';
/**
* Search UI pack (issue #50). Adds body content to a page through the editor
* (indexed by #49 via the collab persistence hook), then opens the search
* palette with the "/" shortcut and checks that the content is found, the match
* is highlighted, the scope toggle works, and Enter opens the page.
* Language-independent selectors (CSS classes + unique content).
*/
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173';
type Ctx = Awaited<ReturnType<typeof contextForUser>>;
async function personalPond(context: Ctx): Promise<{ id: string; slug: string }> {
const ponds = await context.request.get('/api/v1/ponds');
const pond = (await ponds.json()).find((p: { type: string }) => p.type === 'personal');
return { id: pond.id, slug: pond.slug };
}
test('search finds page content, highlights it, and is keyboard-operable', async ({ browser }) => {
const context = await contextForUser(browser, BASE_URL, 'fixture-user');
const pond = await personalPond(context);
const ts = Date.now();
const word = `srchword${ts}`;
const title = `Search Page ${ts}`;
const created = await (
await context.request.post(`/api/v1/ponds/${pond.id}/pages`, { data: { title } })
).json();
const page = await context.newPage();
await page.goto(`/p/${pond.slug}/${created.slug}`);
// Add body content through the editor, then reload to flush it to the index.
await page.locator('.editor-page__mode-toggle').click();
const body = page.locator('.editor-content .ProseMirror');
await body.click();
await page.keyboard.type(`the unique ${word} lives in this body`);
await page.reload();
await expect(page.locator('.editor-content')).toContainText(word);
// The reload already landed back in read mode (the mode state resets), so
// "/" acts as the global shortcut here. This spec used to click the mode
// toggle again "to leave edit mode" — which actually RE-ENTERED it; that
// passed only while entering edit mode left focus on the toggle button.
// Since the editor grabs focus on entering edit mode (PR #140), "/" would
// become ordinary editor input there, racing the deferred focus — the
// intermittent-then-persistent failure of this pack.
// Open search with the "/" shortcut.
await page.keyboard.press('/');
const palette = page.locator('.search-palette');
await expect(palette).toBeVisible();
await palette.locator('.search-palette__input').fill(word);
const result = page.locator('.search-result', { hasText: title });
await expect(result).toBeVisible();
// The matched word is highlighted in the snippet.
await expect(page.locator('.search-result__snippet mark')).toBeVisible();
// Scope toggle is present and keeps the result (page is in the user's ponds).
await palette.locator('.search-palette__scope input[type="checkbox"]').first().check();
await expect(page.locator('.search-result', { hasText: title })).toBeVisible();
// Enter opens the selected result.
await palette.locator('.search-palette__input').press('Enter');
await expect(page).toHaveURL(new RegExp(`/p/${pond.slug}/${created.slug}$`));
await context.close();
});