Files
mnote/wolai-frontend/src/components/documents/move-embed-picker-dialog.test.tsx
T

270 lines
7.7 KiB
TypeScript
Raw Normal View History

import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ReactNode } from "react";
import { MoveEmbedPickerDialog } from "./move-embed-picker-dialog";
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const sidebarData = {
activeWorkspaceId: "ws_test",
workspaces: [],
documents: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelSidebarTree: [],
trashedDocuments: [],
};
vi.mock("@tanstack/react-query", () => ({
useQuery: ({ queryKey }: { queryKey: unknown[] }) => {
const key = Array.isArray(queryKey) ? queryKey[0] : queryKey;
if (key === "move-embed-picker-sidebar") {
return {
data: sidebarData,
isLoading: false,
error: null,
};
}
return {
data: null,
isLoading: false,
error: null,
};
},
}));
2026-04-24 06:10:18 +08:00
const mockUseDocumentSearch = vi.fn(() => ({
data: null,
isLoading: false,
error: null,
}));
vi.mock("@/hooks/use-document-search", () => ({
2026-04-24 06:10:18 +08:00
useDocumentSearch: (...args: unknown[]) => mockUseDocumentSearch(...args),
}));
vi.mock("@/components/ui/dialog", () => ({
Dialog: ({ open, children }: { open: boolean; children: ReactNode }) => (open ? <div>{children}</div> : null),
DialogContent: ({ children, className }: { children: ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
DialogTitle: ({ children, className }: { children: ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
}));
vi.mock("@/components/ui/input", () => ({
Input: (props: React.InputHTMLAttributes<HTMLInputElement>) => <input {...props} />,
}));
vi.mock("@/components/ui/tabs", () => ({
Tabs: ({ children }: { children: ReactNode }) => <div>{children}</div>,
TabsList: ({ children, className }: { children: ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
TabsTrigger: ({
children,
className,
value,
}: {
children: ReactNode;
className?: string;
value: string;
}) => (
<button type="button" className={className} data-value={value}>
{children}
</button>
),
TabsContent: ({ children, className }: { children: ReactNode; className?: string }) => (
<div className={className}>{children}</div>
),
}));
describe("MoveEmbedPickerDialog", () => {
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => {
root.unmount();
});
container.remove();
vi.clearAllMocks();
2026-04-24 06:10:18 +08:00
mockUseDocumentSearch.mockReset();
mockUseDocumentSearch.mockReturnValue({
data: null,
isLoading: false,
error: null,
});
delete window.__MNOTE_RUNTIME_CONFIG__;
});
it("空查询时会使用统一 picker surface 并透传根目录选择", async () => {
const onPick = vi.fn(async () => undefined);
const onOpenChange = vi.fn();
await act(async () => {
root.render(
<MoveEmbedPickerDialog
open
onOpenChange={onOpenChange}
workspaceId="ws_test"
defaultMode="move"
allowRoot
excludeIds={["doc_hidden"]}
onPick={onPick}
/>,
);
});
const shellPickButton = container.querySelector('[data-testid="tree-picker-root"]');
expect(shellPickButton).not.toBeNull();
await act(async () => {
shellPickButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onPick).toHaveBeenCalledTimes(1);
expect(onPick).toHaveBeenCalledWith("move", null);
expect(onOpenChange).toHaveBeenCalledWith(false);
});
2026-04-24 06:10:18 +08:00
it("搜索结果也应继续复用统一 picker surface", async () => {
mockUseDocumentSearch.mockReturnValue({
data: {
results: [
{
id: "doc_target",
title: "目标页面",
matchField: "title",
},
],
},
isLoading: false,
error: null,
});
const onPick = vi.fn(async () => undefined);
const onOpenChange = vi.fn();
await act(async () => {
root.render(
<MoveEmbedPickerDialog
open
onOpenChange={onOpenChange}
workspaceId="ws_test"
defaultMode="move"
allowRoot
excludeIds={[]}
onPick={onPick}
/>,
);
});
const input = container.querySelector("input");
expect(input).not.toBeNull();
await act(async () => {
input?.dispatchEvent(new Event("input", { bubbles: true }));
Object.defineProperty(input as HTMLInputElement, "value", {
configurable: true,
value: "目标",
});
input?.dispatchEvent(new Event("change", { bubbles: true }));
});
const pickerSurface = container.querySelector('[data-testid="tree-picker-surface"]');
const pickerRows = container.querySelectorAll('[data-testid="tree-picker-row"]');
expect(pickerSurface).not.toBeNull();
expect(pickerRows).toHaveLength(1);
await act(async () => {
pickerRows[0]?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
expect(onPick).toHaveBeenCalledWith("move", "doc_target");
expect(onOpenChange).toHaveBeenCalledWith(false);
});
it("rust_family 配置下,picker 空态与结果态都应进入统一 host", async () => {
window.__MNOTE_RUNTIME_CONFIG__ = {
treeRendererFamily: "rust_family",
};
const onPick = vi.fn(async () => undefined);
const onOpenChange = vi.fn();
await act(async () => {
root.render(
<MoveEmbedPickerDialog
open
onOpenChange={onOpenChange}
workspaceId="ws_test"
defaultMode="move"
allowRoot
excludeIds={[]}
onPick={onPick}
/>,
);
});
const emptySurface = container.querySelector('[data-testid="tree-picker-surface"]');
expect(emptySurface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
expect(emptySurface?.getAttribute("data-tree-host-implementation")).toBe("mnote_web_iframe_proxy");
expect(
container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
).not.toBeNull();
expect(
container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]'),
).not.toBeNull();
mockUseDocumentSearch.mockReturnValue({
data: {
results: [
{
id: "doc_target",
title: "目标页面",
matchField: "title",
},
],
},
isLoading: false,
error: null,
});
const input = container.querySelector("input");
expect(input).not.toBeNull();
await act(async () => {
Object.defineProperty(input as HTMLInputElement, "value", {
configurable: true,
value: "目标",
});
input?.dispatchEvent(new Event("input", { bubbles: true }));
input?.dispatchEvent(new Event("change", { bubbles: true }));
});
const resultSurface = container.querySelector('[data-testid="tree-picker-surface"]');
expect(resultSurface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
expect(resultSurface?.getAttribute("data-tree-host-implementation")).toBe("mnote_web_iframe_proxy");
expect(
container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
).not.toBeNull();
expect(
container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]'),
).not.toBeNull();
});
});