/** * T-150/T-151 — page versions compare/preview E2E (dev-stack, Playwright, * single-origin). * * Origin: `https://notion.filps.com` (Caddy → web :3000 / api :4000). A * localhost origin does NOT work — the API's CORS is pinned to the domain. * * Verifies the versions panel end to end: * - two snapshots (POST /pages/:id/versions) bracketing a block edit * (PATCH /blocks/:id) * - the topbar "Page history" popover lists both versions (v2/v1 + notes) * - "Compare" opens the dialog: the block-level diff (GET …/compare?from&to) * reports 1 modified (fields: content) * - the Preview tab renders the v2 snapshot read-only (GET * …/versions/:versionId) with the CHANGED text (not the live state) */ import { test, expect } from "../fixtures"; import { createPage, createBlock } from "../utils/api"; import type { PageDto } from "../fixtures"; interface VersionRow { id: string; version: number; note: string | null; } test.describe("Versions compare/preview — P2 (T-150/T-151)", () => { test("history → compare shows the diff → preview shows the snapshot text", async ({ authPage, api, freshWorkspace }) => { const ws = await freshWorkspace("Versions Space"); const page = (await createPage(api, ws.id, { title: "Versioned Doc" })) as PageDto; const block = await createBlock(api, page.id, { type: "PARAGRAPH", content: { text: "Original text" } }); // v1 snapshot → edit the block → v2 snapshot. const snap1 = await api.post(`/api/v1/pages/${page.id}/versions`, { data: { note: "snapshot-1" } }); expect(snap1.status(), await snap1.text()).toBe(201); const up = await api.patch(`/api/v1/blocks/${block.id}`, { data: { content: { text: "Changed text" } } }); expect(up.status(), await up.text()).toBe(200); const snap2 = await api.post(`/api/v1/pages/${page.id}/versions`, { data: { note: "snapshot-2" } }); expect(snap2.status(), await snap2.text()).toBe(201); // The list (version desc) gives both ids for the selectors. const list = (await (await api.get(`/api/v1/pages/${page.id}/versions`)).json()) as { data: VersionRow[] }; expect(list.data.map((v) => v.version).sort()).toEqual([1, 2]); const v2 = list.data.find((v) => v.version === 2)!; const v1 = list.data.find((v) => v.version === 1)!; // Open the page; the topbar history button appears once the page loads. await authPage.goto(`/w/${ws.slug}/page/${page.id}`); const historyButton = authPage.getByRole("button", { name: "Page history" }); await expect(historyButton).toBeVisible({ timeout: 20_000 }); await historyButton.click(); // Both versions are listed, newest first. const menu = authPage.getByRole("menu", { name: "Page history" }); await expect(menu).toBeVisible(); await expect(menu).toContainText("v2 — snapshot-2"); await expect(menu).toContainText("v1 — snapshot-1"); // Compare v1 → v2: the dialog reports exactly one modified block (content). await menu.getByTestId(`version-compare-${v2.id}`).click(); const dialog = authPage.getByTestId("version-compare-dialog"); await expect(dialog).toBeVisible({ timeout: 15_000 }); await expect(dialog.getByTestId("version-diff-from")).toHaveValue(String(v1.version), { timeout: 10_000 }); await expect(dialog.getByTestId("version-diff-summary")).toContainText("1 modified", { timeout: 10_000 }); await expect(dialog.getByTestId("version-diff-summary")).toContainText("0 added"); await expect(dialog.getByTestId(`version-diff-item-${block.id}`)).toContainText("content"); // Preview tab: the v2 SNAPSHOT shows the changed text (read-only). await dialog.getByTestId("version-tab-preview").click(); const blocksEl = dialog.getByTestId("version-preview-blocks"); await expect(blocksEl.getByTestId(`version-preview-block-${block.id}`)).toContainText("Changed text", { timeout: 15_000 }); // The live edit is NOT conflated with the snapshot: the v2 snapshot has // exactly the state at snapshot time (2 blocks: title + the paragraph). await expect(dialog.getByTestId("version-preview-blocks")).toContainText("Versioned Doc"); }); });