ai-chatbot/tests/routes/canvas-toolbar-capability.test.ts
WuYongJie 6b7b24a86e 修复图片工具提交前的画布同步与结果合并
新导入或新生成的图片在自动保存前点击去水印,会因服务端缺少节点而失败。工具栏统一先保存当前画布,保存成功后再调用处理能力;处理与自动保存共用队列,结果合并保留等待期间的本地编辑并更新保存版本。适用于去水印、抠图、增强、扩图和图层拆分。

验证:14 项画布能力与提交同步测试通过,覆盖保存失败不提交、执行期间编辑保留、上游失败和切换画布;TypeScript 检查通过。
2026-11-03 14:23:59 +08:00

82 lines
4.1 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { createCanvasSaveQueue } from "@/lib/client/canvas-save-queue";
import { executeCanvasToolbarCapability } from "@/lib/client/canvas-toolbar-capability";
import { createCanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
import { useCanvasStore } from "@/store/canvas";
const originalFetch = globalThis.fetch;
const node = (id: string) => ({ id, type: "image", position: { x: 0, y: 0 },
size: { width: 100, height: 100 }, data: { src: `https://example.com/${id}.png` } });
const command = () => createCanvasCapabilityClientCommand({
projectId: "project-a", capabilityId: "image.remove_watermark", nodeIds: ["source"], source: "toolbar",
});
test.beforeEach(() => {
useCanvasStore.getState().clearCanvas();
useCanvasStore.getState().setProject("project-a");
useCanvasStore.getState().loadCanvas({ nodes: [node("source") as any], edges: [] });
useCanvasStore.getState().setCanvasRevision("r1", "r1");
});
test.afterEach(() => { globalThis.fetch = originalFetch; });
test("waits for the selected image to be saved before submitting and keeps subsequent saves behind the result", async () => {
const queue = createCanvasSaveQueue();
const events: string[] = [];
let finishSave!: () => void;
const pendingSave = new Promise<void>(resolve => { finishSave = resolve; });
const pending = queue.enqueueOperation(async () => {
events.push("saving"); await pendingSave; events.push("saved"); return true;
}, async () => { events.push("submitted"); });
const autosave = queue.enqueue(async () => { events.push("autosaved"); });
await Promise.resolve();
expect(events).toEqual(["saving"]);
finishSave();
await Promise.all([pending, autosave]);
expect(events).toEqual(["saving", "saved", "submitted", "autosaved"]);
});
test("a failed save does not submit a paid image task or block later saves", async () => {
const queue = createCanvasSaveQueue();
let submissions = 0;
await expect(queue.enqueueOperation(async () => false, async () => { submissions++; })).rejects.toThrow("尚未保存成功");
expect(submissions).toBe(0);
expect(await queue.enqueue(async () => "saved")).toBe("saved");
});
test("merges the result with edits made while processing and advances the save revision", async () => {
globalThis.fetch = (async (url) => {
if (String(url).endsWith("/capabilities")) {
useCanvasStore.getState().moveNode("source", { x: 50, y: 80 });
return Response.json({ success: true });
}
return Response.json({ success: true, data: {
updatedAt: "r2", canvasData: { nodes: [node("source"), node("result")], edges: [] },
} });
}) as typeof fetch;
await executeCanvasToolbarCapability(command());
const state = useCanvasStore.getState();
expect(state.nodes.map(n => n.id)).toEqual(["source", "result"]);
expect(state.nodes[0].position).toEqual({ x: 50, y: 80 });
expect(state.canvasRevision).toBe("r2");
});
test("a provider failure is shown without reloading or changing the canvas", async () => {
globalThis.fetch = (async () => Response.json({ success: false, error: "去水印服务繁忙" }, { status: 503 })) as typeof fetch;
await expect(executeCanvasToolbarCapability(command())).rejects.toThrow("去水印服务繁忙");
expect(useCanvasStore.getState().nodes.map(n => n.id)).toEqual(["source"]);
expect(useCanvasStore.getState().canvasRevision).toBe("r1");
});
test("switching projects during processing does not load the old result into the new project", async () => {
globalThis.fetch = (async (url) => {
if (String(url).endsWith("/capabilities")) {
useCanvasStore.getState().setProject("project-b");
useCanvasStore.getState().loadCanvas({ nodes: [node("other") as any], edges: [] });
return Response.json({ success: true });
}
return Response.json({ success: true, data: { updatedAt: "r2", canvasData: { nodes: [node("result")], edges: [] } } });
}) as typeof fetch;
await executeCanvasToolbarCapability(command());
expect(useCanvasStore.getState().nodes.map(n => n.id)).toEqual(["other"]);
expect(useCanvasStore.getState().canvasRevision).toBe("r1");
});