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
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
64 lines
2.7 KiB
TypeScript
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();
|
|
});
|