// @vitest-environment jsdom /** * MoveToDialog — destination picker behavior. * * Pins: * - the dialog is closed until the ui-store opens it (mounted at the shell, * driven by `setMove`); * - the destination list is the workspace top-level pages EXCLUDING the * page being moved; * - "Root (top level)" selects parentId = null; * - picking a page row selects that page id; * - "Move here" is disabled until a destination is chosen, then fires * updatePage.mutate({ id, parentId }) — the reparent the sidebar tree * refreshes off (useUpdatePage invalidates the page-tree cache). */ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; import { act } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = { moveOpen: false, moveContext: null as null | { pageId: string; workspaceId: string; title?: string }, setMove: vi.fn(), pushToast: vi.fn(), mutate: vi.fn(), tree: [ { id: "p1", parentId: null, title: "The page being moved", icon: null, type: "page", position: "A", updatedAt: "" }, { id: "p2", parentId: null, title: "Folder A", icon: null, type: "page", position: "B", hasMore: true, updatedAt: "" }, { id: "p3", parentId: null, title: "Leaf", icon: null, type: "page", position: "C", updatedAt: "" } ] }; vi.mock("@/hooks/use-pages", () => ({ usePageTree: (_wsId: string, parentId: string | null) => ({ data: parentId === null ? mocks.tree : [], isLoading: false }), useUpdatePage: () => ({ mutate: mocks.mutate }) })); vi.mock("@/stores/ui-store", () => ({ useUiStore: (sel: (s: typeof mocks) => unknown) => sel(mocks) })); import { MoveToDialog } from "../move-to-dialog"; let root: Root; let host: HTMLElement; beforeEach(() => { host = document.createElement("div"); document.body.appendChild(host); root = createRoot(host); mocks.moveOpen = false; mocks.moveContext = null; mocks.mutate.mockClear(); mocks.setMove.mockClear(); }); afterEach(() => { act(() => root.unmount()); host.remove(); }); const renderDialog = () => act(() => root.render()); const buttons = () => Array.from(host.querySelectorAll("button")); const buttonWithText = (label: string) => buttons().find((b) => (b.textContent ?? "").includes(label)); describe("MoveToDialog", () => { it("renders nothing until the ui-store opens it", () => { renderDialog(); expect(host.querySelector('[role="dialog"]')).toBeNull(); }); it("lists top-level pages and excludes the page being moved", () => { mocks.moveOpen = true; mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "The page being moved" }; renderDialog(); expect(host.textContent).toContain('Move “The page being moved” to…'); expect(host.textContent).toContain("Root (top level)"); expect(buttonWithText("Folder A")).toBeTruthy(); expect(buttonWithText("Leaf")).toBeTruthy(); // p1 is the page being moved — never a valid destination. expect(host.textContent).not.toContain("The page being moved” is"); const rows = buttons().filter((b) => (b.textContent ?? "").includes("The page being moved")); expect(rows).toHaveLength(0); }); it("keeps 'Move here' disabled until a destination is picked", () => { mocks.moveOpen = true; mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" }; renderDialog(); expect(buttonWithText("Move here")?.disabled).toBe(true); }); it("moves to the root level when 'Root (top level)' is picked", () => { mocks.moveOpen = true; mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" }; renderDialog(); act(() => buttonWithText("Root (top level)")?.click()); const move = buttonWithText("Move here"); expect(move?.disabled).toBe(false); act(() => move?.click()); expect(mocks.mutate).toHaveBeenCalledTimes(1); expect(mocks.mutate.mock.calls[0]?.[0]).toEqual({ id: "p1", parentId: null }); }); it("moves under the picked page", () => { mocks.moveOpen = true; mocks.moveContext = { pageId: "p1", workspaceId: "ws1", title: "T" }; renderDialog(); act(() => buttonWithText("Folder A")?.click()); act(() => buttonWithText("Move here")?.click()); expect(mocks.mutate).toHaveBeenCalledTimes(1); expect(mocks.mutate.mock.calls[0]?.[0]).toEqual({ id: "p1", parentId: "p2" }); }); });