dorfteich/apps/web/e2e/wikilink.spec.ts
Claude Opus 4.8 7244b89215
All checks were successful
CD / Build and push images (push) Successful in 3m2s
CI / Lint, typecheck, test (push) Successful in 2m15s
CI / Auth e2e pack (push) Successful in 2m42s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Add wikilink node with autocomplete (#46)
Introduce Obsidian-style `[[page links]]` (ADR 0004).

- shared: reserved `wikilink` inline atom in the editor schema (attrs
  `targetSlug`, optional `displayText`); markdown mapping `[[slug]]` /
  `[[slug|text]]` via a markdown-it inline rule + serializer node; plain-text
  and HTML derivation include the shown text. Round-trip + parse unit tests.
- web:
  - `Wikilink` node extension with a React NodeView: shows the explicit
    display text or the target's current title (so a rename updates the link),
    renders a missing target as a dashed phantom with a tooltip, navigates on
    click in read mode.
  - `[[` autocomplete popup (`WikilinkAutocomplete`), dependency-free: filters
    the pond's pages as you type with a create-new-page hint for misses,
    Enter/click inserts the node and removes the typed `[[query`; ↑/↓/Enter/Esc
    intercepted in the capture phase so ProseMirror does not act on them.
  - `WikilinkContext` provides the pond's pages (slug→title) for live
    resolution and the autocomplete, populated by the page editor.
  - i18n `editor.wikilink.*` (de + en); wikilink + phantom + popup styles.
- e2e `wikilink.spec.ts` (new CI pack): type `[[`, autocomplete filters and
  inserts a working link that resolves the target title and persists across a
  reload. Phantom → live resolution on page creation is verified in #47.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 12:42:01 +02:00

64 lines
2.7 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { contextForUser } from './helpers';
/**
* Wikilink pack (issue #46). Types `[[` in the editor, verifies the
* autocomplete filters and inserts a working link node, and that the link
* survives a reload (persisted through the collab server). Language-independent
* selectors (CSS classes + page titles). Phantom → live resolution on page
* creation is verified in #47.
*/
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 };
}
async function createPage(context: Ctx, pondId: string, title: string): Promise<{ slug: string }> {
const created = await context.request.post(`/api/v1/ponds/${pondId}/pages`, { data: { title } });
return created.json();
}
test('typing [[ autocompletes and inserts a working wikilink', async ({ browser }) => {
const context = await contextForUser(browser, BASE_URL, 'fixture-user');
const pond = await personalPond(context);
const ts = Date.now();
const targetTitle = `Wiki Target ${ts}`;
await createPage(context, pond.id, targetTitle);
const source = await createPage(context, pond.id, `Wiki Source ${ts}`);
const page = await context.newPage();
await page.goto(`/p/${pond.slug}/${source.slug}`);
// Enter edit mode and focus the editor body.
await page.locator('.editor-page__mode-toggle').click();
const body = page.locator('.editor-content .ProseMirror');
await expect(body).toBeVisible();
await body.click();
// Type the trigger and part of the target title — the popup filters live.
await page.keyboard.type(`[[Wiki Target ${ts}`);
const suggest = page.locator('.wikilink-suggest');
await expect(suggest).toBeVisible();
await expect(suggest.getByText(targetTitle, { exact: true })).toBeVisible();
// Enter inserts the wikilink node, which renders the target's current title.
await page.keyboard.press('Enter');
const link = page.locator('.editor-content a.wikilink', { hasText: targetTitle });
await expect(link).toBeVisible();
// A resolved (existing) target is not phantom.
await expect(link).not.toHaveClass(/wikilink--phantom/);
// Persisted through collaboration: reload and the link is still there.
await page.reload();
await page.locator('.editor-page__mode-toggle').click();
await expect(page.locator('.editor-content a.wikilink', { hasText: targetTitle })).toBeVisible();
await context.close();
});