import { expect, test } from '@playwright/test'; import { contextForUser } from './helpers'; /** * Tasks pack (issues #150/#152/#153/#154): task lines get stable ids, the * task overview block collects the page + subtree into a table with mention * and date columns, and checking a box in the overview writes back to the * source page through the collab server. Language-independent selectors. */ 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 }; } test('task overview collects subtree tasks and toggles write back', async ({ browser }) => { test.setTimeout(120_000); const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const ts = Date.now(); const parentRes = await context.request.post(`/api/v1/ponds/${pond.id}/pages`, { data: { title: `Tasks Parent ${ts}` }, }); const parent = await parentRes.json(); const childRes = await context.request.post(`/api/v1/ponds/${pond.id}/pages`, { data: { title: `Tasks Child ${ts}`, parentId: parent.id }, }); const child = await childRes.json(); const page = await context.newPage(); // Child: one task line with a due date (typed, so it gets an id, #153). await page.goto(`/p/${pond.slug}/${child.slug}`); await page.locator('.editor-page__mode-toggle').click(); const body = page.locator('.editor-content .ProseMirror'); await body.click(); await page.locator('.editor-toolbar button', { hasText: '☑' }).click(); await page.keyboard.type('Kabel prüfen >>31.12.2026 '); await expect(page.locator('.editor-content .dt-date--due')).toBeVisible(); // The collab server persists debounced (~2 s) — wait until the task shows // up in the collection before moving on. await expect .poll( async () => { const res = await context.request.get(`/api/v1/read/${pond.slug}/${child.slug}/tasks`); return JSON.stringify(await res.json()); }, { timeout: 20_000 }, ) .toContain('Kabel prüfen'); // Parent: a task line plus the overview block. await page.goto(`/p/${pond.slug}/${parent.slug}`); await page.locator('.editor-page__mode-toggle').click(); await page.locator('.editor-content .ProseMirror').click(); await page.locator('.editor-toolbar button', { hasText: '☑' }).click(); await page.keyboard.type('Bühne buchen'); await page.keyboard.press('Enter'); // Leave the task list (an empty item converts back to a paragraph). await page.keyboard.press('Enter'); await page.locator('.editor-toolbar__block-select').selectOption('builtin/tasks'); await expect(page.locator('.dt-transclusion-card')).toBeVisible(); await expect .poll( async () => { const res = await context.request.get(`/api/v1/read/${pond.slug}/${parent.slug}/tasks`); return ((await res.json()) as { tasks: unknown[] }[]).reduce( (sum, p) => sum + p.tasks.length, 0, ); }, { timeout: 20_000 }, ) .toBe(2); // Read mode: the overview table lists both tasks with the date column. await page.reload(); const table = page.locator('.dt-task-overview-table'); await expect(table).toBeVisible(); await expect(table).toContainText('Bühne buchen'); await expect(table).toContainText('Kabel prüfen'); await expect(table.locator('tbody tr')).toHaveCount(2); // Toggle the child's task from the overview — it writes back through the // collab server; after the refetch the box stays checked. const childRow = table.locator('tbody tr', { hasText: 'Kabel prüfen' }); const checkbox = childRow.locator('input[type="checkbox"]'); await expect(checkbox).toBeEnabled(); await checkbox.check(); // Write-back travels api → NOTIFY → collab → debounced persist. await expect .poll( async () => { const res = await context.request.get(`/api/v1/read/${pond.slug}/${child.slug}/tasks`); const pages = (await res.json()) as { tasks: { checked: boolean }[] }[]; return pages[0]?.tasks[0]?.checked ?? false; }, { timeout: 20_000 }, ) .toBe(true); await page.reload(); await expect( page .locator('.dt-task-overview-table tbody tr', { hasText: 'Kabel prüfen' }) .locator('input[type="checkbox"]'), ).toBeChecked(); // The source page itself now shows the checked box. await page.goto(`/p/${pond.slug}/${child.slug}`); await expect(page.locator('.editor-content li[data-type="task_item"] input')).toBeChecked(); await context.close(); });