From c4ea7578fe3dddc8d480f3abb8cb5f2d064120c0 Mon Sep 17 00:00:00 2001 From: Daria Date: Tue, 27 May 2025 13:43:30 +0300 Subject: [PATCH] fix(editor): Delete all connections of nodes with multiple ones when removed from canvas (#15713) --- .../composables/useCanvasOperations.test.ts | 76 +++++++++++++++++++ .../src/composables/useCanvasOperations.ts | 5 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts b/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts index 1ea3f82948..fe954b6c77 100644 --- a/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts +++ b/packages/frontend/editor-ui/src/composables/useCanvasOperations.test.ts @@ -2419,6 +2419,82 @@ describe('useCanvasOperations', () => { expect(workflowsStore.removeConnection).not.toHaveBeenCalled(); }); + + it('should delete all connections of a node with multiple connections', () => { + const workflowsStore = mockedStore(useWorkflowsStore); + const { deleteConnectionsByNodeId } = useCanvasOperations({ router }); + + const sourceNode = createTestNode({ id: 'source', name: 'Source Node' }); + const targetNode = createTestNode({ id: 'target', name: 'Target Node' }); + + workflowsStore.workflow.nodes = [sourceNode, targetNode]; + workflowsStore.workflow.connections = { + [sourceNode.name]: { + [NodeConnectionTypes.Main]: [ + [ + { node: targetNode.name, type: NodeConnectionTypes.Main, index: 0 }, + { node: targetNode.name, type: NodeConnectionTypes.Main, index: 1 }, + ], + ], + }, + [targetNode.name]: { + [NodeConnectionTypes.Main]: [], + }, + }; + + workflowsStore.getNodeById = vi.fn().mockImplementation((id) => { + if (id === sourceNode.id) return sourceNode; + if (id === targetNode.id) return targetNode; + return null; + }); + workflowsStore.getNodeByName = vi.fn().mockImplementation((name) => { + if (name === sourceNode.name) return sourceNode; + if (name === targetNode.name) return targetNode; + return null; + }); + + workflowsStore.removeConnection = vi + .fn() + .mockImplementation((data: { connection: IConnection[] }) => { + const sourceData = data.connection[0]; + const destinationData = data.connection[1]; + + const connections = + workflowsStore.workflow.connections[sourceData.node][sourceData.type][sourceData.index]; + + for (const index in connections) { + if ( + connections[+index].node === destinationData.node && + connections[+index].type === destinationData.type && + connections[+index].index === destinationData.index + ) { + connections.splice(parseInt(index, 10), 1); + } + } + }); + + deleteConnectionsByNodeId(targetNode.id); + + expect(workflowsStore.removeConnection).toHaveBeenCalledTimes(2); + + expect(workflowsStore.removeConnection).toHaveBeenCalledWith({ + connection: [ + { node: sourceNode.name, type: NodeConnectionTypes.Main, index: 0 }, + { node: targetNode.name, type: NodeConnectionTypes.Main, index: 0 }, + ], + }); + + expect(workflowsStore.removeConnection).toHaveBeenCalledWith({ + connection: [ + { node: sourceNode.name, type: NodeConnectionTypes.Main, index: 0 }, + { node: targetNode.name, type: NodeConnectionTypes.Main, index: 1 }, + ], + }); + + expect( + workflowsStore.workflow.connections[sourceNode.name][NodeConnectionTypes.Main][0], + ).toEqual([]); + }); }); describe('duplicateNodes', () => { diff --git a/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts b/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts index 0e7d649ea9..e50a76aefa 100644 --- a/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/frontend/editor-ui/src/composables/useCanvasOperations.ts @@ -109,6 +109,7 @@ import type { CanvasLayoutEvent } from './useCanvasLayout'; import { chatEventBus } from '@n8n/chat/event-buses'; import { isChatNode } from '@/components/CanvasChat/utils'; import { useLogsStore } from '@/stores/logs.store'; +import { cloneDeep } from 'lodash-es'; type AddNodeData = Partial & { type: string; @@ -1122,7 +1123,7 @@ export function useCanvasOperations({ router }: { router: ReturnType