perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突
This commit is contained in:
95
web/test/canvas-node-toolbar.test.ts
Normal file
95
web/test/canvas-node-toolbar.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { buildImageToolbarTools } from "@/components/canvas/canvas-image-toolbar-tools";
|
||||
import { resolveNodeToolbarPlacement, resolveToolbarTools, type ToolContext, type ToolbarHandlers } from "@/lib/canvas/tool-registry";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
|
||||
function createNodeContext(node: CanvasNodeData): ToolContext {
|
||||
return {
|
||||
selectedCount: 0,
|
||||
selectedNodeTypes: new Set(),
|
||||
selectedVideoCount: 0,
|
||||
canvasTool: "move",
|
||||
workspaceMode: "professional",
|
||||
isProjectLinked: false,
|
||||
canUndo: false,
|
||||
canRedo: false,
|
||||
node,
|
||||
nodeMetadata: node.metadata,
|
||||
extractingVideoFrames: false,
|
||||
extractingAudio: false,
|
||||
trimmingVideo: false,
|
||||
mergingVideos: false,
|
||||
addPanelOpen: false,
|
||||
appearancePanelOpen: false,
|
||||
settingsPanelOpen: false,
|
||||
handlers: {} as ToolbarHandlers,
|
||||
};
|
||||
}
|
||||
|
||||
describe("canvas node toolbar model", () => {
|
||||
test("preserves empty, simple and busy video states", () => {
|
||||
const ctx = createNodeContext({ id: "video", type: CanvasNodeType.Video, title: "视频", position: { x: 0, y: 0 }, width: 320, height: 180, metadata: {} });
|
||||
const emptyTools = resolveToolbarTools("node-hover", ctx, null);
|
||||
expect(emptyTools.some((tool) => tool.id === "extractAudio")).toBe(false);
|
||||
expect(resolveNodeToolbarPlacement(emptyTools.find((tool) => tool.id === "uploadVideo")!, ctx).group).toBe("primary");
|
||||
ctx.nodeMetadata = { content: "video.mp4" };
|
||||
ctx.extractingAudio = true;
|
||||
const audio = resolveToolbarTools("node-hover", ctx, null).find((tool) => tool.id === "extractAudio")!;
|
||||
expect(audio.disabled?.(ctx)).toBe(true);
|
||||
ctx.workspaceMode = "simple";
|
||||
expect(resolveToolbarTools("node-hover", ctx, null).some((tool) => tool.id === "extractAudio")).toBe(false);
|
||||
});
|
||||
|
||||
test("image menu actions remain lazy and keep the original node", () => {
|
||||
const node: CanvasNodeData = { id: "image", type: CanvasNodeType.Image, title: "图片", position: { x: 0, y: 0 }, width: 320, height: 180, metadata: { content: "image.png", freeResize: true } };
|
||||
const received: CanvasNodeData[] = [];
|
||||
const tools = buildImageToolbarTools(node, { onCrop: (target: CanvasNodeData) => received.push(target) } as never);
|
||||
expect(received).toEqual([]);
|
||||
tools.find((tool) => tool.id === "crop")!.onClick();
|
||||
expect(received).toEqual([node]);
|
||||
expect(tools.find((tool) => tool.id === "resize")?.active).toBe(false);
|
||||
});
|
||||
|
||||
test("keeps video primary actions compact and moves secondary processing into a menu", () => {
|
||||
const ctx = createNodeContext({ id: "video", type: CanvasNodeType.Video, title: "视频", position: { x: 0, y: 0 }, width: 320, height: 180, metadata: { content: "video.mp4" } });
|
||||
const tools = resolveToolbarTools("node-hover", ctx, null);
|
||||
const groups = new Map(tools.map((tool) => [tool.id, resolveNodeToolbarPlacement(tool, ctx).group]));
|
||||
|
||||
expect([...groups].filter(([, group]) => group === "primary").map(([id]) => id)).toEqual(["trimRegenerate", "subtitles"]);
|
||||
expect([...groups].filter(([, group]) => group === "process").map(([id]) => id)).toEqual(["extractFrames", "extractAudio"]);
|
||||
expect(groups.get("download")).toBe("utility");
|
||||
expect(groups.get("timeline")).toBe("workspace");
|
||||
expect(groups.get("uploadVideo")).toBe("more");
|
||||
expect(groups.get("delete")).toBe("more");
|
||||
expect(groups.get("node-lock")).toBe("more");
|
||||
});
|
||||
|
||||
test("uses the redesigned video terminology from the registry", () => {
|
||||
const ctx = createNodeContext({ id: "video", type: CanvasNodeType.Video, title: "视频", position: { x: 0, y: 0 }, width: 320, height: 180, metadata: { content: "video.mp4" } });
|
||||
const tools = new Map(resolveToolbarTools("node-hover", ctx, null).map((tool) => [tool.id, tool]));
|
||||
const displayLabel = (id: string) => {
|
||||
const value = tools.get(id)?.displayLabel;
|
||||
return typeof value === "function" ? value(ctx) : value;
|
||||
};
|
||||
|
||||
expect(displayLabel("subtitles")).toBe("字幕");
|
||||
expect(displayLabel("timeline")).toBe("进入剪辑");
|
||||
expect(displayLabel("extractFrames")).toBe("提取画面");
|
||||
expect(displayLabel("extractAudio")).toBe("提取音频");
|
||||
});
|
||||
|
||||
test("image-only tools carry presentation metadata instead of relying on component ID lists", () => {
|
||||
const node: CanvasNodeData = { id: "image", type: CanvasNodeType.Image, title: "图片", position: { x: 0, y: 0 }, width: 320, height: 180, metadata: { content: "image.png" } };
|
||||
const tools = buildImageToolbarTools(node, {} as never);
|
||||
|
||||
expect(tools.find((tool) => tool.id === "angle")?.group).toBe("viewpoint");
|
||||
expect(tools.find((tool) => tool.id === "maskEdit")?.group).toBe("primary");
|
||||
expect(tools.some((tool) => tool.id === "superResolve")).toBe(false);
|
||||
expect(tools.find((tool) => tool.id === "emotion")?.group).toBe("portrait");
|
||||
expect(tools.find((tool) => tool.id === "upscale")?.description).toContain("不是 AI 超分");
|
||||
expect(tools.find((tool) => tool.id === "crop")?.section).toBe("构图与尺寸");
|
||||
expect(tools.find((tool) => tool.id === "resize")?.active).toBe(true);
|
||||
expect(tools.find((tool) => tool.id === "copyPrompt")?.group).toBe("more");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user