/**
* Confluence P1 — T-138 (page duplicate) + T-140 (backlinks/Related panel) E2E
*
* Both API surfaces existed since 2026-10-11 (`POST /pages/:id/duplicate`,
* `GET /pages/:id/backlinks`); this spec verifies the WEB surfaces that
* landed in this round:
*
* - T-138: the topbar "More" → Duplicate menu item deep-copies the page
* (server title `
(copy)`), lands on the NEW page, and the new page
* carries a recursive copy of the source's blocks.
* - T-140: the page-view "Related" button opens the backlinks slide-over —
* a page referencing this one (via a CHILD_PAGE block) is listed, and the
* empty state shows when nothing references it.
*
* Origin: `https://notion.filps.com` (Caddy single-origin; localhost does NOT
* work — see confluence-templates.spec.ts). URL navigation uses the workspace
* SLUG (sidebar/topbar links); API calls use the workspace ID.
*/
import { test, expect } from "../fixtures";
import { createPage, createBlock, getBlocks } from "../utils/api";
import type { PageDto, BlockDto } from "../fixtures";
test.describe("Confluence P1 — page duplicate + related (T-138/T-140)", () => {
test("topbar More → Duplicate deep-copies the page and its blocks", async ({
authPage,
api,
freshWorkspace
}) => {
const ws = await freshWorkspace("Duplicate Space");
const src = (await createPage(api, ws.id, { title: "Dup Source" })) as PageDto;
const blocks = [
await createBlock(api, src.id, { type: "PARAGRAPH", content: { text: "dup-block-A" } }),
await createBlock(api, src.id, { type: "PARAGRAPH", content: { text: "dup-block-B" } })
];
const srcPath = `/w/${ws.slug}/page/${src.id}`;
await authPage.goto(srcPath);
await authPage.getByRole("button", { name: "More actions" }).click();
await authPage.getByRole("menuitem", { name: "Duplicate" }).click();
// Land on the NEW page — wait until the URL is NOT the source page's.
await authPage.waitForURL(
(u) => u.pathname !== srcPath,
{ timeout: 15_000 },
);
const copyId = new URL(authPage.url()).pathname.split("/").pop() ?? "";
expect(copyId, "the copy has its own page id").not.toBe(src.id);
// GET /pages/:id returns the { page, workspace, … } ENVELOPE (tolerate a
// bare page too, in case the endpoint changes shape).
const copyRes = await (await api.get(`/api/v1/pages/${copyId}`)).json() as {
page?: PageDto;
} & PageDto;
const copyPage = copyRes.page ?? copyRes;
expect(copyPage.title).toBe("Dup Source (copy)");
// The recursive copy carries the source blocks' text.
const copyBlocks = await getBlocks(api, copyId);
const texts = copyBlocks
.filter((b) => b.type === "PARAGRAPH")
.map((b) => (b.content as { text?: string })?.text ?? "");
expect(texts).toEqual(
expect.arrayContaining(["dup-block-A", "dup-block-B"]),
);
});
test("Related panel lists a referencing page (CHILD_PAGE) and shows an empty state otherwise", async ({
authPage,
api,
freshWorkspace
}) => {
const ws = await freshWorkspace("Related Space");
const referenced = (await createPage(api, ws.id, { title: "Related Target" })) as PageDto;
const referrer = (await createPage(api, ws.id, { title: "Related Source" })) as PageDto;
// The referrer embeds the referenced page via a CHILD_PAGE block — the
// exact block type `backlinks` scans (content.pageId).
await createBlock(api, referrer.id, {
type: "CHILD_PAGE",
content: { pageId: referenced.id }
});
await authPage.goto(`/w/${ws.slug}/page/${referenced.id}`);
await authPage.getByTestId("related-button").click();
const panel = authPage.getByTestId("related-panel");
await expect(panel).toBeVisible();
// The referrer is listed by title, deep-linking to it.
const row = panel.getByTestId(`related-item-${referrer.id}`);
await expect(row).toHaveText("Related Source");
await expect(row).toHaveAttribute(
"href",
new RegExp(`/w/${ws.slug}/page/${referrer.id}$`)
);
// Close (the row click navigates + closes; use the close button first).
await panel.getByRole("button", { name: "Close related pages" }).click();
await expect(panel).toBeHidden();
// Empty state: a page nothing references.
await authPage.goto(`/w/${ws.slug}/page/${referrer.id}`);
await authPage.getByTestId("related-button").click();
const emptyPanel = authPage.getByTestId("related-panel");
await expect(emptyPanel).toBeVisible();
await expect(emptyPanel.getByText("No related pages yet")).toBeVisible();
});
});