Browse Source

节点导航栏

feature/composer
TianYun 3 weeks ago
parent
commit
e14e2e88dd
  1. 4
      src/components/CanvasOutlineDrawer.tsx
  2. 107
      src/components/__tests__/CanvasOutlineDrawer.test.tsx

4
src/components/CanvasOutlineDrawer.tsx

@ -250,11 +250,11 @@ export function CanvasOutlineDrawer({ open, onClose }: CanvasOutlineDrawerProps)
open={open}
onClose={onClose}
placement="left"
width={280}
size={280}
title={t("canvasOutline.title")}
classNames={{
mask: "!bg-black/20",
content: "!bg-[var(--surface-2)] !text-[var(--text-primary)]",
section: "!bg-[var(--surface-2)] !text-[var(--text-primary)]",
header: "!border-[var(--border-subtle)] !px-4 !py-3",
body: "!flex !h-full !flex-col !gap-3 !p-3",
}}

107
src/components/__tests__/CanvasOutlineDrawer.test.tsx

@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { CanvasOutlineDrawer } from "@/components/CanvasOutlineDrawer";
const translations: Record<string, string> = {
"canvasOutline.title": "Canvas outline",
"canvasOutline.group": "Group",
"canvasOutline.nodeFallback": "Node",
"canvasOutline.searchPlaceholder": "Search nodes",
"canvasOutline.empty": "No nodes on the canvas",
"canvasOutline.noSearchResults": "No matching nodes",
"canvasOutline.totalNodes": "{count} nodes",
"canvasOutline.groupNodeCount": "{count} nodes",
"canvasOutline.expandGroup": "Expand group",
"canvasOutline.collapseGroup": "Collapse group",
"node.imageInput": "Image Input",
"node.prompt": "Prompt",
};
vi.mock("@/i18n", () => ({
useI18n: () => ({
t: (key: string, values?: Record<string, string | number>) => {
let text = translations[key] ?? key;
if (values) {
for (const [name, value] of Object.entries(values)) {
text = text.replace(`{${name}}`, String(value));
}
}
return text;
},
}),
}));
const mockSelectSingleNode = vi.fn();
const mockSetNavigationTarget = vi.fn();
const mockUseWorkflowStore = vi.fn();
vi.mock("@/store/workflowStore", () => ({
useWorkflowStore: (selector: (state: unknown) => unknown) => mockUseWorkflowStore(selector),
}));
const createState = () => ({
nodes: [
{
id: "image-1",
type: "imageInput",
position: { x: 0, y: 0 },
data: { customTitle: "Hero image" },
},
{
id: "group-node-group-1",
type: "group",
position: { x: 100, y: 100 },
data: { groupId: "group-1" },
},
{
id: "prompt-1",
type: "prompt",
parentId: "group-node-group-1",
groupId: "group-1",
position: { x: 20, y: 30 },
data: {},
},
],
groups: {
"group-1": {
id: "group-1",
name: "Storyboard",
color: "neutral",
position: { x: 100, y: 100 },
size: { width: 400, height: 300 },
},
},
selectSingleNode: mockSelectSingleNode,
setNavigationTarget: mockSetNavigationTarget,
});
describe("CanvasOutlineDrawer", () => {
beforeEach(() => {
vi.clearAllMocks();
mockUseWorkflowStore.mockImplementation((selector) => selector(createState()));
});
it("renders root nodes, grouped child nodes, and navigates to clicked nodes", () => {
render(<CanvasOutlineDrawer open onClose={vi.fn()} />);
expect(screen.getByText("Hero image")).toBeInTheDocument();
expect(screen.getByText("Storyboard")).toBeInTheDocument();
expect(screen.getByText("Prompt")).toBeInTheDocument();
expect(screen.getByText("2 nodes")).toBeInTheDocument();
fireEvent.click(screen.getByTitle("Hero image"));
expect(mockSelectSingleNode).toHaveBeenCalledWith("image-1");
expect(mockSetNavigationTarget).toHaveBeenCalledWith("image-1");
});
it("navigates to the backing group node when a group row is clicked", () => {
render(<CanvasOutlineDrawer open onClose={vi.fn()} />);
fireEvent.click(screen.getByTitle("Storyboard"));
expect(mockSelectSingleNode).toHaveBeenCalledWith("group-node-group-1");
expect(mockSetNavigationTarget).toHaveBeenCalledWith("group-node-group-1");
});
});
Loading…
Cancel
Save