1
17import { test, expect } from "../fixtures";
18
19interface WhiteboardPageDto {
20 id: string;
21}
22interface CanvasElement {
23 id: string;
24 type: string;
25}
26
27const TOTAL = 2_000;
28const NEAR = 10;
29
30
32function buildCanvas() {
33 const elements: Record<string, CanvasElement> = {};
34 for (let i = 0; i < NEAR; i++) {
35 elements[`r${i}`] = {
36 id: `r${i}`,
37 type: "rect",
38 x: (i % 2) * 80,
39 y: Math.floor(i / 2) * 60,
40 w: 60,
41 h: 40,
42 z: i,
43 fill: null,
44 stroke: null
45 };
46 }
47 for (let i = NEAR; i < TOTAL; i++) {
48 const j = i - NEAR;
49 elements[`r${i}`] = {
50 id: `r${i}`,
51 type: "rect",
52 x: 10_000 + (j % 20) * 100,
53 y: Math.floor(j / 20) * 100,
54 w: 60,
55 h: 40,
56 z: i,
57 fill: null,
58 stroke: null
59 };
60 }
61 return { version: 1, elements };
62}
63
64test.describe("Whiteboard — 2,000-object performance gate (T-167/P5)", () => {
65 test("renders a 2,000-element board with viewport culling (DOM ≪ total)", async ({
66 authPage,
67 api,
68 freshWorkspace
69 }) => {
70 const ws = await freshWorkspace("Perf Gate Space");
71 const createRes = await api.post(`/api/v1/workspaces/${ws.id}/pages`, {
72 data: { title: "Perf Wall", type: "whiteboard" }
73 });
74 expect(createRes.status(), await createRes.text()).toBe(201);
75 const page = (await createRes.json()) as WhiteboardPageDto;
76
77
78
79 const canvas = buildCanvas();
80 const put = await api.put(`/api/v1/pages/${page.id}/whiteboard`, { data: canvas });
81 expect(put.status(), await put.text()).toBe(200);
82
83
84 const get = await api.get(`/api/v1/pages/${page.id}/whiteboard`);
85 expect(get.status()).toBe(200);
86 const body = (await get.json()) as { canvas?: { elements?: Record<string, CanvasElement> } };
87 expect(Object.keys(body.canvas?.elements ?? {}).length).toBe(TOTAL);
88
89 await authPage.goto(`/w/${ws.slug}/page/${page.id}`);
90 const root = authPage.getByTestId("wb-root");
91 await expect(root).toBeVisible({ timeout: 20_000 });
92 await expect(authPage.getByTestId("wb-canvas")).toBeVisible();
93
94
95
96
97
98
99
100 await expect
101 .poll(
102 async () => authPage.getByTestId("wb-rect").count(),
103 { timeout: 15_000, message: "expected the renderer to cull off-viewport elements (mount ≪ total)" }
104 )
105 .toBeLessThan(100);
106
107
108 const mounted = await authPage.getByTestId("wb-rect").count();
109 expect(mounted).toBeGreaterThan(0);
110
111 expect(mounted).toBeLessThan(TOTAL / 10);
112 });
113});
114