



























@@ -0,0 +1,137 @@
1+import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2+import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
3+import { beforeEach, describe, expect, it, vi } from "vitest";
4+import canvasPlugin from "./index.js";
5+6+const mocks = vi.hoisted(() => {
7+const httpHandler = {
8+handleHttpRequest: vi.fn(async () => true),
9+handleUpgrade: vi.fn(async () => true),
10+close: vi.fn(async () => {}),
11+};
12+const toolExecute = vi.fn(async () => ({ content: [{ type: "text", text: "ok" }] }));
13+return {
14+ httpHandler,
15+createCanvasHttpRouteHandler: vi.fn(() => httpHandler),
16+resolveCanvasHttpPathToLocalPath: vi.fn(() => "/tmp/canvas-asset"),
17+createDefaultCanvasCliDependencies: vi.fn(() => ({ deps: true })),
18+registerNodesCanvasCommands: vi.fn(),
19+ toolExecute,
20+createCanvasTool: vi.fn(() => ({
21+label: "Canvas",
22+name: "canvas",
23+description: "Canvas",
24+parameters: {},
25+execute: toolExecute,
26+})),
27+};
28+});
29+30+vi.mock("./src/http-route.js", () => ({
31+createCanvasHttpRouteHandler: mocks.createCanvasHttpRouteHandler,
32+}));
33+34+vi.mock("./src/documents.js", () => ({
35+resolveCanvasHttpPathToLocalPath: mocks.resolveCanvasHttpPathToLocalPath,
36+}));
37+38+vi.mock("./src/cli.js", () => ({
39+createDefaultCanvasCliDependencies: mocks.createDefaultCanvasCliDependencies,
40+registerNodesCanvasCommands: mocks.registerNodesCanvasCommands,
41+}));
42+43+vi.mock("./src/tool.js", () => ({
44+createCanvasTool: mocks.createCanvasTool,
45+}));
46+47+function registerCanvas() {
48+const routes: Array<Parameters<OpenClawPluginApi["registerHttpRoute"]>[0]> = [];
49+const services: Array<Parameters<OpenClawPluginApi["registerService"]>[0]> = [];
50+const resolvers: Array<Parameters<OpenClawPluginApi["registerHostedMediaResolver"]>[0]> = [];
51+const tools: Array<Parameters<OpenClawPluginApi["registerTool"]>[0]> = [];
52+const cliFeatures: Array<{
53+registrar: Parameters<OpenClawPluginApi["registerNodeCliFeature"]>[0];
54+opts: Parameters<OpenClawPluginApi["registerNodeCliFeature"]>[1];
55+}> = [];
56+canvasPlugin.register?.(
57+createTestPluginApi({
58+id: "canvas",
59+name: "Canvas",
60+config: {},
61+registerHttpRoute: (route) => routes.push(route),
62+registerService: (service) => services.push(service),
63+registerHostedMediaResolver: (resolver) => resolvers.push(resolver),
64+registerTool: (tool) => tools.push(tool),
65+registerNodeCliFeature: (registrar, opts) => cliFeatures.push({ registrar, opts }),
66+registerNodeInvokePolicy: vi.fn(),
67+}),
68+);
69+return { routes, services, resolvers, tools, cliFeatures };
70+}
71+72+describe("Canvas plugin entry", () => {
73+beforeEach(() => {
74+vi.clearAllMocks();
75+});
76+77+it("defers Canvas host implementation until a registered route is used", async () => {
78+const { routes, services } = registerCanvas();
79+80+expect(routes).toHaveLength(3);
81+expect(services).toHaveLength(1);
82+expect(mocks.createCanvasHttpRouteHandler).not.toHaveBeenCalled();
83+84+await services[0]?.stop?.({} as never);
85+expect(mocks.createCanvasHttpRouteHandler).not.toHaveBeenCalled();
86+87+await routes[0]?.handler({ url: "/__openclaw__/canvas" } as never, {} as never);
88+expect(mocks.createCanvasHttpRouteHandler).toHaveBeenCalledTimes(1);
89+expect(mocks.httpHandler.handleHttpRequest).toHaveBeenCalledTimes(1);
90+91+await services[0]?.stop?.({} as never);
92+expect(mocks.httpHandler.close).toHaveBeenCalledTimes(1);
93+});
94+95+it("defers Canvas resolver, CLI, and tool implementations until use", async () => {
96+const { resolvers, tools, cliFeatures } = registerCanvas();
97+98+expect(resolvers).toHaveLength(1);
99+expect(tools).toHaveLength(1);
100+expect(cliFeatures).toHaveLength(1);
101+expect(mocks.resolveCanvasHttpPathToLocalPath).not.toHaveBeenCalled();
102+expect(mocks.createDefaultCanvasCliDependencies).not.toHaveBeenCalled();
103+expect(mocks.createCanvasTool).not.toHaveBeenCalled();
104+105+await expect(resolvers[0]?.("/__openclaw__/canvas/documents/id/index.html")).resolves.toBe(
106+"/tmp/canvas-asset",
107+);
108+expect(mocks.resolveCanvasHttpPathToLocalPath).toHaveBeenCalledTimes(1);
109+110+await cliFeatures[0]?.registrar({
111+program: {} as never,
112+parentPath: ["nodes"],
113+config: {},
114+workspaceDir: undefined,
115+logger: { info() {}, warn() {}, error() {}, debug() {} },
116+});
117+expect(mocks.createDefaultCanvasCliDependencies).toHaveBeenCalledTimes(1);
118+expect(mocks.registerNodesCanvasCommands).toHaveBeenCalledTimes(1);
119+120+const toolFactory = tools[0];
121+expect(typeof toolFactory).toBe("function");
122+const tool = (toolFactory as Exclude<typeof toolFactory, AnyAgentTool>)({
123+config: {},
124+workspaceDir: "/tmp/workspace",
125+});
126+expect(Array.isArray(tool)).toBe(false);
127+expect((tool as AnyAgentTool).name).toBe("canvas");
128+expect(mocks.createCanvasTool).not.toHaveBeenCalled();
129+130+await (tool as AnyAgentTool).execute("tool-call", { action: "hide" });
131+expect(mocks.createCanvasTool).toHaveBeenCalledWith({
132+config: {},
133+workspaceDir: "/tmp/workspace",
134+});
135+expect(mocks.toolExecute).toHaveBeenCalledWith("tool-call", { action: "hide" });
136+});
137+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。