All checks were successful
CD / Build and push images (push) Successful in 3m24s
CI / Lint, typecheck, test (push) Successful in 2m16s
CI / Auth e2e pack (push) Successful in 2m52s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
A search palette over the #49 full-text search. - web: - `SearchPalette`: opened from a top-bar button or the global "/" shortcut (ignored while typing in a field). Scoped to the current pond by default with an "all my ponds" toggle and, when scoped, a label filter. Results list title, pond, label chips, and a highlighted snippet; recent searches (localStorage) show before typing; empty/error/hint states. - `HighlightedSnippet` renders the match — the api wraps hits in shared sentinels (private-use codepoints), split here into `<mark>` so no HTML from the content is interpreted. - Fully keyboard-operable: "/" opens, ↑/↓ move, Enter opens the page, Esc closes. - i18n `search` namespace (de + en); palette + result styles. - api: a pondId scope test proves search narrows to one pond. - e2e `search.spec.ts` (new CI pack): body content added via the editor is found and highlighted, the scope toggle keeps the result, and Enter opens the page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
66 lines
2.8 KiB
TypeScript
66 lines
2.8 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);
|
|
// Leave edit mode so "/" is a shortcut, not editor input.
|
|
await page.locator('.editor-page__mode-toggle').click();
|
|
|
|
// 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();
|
|
});
|