1
2
16(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
17import { act } from "react";
18import { createRoot, type Root } from "react-dom/client";
19import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
20
21const mocks = {
22 moveOpen: false,
23 moveContext: null as null | { pageId: string; workspaceId: string; title?: string },
24 setMove: vi.fn(),
25 pushToast: vi.fn(),
26 mutate: vi.fn(),
27 tree: [
28 { id: "p1", parentId: null, title: "The page being moved", icon: null, type: "page", position: "A", updatedAt: "" },
29 { id: "p2", parentId: null, title: "Folder A", icon: null, type: "page", position: "B", hasMore: true, updatedAt: "" },
30 { id: "p3", parentId: null, title: "Leaf", icon: null, type: "page", position: "C", updatedAt: "" }
31 ]
32};
33
34vi.mock("@/hooks/use-pages", () => ({
35 usePageTree: (_wsId: string, parentId: string | null) => ({
36 data: parentId === null ? mocks.tree : [],
37 isLoading: false
38 }),
39 useUpdatePage: () => ({ mutate: mocks.mutate })
40}));
41
42vi.mock("@/stores/ui-store", () => ({
43 useUiStore: (sel: (s: typeof mocks) => unknown) => sel(mocks)
44}));
45
46import { MoveToDialog } from "../move-to-dialog";
47
48let root: Root;
49let host: HTMLElement;
50
51beforeEach(() => {
52 host = document.createElement("div");
53 document.body.appendChild(host);
54 root = createRoot(host);
55 mocks.moveOpen = false;
56 mocks.moveContext = null;
57 mocks.mutate.mockClear();
58 mocks.setMove.mockClear();
59});
60
61afterEach(() => {
62 act(() => root.unmount());
63 host.remove();
64});
65
66const renderDialog = () => act(() => root.render(<MoveToDialog />));
67
68const buttons = () => Array.from(host.querySelectorAll("button"));
69
70const buttonWithText = (label: string) =>
71 buttons().find((b) => (b.textContent ?? "").includes(label));
72
73describe("MoveToDialog", () => {
74 it("renders nothing until the ui-store opens it", () => {
75 renderDialog();
76 expect(host.querySelector('[role="dialog"]')).toBeNull();
77 });
78
79 it("lists top-level pages and excludes the page being moved", () => {
80 mocks.moveOpen = true;
81 mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "The page being moved" };
82 renderDialog();
83
84 expect(host.textContent).toContain('Move “The page being moved” to…');
85 expect(host.textContent).toContain("Root (top level)");
86 expect(buttonWithText("Folder A")).toBeTruthy();
87 expect(buttonWithText("Leaf")).toBeTruthy();
88
89 expect(host.textContent).not.toContain("The page being moved” is");
90 const rows = buttons().filter((b) => (b.textContent ?? "").includes("The page being moved"));
91 expect(rows).toHaveLength(0);
92 });
93
94 it("keeps 'Move here' disabled until a destination is picked", () => {
95 mocks.moveOpen = true;
96 mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" };
97 renderDialog();
98 expect(buttonWithText("Move here")?.disabled).toBe(true);
99 });
100
101 it("moves to the root level when 'Root (top level)' is picked", () => {
102 mocks.moveOpen = true;
103 mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" };
104 renderDialog();
105
106 act(() => buttonWithText("Root (top level)")?.click());
107 const move = buttonWithText("Move here");
108 expect(move?.disabled).toBe(false);
109
110 act(() => move?.click());
111 expect(mocks.mutate).toHaveBeenCalledTimes(1);
112 expect(mocks.mutate.mock.calls[0]?.[0]).toEqual({ id: "p1", parentId: null });
113 });
114
115 it("moves under the picked page", () => {
116 mocks.moveOpen = true;
117 mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" };
118 renderDialog();
119
120 act(() => buttonWithText("Folder A")?.click());
121 act(() => buttonWithText("Move here")?.click());
122
123 expect(mocks.mutate).toHaveBeenCalledTimes(1);
124 expect(mocks.mutate.mock.calls[0]?.[0]).toEqual({ id: "p1", parentId: "p2" });
125 });
126});
127