import { expect, test } from '@playwright/test'; import { contextForUser } from './helpers'; /** * Backlinks + missing-pages pack (issue #48). Drives the editor to create * wikilinks (persisted via collab, indexed by #47), then checks the read-mode * "Linked from" panel and the pond's missing-pages view. Language-independent * selectors (CSS classes + page titles/slugs). */ const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173'; type Ctx = Awaited>; 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(); } /** Types a `[[query` in the editor and picks the first suggestion. */ async function insertWikilink(page: import('@playwright/test').Page, query: string): Promise { const body = page.locator('.editor-content .ProseMirror'); await body.click(); await page.keyboard.type(`[[${query}`); await expect(page.locator('.wikilink-suggest')).toBeVisible(); await page.keyboard.press('Enter'); } test('a backlink appears on the target page after another page links it', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const ts = Date.now(); const targetTitle = `BL Target ${ts}`; const sourceTitle = `BL Source ${ts}`; const target = await createPage(context, pond.id, targetTitle); const source = await createPage(context, pond.id, sourceTitle); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${source.slug}`); await page.locator('.editor-page__mode-toggle').click(); await insertWikilink(page, targetTitle); // Reload disconnects the collab session, flushing the store → page_links row. await page.reload(); await expect(page.locator('.editor-content a.wikilink', { hasText: targetTitle })).toBeVisible(); // Open the target in read mode: the "Linked from" panel lists the source. await page.goto(`/p/${pond.slug}/${target.slug}`); const backlinks = page.locator('.backlinks'); await expect(backlinks).toBeVisible(); await expect(backlinks.locator('.backlinks__link', { hasText: sourceTitle })).toBeVisible(); await context.close(); }); test('missing-pages view lists a phantom target and creates it', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const ts = Date.now(); const ghostSlug = `ghost-page-${ts}`; const source = await createPage(context, pond.id, `Ghost Source ${ts}`); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${source.slug}`); await page.locator('.editor-page__mode-toggle').click(); // No page matches, so the only suggestion is the create-phantom hint. await insertWikilink(page, ghostSlug); await page.reload(); await expect(page.locator('.editor-content a.wikilink.wikilink--phantom')).toBeVisible(); // The pond's missing-pages view lists the phantom slug. await page.goto(`/p/${pond.slug}/settings`); const item = page.locator('.phantom-pages__item', { hasText: ghostSlug }); await expect(item).toBeVisible(); // Creating it navigates to the new page (slug = phantom slug). await item.getByRole('button').click(); await expect(page).toHaveURL(new RegExp(`/p/${pond.slug}/${ghostSlug}$`)); await context.close(); });