From ffd7f389b8b5c9d8a420b7ff6aa5a1e6eb5bf55b Mon Sep 17 00:00:00 2001 From: Matthias Meister Date: Fri, 3 Apr 2026 22:09:17 +0200 Subject: [PATCH] test(canvas): cover flow resize lock Add hook-level regression coverage proving useCanvasFlowReconciliation preserves local node state while the shared resize lock is active. The test harness now supports driving the resize ref the same way production interactions do. --- .../use-canvas-flow-reconciliation.test.ts | 109 +++++++++++++++++- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/components/canvas/__tests__/use-canvas-flow-reconciliation.test.ts b/components/canvas/__tests__/use-canvas-flow-reconciliation.test.ts index a642413..91ddaf7 100644 --- a/components/canvas/__tests__/use-canvas-flow-reconciliation.test.ts +++ b/components/canvas/__tests__/use-canvas-flow-reconciliation.test.ts @@ -39,6 +39,7 @@ type HarnessProps = { previousConvexNodeIdsSnapshot: Set; pendingLocalPositionPins?: Map; preferLocalPositionNodeIds?: Set; + isResizingRefOverride?: { current: boolean }; }; const latestStateRef: { @@ -81,7 +82,8 @@ function HookHarness(props: HarnessProps) { props.preferLocalPositionNodeIds ?? new Set(), ); const isDraggingRef = useRef(props.isDragging); - const isResizingRef = useRef(props.isResizing); + const internalIsResizingRef = useRef(props.isResizing); + const isResizingRef = props.isResizingRefOverride ?? internalIsResizingRef; useEffect(() => { nodesRef.current = nodes; @@ -93,8 +95,8 @@ function HookHarness(props: HarnessProps) { useEffect(() => { isDraggingRef.current = props.isDragging; - isResizingRef.current = props.isResizing; - }, [props.isDragging, props.isResizing]); + internalIsResizingRef.current = props.isResizing; + }, [props.isDragging, props.isResizing, internalIsResizingRef]); useCanvasFlowReconciliation({ convexNodes: props.convexNodes, @@ -286,4 +288,105 @@ describe("useCanvasFlowReconciliation", () => { }, ]); }); + + it("keeps local nodes unchanged while resize-lock is active", async () => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + const sharedIsResizingRef = { current: false }; + + await act(async () => { + root?.render( + React.createElement(HookHarness, { + initialNodes: [ + { + id: "node-1", + type: "image", + position: { x: 320, y: 180 }, + width: 640, + height: 360, + data: { label: "local" }, + }, + ], + initialEdges: [], + convexNodes: [ + { + _id: asNodeId("node-1"), + _creationTime: 1, + canvasId: asCanvasId("canvas-1"), + type: "image", + positionX: 320, + positionY: 180, + width: 640, + height: 360, + data: { label: "local" }, + } as Doc<"nodes">, + ], + convexEdges: [] as Doc<"edges">[], + storageUrlsById: {}, + themeMode: "light", + isDragging: false, + isResizing: false, + isResizingRefOverride: sharedIsResizingRef, + resolvedRealIdByClientRequest: new Map>(), + pendingConnectionCreateIds: new Set(), + previousConvexNodeIdsSnapshot: new Set(["node-1"]), + }), + ); + }); + + const nodesBeforeResize = latestStateRef.current?.nodes; + + sharedIsResizingRef.current = true; + + await act(async () => { + root?.render( + React.createElement(HookHarness, { + initialNodes: [ + { + id: "node-1", + type: "image", + position: { x: 320, y: 180 }, + width: 640, + height: 360, + data: { label: "local" }, + }, + ], + initialEdges: [], + convexNodes: [ + { + _id: asNodeId("node-1"), + _creationTime: 1, + canvasId: asCanvasId("canvas-1"), + type: "image", + positionX: 20, + positionY: 40, + width: 280, + height: 200, + data: { label: "server" }, + } as Doc<"nodes">, + ], + convexEdges: [] as Doc<"edges">[], + storageUrlsById: {}, + themeMode: "light", + isDragging: false, + isResizing: true, + isResizingRefOverride: sharedIsResizingRef, + resolvedRealIdByClientRequest: new Map>(), + pendingConnectionCreateIds: new Set(), + previousConvexNodeIdsSnapshot: new Set(["node-1"]), + }), + ); + }); + + expect(latestStateRef.current?.nodes).toBe(nodesBeforeResize); + expect(latestStateRef.current?.nodes[0]).toMatchObject({ + id: "node-1", + type: "image", + position: { x: 320, y: 180 }, + width: 640, + height: 360, + data: { label: "local" }, + }); + }); });