From f2f22b66a709e509be6c86ad9317d2b3e6fc6e89 Mon Sep 17 00:00:00 2001 From: Matthias Meister Date: Fri, 3 Apr 2026 20:51:21 +0200 Subject: [PATCH] test(canvas): expand reconciliation helper coverage --- ...canvas-flow-reconciliation-helpers.test.ts | 193 ++++++++++++++++++ vitest.config.ts | 2 +- 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts b/components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts index 5d86e6c..dbddd99 100644 --- a/components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts +++ b/components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts @@ -8,6 +8,7 @@ import { } from "@/components/canvas/canvas-flow-reconciliation-helpers"; const asNodeId = (id: string): Id<"nodes"> => id as Id<"nodes">; +const asEdgeId = (id: string) => id as Id<"edges">; describe("canvas flow reconciliation helpers", () => { it("carries an optimistic edge while a pending connection create awaits convex edge sync", () => { @@ -81,6 +82,146 @@ describe("canvas flow reconciliation helpers", () => { ]); }); + it("suppresses carried optimistic edges when convex already has the same connection signature", () => { + const result = reconcileCanvasFlowEdges({ + previousEdges: [ + { + id: "optimistic_edge_req-3", + source: "optimistic_req-3", + target: "node-target", + }, + ], + convexEdges: [ + { + _id: asEdgeId("edge-1"), + sourceNodeId: asNodeId("node-real"), + targetNodeId: asNodeId("node-target"), + sourceHandle: undefined, + targetHandle: undefined, + }, + ], + convexNodes: [ + { _id: asNodeId("node-real"), type: "image" }, + { _id: asNodeId("node-target"), type: "prompt" }, + ], + previousConvexNodeIdsSnapshot: new Set(["node-real", "node-target"]), + pendingRemovedEdgeIds: new Set(), + pendingConnectionCreateIds: new Set(), + resolvedRealIdByClientRequest: new Map([["req-3", asNodeId("node-real")]]), + localNodeIds: new Set(), + isAnyNodeDragging: false, + colorMode: "light", + }); + + expect(result.edges).toHaveLength(1); + expect(result.edges[0]).toMatchObject({ + id: "edge-1", + source: "node-real", + target: "node-target", + }); + }); + + it("preserves temp edges while filtering pending removed convex edges", () => { + const result = reconcileCanvasFlowEdges({ + previousEdges: [ + { + id: "temp-edge-1", + source: "node-a", + target: "node-b", + className: "temp", + }, + ], + convexEdges: [ + { + _id: asEdgeId("edge-removed"), + sourceNodeId: asNodeId("node-a"), + targetNodeId: asNodeId("node-b"), + sourceHandle: undefined, + targetHandle: undefined, + }, + ], + convexNodes: [ + { _id: asNodeId("node-a"), type: "image" }, + { _id: asNodeId("node-b"), type: "prompt" }, + ], + previousConvexNodeIdsSnapshot: new Set(["node-a", "node-b"]), + pendingRemovedEdgeIds: new Set(["edge-removed"]), + pendingConnectionCreateIds: new Set(), + resolvedRealIdByClientRequest: new Map(), + localNodeIds: new Set(), + isAnyNodeDragging: false, + colorMode: "light", + }); + + expect(result.edges).toEqual([ + { + id: "temp-edge-1", + source: "node-a", + target: "node-b", + className: "temp", + }, + ]); + }); + + it("keeps optimistic endpoints for local dragging nodes and still carries the edge", () => { + const result = reconcileCanvasFlowEdges({ + previousEdges: [ + { + id: "optimistic_edge_req-drag", + source: "node-source", + target: "optimistic_req-drag", + }, + ], + convexEdges: [], + convexNodes: [{ _id: asNodeId("node-source"), type: "image" }], + previousConvexNodeIdsSnapshot: new Set(["node-source"]), + pendingRemovedEdgeIds: new Set(), + pendingConnectionCreateIds: new Set(["req-drag"]), + resolvedRealIdByClientRequest: new Map([["req-drag", asNodeId("node-real")]]), + localNodeIds: new Set(["optimistic_req-drag"]), + isAnyNodeDragging: true, + colorMode: "light", + }); + + expect(result.edges).toEqual([ + { + id: "optimistic_edge_req-drag", + source: "node-source", + target: "optimistic_req-drag", + }, + ]); + }); + + it("reports settled pending connection creates once convex has the real node and edge", () => { + const result = reconcileCanvasFlowEdges({ + previousEdges: [], + convexEdges: [ + { + _id: asEdgeId("edge-settled"), + sourceNodeId: asNodeId("node-source"), + targetNodeId: asNodeId("node-real"), + sourceHandle: undefined, + targetHandle: undefined, + }, + ], + convexNodes: [ + { _id: asNodeId("node-source"), type: "image" }, + { _id: asNodeId("node-real"), type: "prompt" }, + ], + previousConvexNodeIdsSnapshot: new Set(["node-source", "node-real"]), + pendingRemovedEdgeIds: new Set(), + pendingConnectionCreateIds: new Set(["req-settled"]), + resolvedRealIdByClientRequest: new Map([ + ["req-settled", asNodeId("node-real")], + ]), + localNodeIds: new Set(), + isAnyNodeDragging: false, + colorMode: "light", + }); + + expect(result.settledPendingConnectionCreateIds).toEqual(["req-settled"]); + }); + it("cleans up matched local position pins once convex catches up", () => { const previousNodes: RFNode[] = [ { @@ -114,4 +255,56 @@ describe("canvas flow reconciliation helpers", () => { expect(result.nodes).toEqual(incomingNodes); expect(result.nextPendingLocalPositionPins.size).toBe(0); }); + + it("filters deleting nodes from incoming reconciliation results", () => { + const result = reconcileCanvasFlowNodes({ + previousNodes: [ + { + id: "node-keep", + type: "image", + position: { x: 0, y: 0 }, + data: {}, + }, + { + id: "node-delete", + type: "image", + position: { x: 40, y: 40 }, + data: {}, + }, + ], + incomingNodes: [ + { + id: "node-keep", + type: "image", + position: { x: 0, y: 0 }, + data: {}, + }, + { + id: "node-delete", + type: "image", + position: { x: 40, y: 40 }, + data: {}, + }, + ], + convexNodes: [ + { _id: asNodeId("node-keep"), type: "image" }, + { _id: asNodeId("node-delete"), type: "image" }, + ], + deletingNodeIds: new Set(["node-delete"]), + resolvedRealIdByClientRequest: new Map(), + pendingConnectionCreateIds: new Set(), + preferLocalPositionNodeIds: new Set(), + pendingLocalPositionPins: new Map(), + pendingMovePins: new Map(), + }); + + expect(result.nodes).toEqual([ + { + id: "node-keep", + type: "image", + position: { x: 0, y: 0 }, + data: {}, + }, + ]); + }); }); diff --git a/vitest.config.ts b/vitest.config.ts index a266a02..242fccb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ environment: "node", include: [ "tests/**/*.test.ts", - "components/**/__tests__/*.test.ts", + "components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts", ], }, });