test: cover confirmed canvas agent moves

This commit is contained in:
134 2026-06-23 19:28:51 +08:00
parent 6d66d126c8
commit 733d6338c2

View File

@ -1004,6 +1004,138 @@ test.describe("canvas agent operation mutation contract", () => {
);
});
test("confirms move-node mutations and keeps retries from moving nodes again", () => {
const first = buildImageNode("image-move-a");
first.position = { x: 300, y: 100 };
const second = buildImageNode("image-move-b");
second.position = { x: 100, y: 100 };
const unrelated = buildImageNode("image-unrelated");
unrelated.position = { x: 900, y: 500 };
const canvasData = getCanvasData([first, second, unrelated]);
const plan: CanvasOperationPlan = {
version: 1,
operationId: "op-move-nodes",
operation: "move_nodes",
projectId,
targetNodeIds: ["image-move-a", "image-move-b"],
targetPosition: { x: 10, y: 20 },
layoutStrategy: "row",
spacing: 30,
metadata: {
messageId: "assistant-message-move",
chatId: "chat-1",
},
};
expect(() =>
applyCanvasOperationPlanToCanvasData({
canvasData,
plan,
})
).toThrow(/需要用户确认/);
const moved = applyCanvasOperationPlanToCanvasData({
canvasData,
plan: {
...plan,
confirmed: true,
metadata: {
...plan.metadata,
confirmedByUser: true,
},
},
});
const movedNodes = getResultNodes(moved.canvasData);
const movedFirst = movedNodes.find((node) => node.id === "image-move-a");
const movedSecond = movedNodes.find((node) => node.id === "image-move-b");
const movedUnrelated = movedNodes.find((node) => node.id === "image-unrelated");
expect(moved.createdNodeIds).toEqual([]);
expect(moved.updatedNodeIds).toEqual(["image-move-b", "image-move-a"]);
expect(movedSecond).toMatchObject({
position: { x: 10, y: 20 },
data: {
metadata: {
movedByAgent: true,
canvasAgentOperationId: "op-move-nodes",
canvasAgentOperation: "move_nodes",
canvasAgentLayoutStrategy: "row",
previousPosition: { x: 100, y: 100 },
},
},
});
expect(movedFirst).toMatchObject({
position: { x: 360, y: 20 },
data: {
metadata: {
movedByAgent: true,
previousPosition: { x: 300, y: 100 },
},
},
});
expect(movedUnrelated?.position).toEqual({ x: 900, y: 500 });
expect((moved.canvasData as { selectedNodeIds?: string[] }).selectedNodeIds).toEqual([
"image-move-b",
"image-move-a",
]);
const retry = applyCanvasOperationPlanToCanvasData({
canvasData: moved.canvasData,
plan: {
...plan,
confirmed: true,
metadata: {
...plan.metadata,
confirmedByUser: true,
},
},
});
const retryNodes = getResultNodes(retry.canvasData);
expect(new Set(retry.updatedNodeIds)).toEqual(
new Set(["image-move-a", "image-move-b"])
);
expect(retryNodes.find((node) => node.id === "image-move-b")?.position).toEqual({
x: 10,
y: 20,
});
expect(retryNodes.find((node) => node.id === "image-move-a")?.position).toEqual({
x: 360,
y: 20,
});
});
test("rejects confirmed stale move-node mutations", () => {
const target = buildImageNode("image-stale-move");
let caughtError: unknown;
try {
applyCanvasOperationPlanToCanvasData({
canvasData: getCanvasData([target]),
baseCanvasWasStale: true,
plan: {
version: 1,
operationId: "op-stale-move-node",
operation: "move_nodes",
projectId,
targetNodeIds: ["image-stale-move"],
targetPosition: { x: 10, y: 20 },
confirmed: true,
metadata: {
confirmedByUser: true,
},
},
});
} catch (error) {
caughtError = error;
}
expect(caughtError).toBeInstanceOf(CanvasAgentMutationError);
expect((caughtError as CanvasAgentMutationError).code).toBe(
"STALE_CANVAS_REVISION"
);
});
test("confirms delete-node mutations, removes connected edges, and keeps retries idempotent", () => {
const source = buildImageNode("image-source");
const target = buildImageNode("image-delete");