1
18import { test, expect } from "../fixtures";
19import { createPage } from "../utils/api";
20import type { PageDto } from "../fixtures";
21
22test.describe("Confluence P1 — bulk page actions (T-139)", () => {
23
24
25
26 async function selectPage(page: import("@playwright/test").Page, id: string, title: string) {
27 const row = page.locator("li", { has: page.getByTestId(`tree-select-${id}`) });
28 await row.getByTitle(title, { exact: true }).hover();
29 const cb = row.getByTestId(`tree-select-${id}`);
30 await expect(cb).toBeVisible();
31 await cb.click();
32 }
33
34 test("multi-select 2 pages → confirm bulk trash → both leave the tree", async ({
35 authPage,
36 api,
37 freshWorkspace
38 }) => {
39 const ws = await freshWorkspace("Bulk Space");
40 const p1 = (await createPage(api, ws.id, { title: "Bulk Page A" })) as PageDto;
41 const p2 = (await createPage(api, ws.id, { title: "Bulk Page B" })) as PageDto;
42
43 await authPage.goto(`/w/${ws.slug}/page/${p1.id}`);
44
45
46 await selectPage(authPage, p1.id, "Bulk Page A");
47 await selectPage(authPage, p2.id, "Bulk Page B");
48
49
50 const bar = authPage.getByTestId("bulk-action-bar");
51 await expect(bar).toBeVisible();
52 await expect(authPage.getByTestId("bulk-count")).toHaveText("2 selected");
53
54
55 await authPage.getByTestId("bulk-trash").click();
56 const dialog = authPage.getByRole("dialog", { name: /Move to trash 2 pages/ });
57 await expect(dialog).toBeVisible();
58
59 await dialog.getByRole("button", { name: "Move to trash" }).click();
60 await expect(dialog).toBeHidden();
61
62
63
64
65 await expect(authPage.getByTestId("bulk-action-bar")).toBeHidden();
66 await expect(authPage.getByTestId(`tree-select-${p1.id}`)).toHaveCount(0);
67 await expect(authPage.getByTestId(`tree-select-${p2.id}`)).toHaveCount(0);
68
69
70 const res = await api.get(`/api/v1/workspaces/${ws.id}/pages`);
71 const body = (await res.json()) as { data: Array<{ id: string }> };
72 const ids = (body.data ?? []).map((p) => p.id);
73 expect(ids).not.toContain(p1.id);
74 expect(ids).not.toContain(p2.id);
75 });
76
77 test("the confirm dialog gates the action — Cancel leaves the pages + selection intact", async ({
78 authPage,
79 api,
80 freshWorkspace
81 }) => {
82 const ws = await freshWorkspace("Bulk Cancel Space");
83 const p1 = (await createPage(api, ws.id, { title: "Keep Me" })) as PageDto;
84
85 await authPage.goto(`/w/${ws.slug}/page/${p1.id}`);
86 await selectPage(authPage, p1.id, "Keep Me");
87
88 await authPage.getByTestId("bulk-trash").click();
89 const dialog = authPage.getByRole("dialog", { name: /Move to trash 1 page/ });
90 await expect(dialog).toBeVisible();
91
92 await dialog.getByRole("button", { name: "Cancel" }).click();
93 await expect(dialog).toBeHidden();
94
95
96 await expect(authPage.getByTestId("bulk-action-bar")).toBeVisible();
97 await expect(authPage.getByTestId("bulk-count")).toHaveText("1 selected");
98
99 const res = await api.get(`/api/v1/workspaces/${ws.id}/pages`);
100 const body = (await res.json()) as { data: Array<{ id: string }> };
101 expect((body.data ?? []).map((p) => p.id)).toContain(p1.id);
102 });
103});
104