diff --git a/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.test.ts b/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.test.ts index d30b7515d0db4..f3cda0a87a0b0 100644 --- a/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.test.ts +++ b/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.test.ts @@ -3,10 +3,8 @@ import type { IConnection, INodeTypeDescription, IWebhookDescription, - Workflow, INodeConnections, WorkflowExecuteMode, - INodeTypes, } from 'n8n-workflow'; import { NodeConnectionTypes, NodeHelpers, UserError, TelemetryHelpers } from 'n8n-workflow'; import type { CanvasConnection, CanvasNode } from '@/features/workflows/canvas/canvas.types'; @@ -201,6 +199,7 @@ describe('useCanvasOperations', () => { let workflowDocumentStoreInstance: ReturnType; beforeEach(() => { + vi.restoreAllMocks(); vi.clearAllMocks(); const pinia = createTestingPinia({ initialState: createInitialState() }); @@ -415,14 +414,12 @@ describe('useCanvasOperations', () => { it('should place the node at the last cancelled connection position', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); vi.spyOn(uiStore, 'lastInteractedWithNode', 'get').mockReturnValue(node); nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); uiStore.lastInteractedWithNodeHandle = 'inputs/main/0'; uiStore.lastCancelledConnectionPosition = [200, 200]; @@ -436,22 +433,21 @@ describe('useCanvasOperations', () => { it('should place the node to the right of the last interacted with node', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [112, 112], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); const { resolveNodePosition } = useCanvasOperations(); const position = resolveNodePosition({ ...node, position: undefined }, nodeTypeDescription); @@ -461,22 +457,21 @@ describe('useCanvasOperations', () => { it('should place the node below the last interacted with node if it has non-main outputs', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [96, 96], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([ { type: NodeConnectionTypes.AiTool }, @@ -519,23 +514,22 @@ describe('useCanvasOperations', () => { it('should apply custom Y offset for AI Language Model connection type', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [100, 100], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiLanguageModel}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([ { type: NodeConnectionTypes.AiLanguageModel }, @@ -556,23 +550,22 @@ describe('useCanvasOperations', () => { it('should apply custom Y offset for AI Memory connection type', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [100, 100], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiMemory}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([ { type: NodeConnectionTypes.AiMemory }, @@ -591,23 +584,22 @@ describe('useCanvasOperations', () => { it('should not apply custom offset for regular connection types', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [100, 100], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([ { type: NodeConnectionTypes.AiTool }, @@ -628,23 +620,22 @@ describe('useCanvasOperations', () => { it('should handle missing connection type gracefully', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const node = createTestNode({ id: '0' }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - uiStore.lastInteractedWithNode = createTestNode({ + const lastInteracted = createTestNode({ position: [100, 100], type: 'test', typeVersion: 1, }); + uiStore.lastInteractedWithNode = lastInteracted; // No lastInteractedWithNodeHandle set nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(node); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue( + lastInteracted as INodeUi, + ); vi.spyOn(NodeHelpers, 'getNodeOutputs').mockReturnValueOnce([ { type: NodeConnectionTypes.AiTool }, @@ -665,7 +656,6 @@ describe('useCanvasOperations', () => { it('should position HITL node vertically between main node and tool node when inserted via connection', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const mainNode = createTestNode({ @@ -688,7 +678,6 @@ describe('useCanvasOperations', () => { }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); // Mock the tool node as lastInteractedWithNode uiStore.lastInteractedWithNode = toolNode; @@ -701,10 +690,8 @@ describe('useCanvasOperations', () => { uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(hitlNode); - vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue(toolNode as INodeUi); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode as INodeUi); const { resolveNodePosition } = useCanvasOperations(); const position = resolveNodePosition( @@ -722,7 +709,6 @@ describe('useCanvasOperations', () => { it('should move tool node vertically if HITL node would be too close', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const mainNode = createTestNode({ @@ -746,7 +732,6 @@ describe('useCanvasOperations', () => { }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); uiStore.lastInteractedWithNode = toolNode; uiStore.lastInteractedWithNodeConnection = { @@ -758,10 +743,8 @@ describe('useCanvasOperations', () => { uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(hitlNode); - vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockReturnValue(toolNode as INodeUi); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode as INodeUi); const setNodePositionByIdSpy = vi.spyOn(workflowDocumentStoreInstance, 'setNodePositionById'); @@ -781,7 +764,6 @@ describe('useCanvasOperations', () => { it('should not apply HITL positioning if node is not a HITL tool type', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const mainNode = createTestNode({ @@ -805,7 +787,6 @@ describe('useCanvasOperations', () => { }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); uiStore.lastInteractedWithNode = toolNode; uiStore.lastInteractedWithNodeConnection = { @@ -817,9 +798,6 @@ describe('useCanvasOperations', () => { uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(regularNode); vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode); const { resolveNodePosition } = useCanvasOperations(); @@ -835,7 +813,6 @@ describe('useCanvasOperations', () => { it('should not apply HITL positioning if lastInteractedWithNode is not a tool type', () => { const uiStore = mockedStore(useUIStore); - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const mainNode = createTestNode({ @@ -859,7 +836,6 @@ describe('useCanvasOperations', () => { }); const nodeTypeDescription = mockNodeTypeDescription(); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); uiStore.lastInteractedWithNode = regularNode; uiStore.lastInteractedWithNodeConnection = { @@ -871,9 +847,6 @@ describe('useCanvasOperations', () => { uiStore.lastInteractedWithNodeHandle = `outputs/${NodeConnectionTypes.AiTool}/0`; nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - workflowObject.getNode = vi.fn().mockReturnValue(hitlNode); vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(mainNode); const { resolveNodePosition } = useCanvasOperations(); @@ -1152,7 +1125,6 @@ describe('useCanvasOperations', () => { describe('addNodes', () => { it('should add nodes at specified positions', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes = [ @@ -1160,8 +1132,6 @@ describe('useCanvasOperations', () => { mockNode({ name: 'Node 2', type: nodeTypeName, position: [96, 256] }), ]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1189,7 +1159,6 @@ describe('useCanvasOperations', () => { }); it('should add nodes at current position when position is not specified', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const nodeTypeName = 'type'; const nodes = [ @@ -1197,8 +1166,6 @@ describe('useCanvasOperations', () => { mockNode({ name: 'Node 2', type: nodeTypeName, position: [192, 320] }), ]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1218,7 +1185,6 @@ describe('useCanvasOperations', () => { }); it('should adjust the position of nodes with multiple inputs', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes = [ @@ -1243,14 +1209,13 @@ describe('useCanvasOperations', () => { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; - workflowsStore.workflowObject = mock({ - getParentNodesByDepth: () => - nodes.map((node) => ({ - name: node.name, - depth: 0, - indicies: [], - })), - }); + vi.mocked(workflowDocumentStoreInstance.getParentNodesByDepth).mockReturnValue( + nodes.map((node) => ({ + name: node.name, + depth: 0, + indicies: [], + })), + ); const { addNodes } = useCanvasOperations(); await addNodes(nodes, {}); @@ -1261,7 +1226,6 @@ describe('useCanvasOperations', () => { }); it('should return newly added nodes', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes = [ @@ -1269,8 +1233,6 @@ describe('useCanvasOperations', () => { mockNode({ name: 'Node 2', type: nodeTypeName, position: [96, 240] }), ]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1281,14 +1243,11 @@ describe('useCanvasOperations', () => { }); it('should mark UI state as dirty', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const uiStore = mockedStore(useUIStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes = [mockNode({ name: 'Node 1', type: nodeTypeName, position: [30, 40] })]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1300,14 +1259,11 @@ describe('useCanvasOperations', () => { }); it('should not mark UI state as dirty if keepPristine is true', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const uiStore = mockedStore(useUIStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes = [mockNode({ name: 'Node 1', type: nodeTypeName, position: [30, 40] })]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1319,7 +1275,6 @@ describe('useCanvasOperations', () => { }); it('should pass actionName to telemetry when adding nodes with actions', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeCreatorStore = mockedStore(useNodeCreatorStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'hubspot'; @@ -1335,7 +1290,6 @@ describe('useCanvasOperations', () => { }, ]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); nodeCreatorStore.onNodeAddedToCanvas = vi.fn(); nodeTypesStore.nodeTypes = { @@ -1354,7 +1308,6 @@ describe('useCanvasOperations', () => { }); it('should respect positionOffset', async () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = useNodeTypesStore(); const nodeTypeName = 'type'; const nodes: AddedNode[] = [ @@ -1362,8 +1315,6 @@ describe('useCanvasOperations', () => { { name: 'Node 2', type: nodeTypeName, positionOffset: [2 * GRID_SIZE, GRID_SIZE] }, ]; - workflowsStore.workflowObject = createTestWorkflowObject(workflowsStore.workflow); - nodeTypesStore.nodeTypes = { [nodeTypeName]: { 1: mockNodeTypeDescription({ name: nodeTypeName }) }, }; @@ -1409,10 +1360,6 @@ describe('useCanvasOperations', () => { it('should delete node and track history', () => { const workflowsStore = mockedStore(useWorkflowsStore); const historyStore = mockedStore(useHistoryStore); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); vi.mocked(workflowDocumentStoreInstance.incomingConnectionsByNodeName).mockReturnValue({}); const id = 'node1'; @@ -1438,10 +1385,6 @@ describe('useCanvasOperations', () => { it('should delete node without tracking history', () => { const workflowsStore = mockedStore(useWorkflowsStore); const historyStore = mockedStore(useHistoryStore); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); vi.mocked(workflowDocumentStoreInstance.incomingConnectionsByNodeName).mockReturnValue({}); const id = 'node1'; @@ -1518,9 +1461,6 @@ describe('useCanvasOperations', () => { }, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); vi.mocked(workflowDocumentStoreInstance.incomingConnectionsByNodeName).mockReturnValue({}); vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(nodes[1]); @@ -1595,9 +1535,6 @@ describe('useCanvasOperations', () => { }, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); vi.mocked(workflowDocumentStoreInstance.incomingConnectionsByNodeName).mockReturnValue({}); vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(nodes[1]); @@ -2030,12 +1967,22 @@ describe('useCanvasOperations', () => { [nodeTypeName]: { 1: nodeType }, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - vi.spyOn(workflowDocumentStoreInstance, 'getNodeById') - .mockReturnValueOnce(nodes[0] as INodeUi) - .mockReturnValueOnce(nodes[1] as INodeUi); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockImplementation( + (id: string) => + ({ + [nodes[0].id]: nodes[0], + [nodes[1].id]: nodes[1], + [nodes[2].id]: nodes[2], + })[id] as INodeUi | undefined, + ); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [nodes[0].name]: nodes[0], + [nodes[1].name]: nodes[1], + [nodes[2].name]: nodes[2], + })[name] as INodeUi) ?? null, + ); nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeType); const { addConnections } = useCanvasOperations(); @@ -2148,14 +2095,14 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(nodeB); nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { createConnection, editableWorkflowObject } = useCanvasOperations(); - - editableWorkflowObject.value.nodes[nodeA.name] = nodeA; - editableWorkflowObject.value.nodes[nodeB.name] = nodeB; + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [nodeA.name]: nodeA, + [nodeB.name]: nodeB, + })[name] as INodeUi) ?? null, + ); + const { createConnection } = useCanvasOperations(); createConnection(connection); @@ -2208,14 +2155,14 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(nodeB); nodeTypesStore.getNodeType = vi.fn().mockReturnValue(nodeTypeDescription); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { createConnection, editableWorkflowObject } = useCanvasOperations(); - - editableWorkflowObject.value.nodes[nodeA.name] = nodeA; - editableWorkflowObject.value.nodes[nodeB.name] = nodeB; + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [nodeA.name]: nodeA, + [nodeB.name]: nodeB, + })[name] as INodeUi) ?? null, + ); + const { createConnection } = useCanvasOperations(); createConnection(connection, { keepPristine: true }); @@ -2243,7 +2190,6 @@ describe('useCanvasOperations', () => { describe('isConnectionAllowed', () => { it('should return false if target node type does not have inputs', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2274,10 +2220,6 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2291,7 +2233,6 @@ describe('useCanvasOperations', () => { }); it('should return false if target node does not exist in the workflow', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2323,9 +2264,6 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2339,7 +2277,6 @@ describe('useCanvasOperations', () => { }); it('should return false if source node does not have connection type', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2371,14 +2308,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2391,7 +2329,6 @@ describe('useCanvasOperations', () => { }); it('should return false if target node does not have connection type', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2423,14 +2360,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2443,7 +2381,6 @@ describe('useCanvasOperations', () => { }); it('should return false if source node type is not allowed by target node input filter', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2484,14 +2421,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2504,7 +2442,6 @@ describe('useCanvasOperations', () => { }); it('should return false if target node type is not allowed by source node output filter', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2549,14 +2486,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2569,7 +2507,6 @@ describe('useCanvasOperations', () => { }); it('should return false if source node type does not have connection type index', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2610,14 +2547,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2630,7 +2568,6 @@ describe('useCanvasOperations', () => { }); it('should return false if target node type does not have connection type index', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ id: '1', @@ -2671,14 +2608,15 @@ describe('useCanvasOperations', () => { index: 1, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2691,7 +2629,6 @@ describe('useCanvasOperations', () => { }); it('should return true if all conditions including filter are met', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2733,14 +2670,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2753,7 +2691,6 @@ describe('useCanvasOperations', () => { }); it('should return true if all conditions are met and no filter is set', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2792,14 +2729,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -2814,7 +2752,6 @@ describe('useCanvasOperations', () => { it.each(['target' as const, 'source' as const])( 'should return true if source node is not installed verified node', (type) => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2853,14 +2790,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; if (type === 'source') { nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => @@ -2890,7 +2828,6 @@ describe('useCanvasOperations', () => { it.each(['target' as const, 'source' as const])( 'should return true if %s node is not installed unverified node', (type) => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2929,14 +2866,15 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - workflowsStore.cloneWorkflowObject = vi.fn().mockReturnValue(workflowObject); - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + [targetNode.name]: targetNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; - editableWorkflowObject.value.nodes[targetNode.name] = targetNode; if (type === 'source') { nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => @@ -2963,7 +2901,6 @@ describe('useCanvasOperations', () => { ); it('should return true if node connecting to itself', () => { - const workflowsStore = mockedStore(useWorkflowsStore); const nodeTypesStore = mockedStore(useNodeTypesStore); const sourceNode = mockNode({ @@ -2987,12 +2924,14 @@ describe('useCanvasOperations', () => { index: 0, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - - const { isConnectionAllowed, editableWorkflowObject } = useCanvasOperations(); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => + (({ + [sourceNode.name]: sourceNode, + })[name] as INodeUi) ?? null, + ); + const { isConnectionAllowed } = useCanvasOperations(); - editableWorkflowObject.value.nodes[sourceNode.name] = sourceNode; nodeTypesStore.getNodeType = vi.fn( (nodeTypeName: string) => ({ @@ -3196,9 +3135,6 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(targetNodeType) .mockReturnValueOnce(sourceNodeType); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeInputConnections } = useCanvasOperations(); revalidateNodeInputConnections(targetNodeId); @@ -3260,9 +3196,6 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(targetNodeType) .mockReturnValueOnce(sourceNodeType); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeInputConnections } = useCanvasOperations(); revalidateNodeInputConnections(targetNodeId); @@ -3317,9 +3250,6 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(targetNodeType) .mockReturnValueOnce(sourceNodeType); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeInputConnections } = useCanvasOperations(); revalidateNodeInputConnections(targetNodeId); @@ -3380,15 +3310,20 @@ describe('useCanvasOperations', () => { return undefined; }); + vi.spyOn(workflowDocumentStoreInstance, 'getNodeByName').mockImplementation( + (name: string) => { + if (name === sourceNode.name) return sourceNode; + if (name === targetNode.name) return targetNode; + return null; + }, + ); + nodeTypesStore.getNodeType = vi.fn().mockImplementation((type) => { if (type === AGENT_NODE_TYPE) return targetNodeType; if (type === OPEN_AI_CHAT_MODEL_NODE_TYPE) return sourceNodeType; return undefined; }); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeInputConnections } = useCanvasOperations(); revalidateNodeInputConnections(targetNodeId); @@ -3471,9 +3406,6 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(targetNodeType) .mockReturnValueOnce(sourceNodeType); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeOutputConnections } = useCanvasOperations(); revalidateNodeOutputConnections(sourceNodeId); @@ -3535,9 +3467,6 @@ describe('useCanvasOperations', () => { .mockReturnValueOnce(targetNodeType) .mockReturnValueOnce(sourceNodeType); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { revalidateNodeOutputConnections } = useCanvasOperations(); revalidateNodeOutputConnections(sourceNodeId); @@ -4311,8 +4240,6 @@ describe('useCanvasOperations', () => { }; // Mock store methods - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockImplementation( (id: string) => ({ @@ -4381,8 +4308,6 @@ describe('useCanvasOperations', () => { }; // Mock store methods - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockImplementation( (id: string) => ({ @@ -4435,8 +4360,6 @@ describe('useCanvasOperations', () => { }, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(nodeB); vi.mocked(workflowDocumentStoreInstance.outgoingConnectionsByNodeName).mockReturnValue({ main: [[{ node: nodeC.name, type: NodeConnectionTypes.Main, index: 0 }]], @@ -4463,8 +4386,6 @@ describe('useCanvasOperations', () => { }, }; - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockReturnValue(nodeB); vi.mocked(workflowDocumentStoreInstance.outgoingConnectionsByNodeName).mockReturnValue({}); vi.mocked(workflowDocumentStoreInstance.incomingConnectionsByNodeName).mockReturnValue({ @@ -4816,7 +4737,6 @@ describe('useCanvasOperations', () => { vi.mocked(workflowDocumentStoreInstance.outgoingConnectionsByNodeName).mockReturnValue({}); const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; workflowsStore.createWorkflowObject.mockReturnValue(workflowObject); const canvasOperations = useCanvasOperations(); @@ -4843,7 +4763,6 @@ describe('useCanvasOperations', () => { vi.mocked(workflowDocumentStoreInstance.outgoingConnectionsByNodeName).mockReturnValue({}); const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; workflowsStore.createWorkflowObject.mockReturnValue(workflowObject); // Mock TelemetryHelpers.generateNodesGraph to throw an error for this test @@ -4874,9 +4793,6 @@ describe('useCanvasOperations', () => { credentials: {}, })); - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - // Create nodes: A -> B (no outgoing from B) const nodeA: IWorkflowTemplateNode = createTestNode({ id: 'X', @@ -5134,7 +5050,15 @@ describe('useCanvasOperations', () => { }); it('should replace connections for a node and track history', () => { const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockImplementation((...args) => + workflowObject.getParentNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockImplementation((...args) => + workflowObject.getChildNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getConnectionsBetweenNodes).mockImplementation( + (...args) => workflowObject.getConnectionsBetweenNodes(...args), + ); const { replaceNodeConnections } = useCanvasOperations(); replaceNodeConnections(targetNode.id, replacementNode.id, { trackHistory: true }); @@ -5232,7 +5156,15 @@ describe('useCanvasOperations', () => { it('should replace connections without tracking history', () => { const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockImplementation((...args) => + workflowObject.getParentNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockImplementation((...args) => + workflowObject.getChildNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getConnectionsBetweenNodes).mockImplementation( + (...args) => workflowObject.getConnectionsBetweenNodes(...args), + ); const { replaceNodeConnections } = useCanvasOperations(); replaceNodeConnections(targetNode.id, replacementNode.id, { trackHistory: false }); @@ -5244,9 +5176,6 @@ describe('useCanvasOperations', () => { }); it('should not replace connections if previous node does not exist', () => { - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { replaceNodeConnections } = useCanvasOperations(); replaceNodeConnections('nonexistent', replacementNode.id); @@ -5255,9 +5184,6 @@ describe('useCanvasOperations', () => { }); it('should not replace connections if new node does not exist', () => { - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { replaceNodeConnections } = useCanvasOperations(); replaceNodeConnections(targetNode.id, 'nonexistent'); @@ -5266,9 +5192,6 @@ describe('useCanvasOperations', () => { }); it('should respect replaceInputs being false', () => { - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { replaceNodeConnections } = useCanvasOperations(); // nextNode only has an input connection replaceNodeConnections(nextNode.id, replacementNode.id, { @@ -5281,9 +5204,6 @@ describe('useCanvasOperations', () => { }); it('should respect replaceOutputs being false', () => { - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { replaceNodeConnections } = useCanvasOperations(); // sourceNode only has an output connection replaceNodeConnections(sourceNode.id, replacementNode.id, { @@ -5355,7 +5275,15 @@ describe('useCanvasOperations', () => { }); const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockImplementation((...args) => + workflowObject.getParentNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockImplementation((...args) => + workflowObject.getChildNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getConnectionsBetweenNodes).mockImplementation( + (...args) => workflowObject.getConnectionsBetweenNodes(...args), + ); const { replaceNodeConnections } = useCanvasOperations(); replaceNodeConnections(previousNode1.id, newNode1.id, { @@ -5494,7 +5422,15 @@ describe('useCanvasOperations', () => { }); it('should replace an existing node and track history', () => { const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockImplementation((...args) => + workflowObject.getParentNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockImplementation((...args) => + workflowObject.getChildNodes(...args), + ); + vi.mocked(workflowDocumentStoreInstance.getConnectionsBetweenNodes).mockImplementation( + (...args) => workflowObject.getConnectionsBetweenNodes(...args), + ); const { replaceNode } = useCanvasOperations(); replaceNode(targetNode.id, replacementNode.id, { trackHistory: true }); @@ -5536,9 +5472,6 @@ describe('useCanvasOperations', () => { }); }); it('should not track history if flag is false', () => { - const workflowObject = createTestWorkflowObject(workflowsStore.workflow); - workflowsStore.workflowObject = workflowObject; - const { replaceNode } = useCanvasOperations(); replaceNode(targetNode.id, replacementNode.id, { trackHistory: false }); @@ -5705,6 +5638,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); @@ -5766,6 +5704,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -5831,6 +5774,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -5900,6 +5848,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -5965,6 +5918,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -6046,6 +6004,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -6117,6 +6080,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -6201,6 +6169,11 @@ describe('useCanvasOperations', () => { workflowDocumentStoreInstance = useWorkflowDocumentStore( createWorkflowDocumentId(useWorkflowsStore().workflowId), ); + vi.mocked(workflowDocumentStoreInstance.getChildNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getParentNodes).mockReturnValue([]); + vi.mocked(workflowDocumentStoreInstance.getNodeByName).mockImplementation( + (name) => useWorkflowsStore().workflow.nodes.find((n) => n.name === name) ?? null, + ); const { getNodesToShift } = useCanvasOperations(); const insertPosition: [number, number] = [250, 100]; @@ -6279,24 +6252,6 @@ describe('useCanvasOperations', () => { }, }; - // Create mock nodeTypes for the workflow object - const mockNodeTypes = mock({ - getByNameAndVersion: vi.fn((type: string) => { - if (type === AGENT_NODE_TYPE) return { description: agentNodeTypeDescription }; - if (type === 'n8n-nodes-base.calculator') return { description: toolNodeTypeDescription }; - if (type === 'n8n-nodes-base.manualChatTriggerHitlTool') - return { description: hitlNodeTypeDescription }; - throw new Error(`Unknown node type: ${type}`); - }), - }); - - const workflowObject = createTestWorkflowObject({ - ...workflowsStore.workflow, - nodeTypes: mockNodeTypes, - }); - workflowObject.getNode = vi.fn().mockReturnValue(hitlNode); - workflowsStore.workflowObject = workflowObject; - vi.spyOn(workflowDocumentStoreInstance, 'getNodeById').mockImplementation((id: string) => { if (id === 'agent') return agentNode; if (id === 'tool') return toolNode; diff --git a/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.ts b/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.ts index b2827a34bf392..ee0f70f307c59 100644 --- a/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.ts +++ b/packages/frontend/editor-ui/src/app/composables/useCanvasOperations.ts @@ -140,6 +140,7 @@ import { useWorkflowDocumentStore, createWorkflowDocumentId, pinDataToExecutionData, + convertToWorkflowAccessors, } from '@/app/stores/workflowDocument.store'; type AddNodeData = Partial & { @@ -213,7 +214,12 @@ export function useCanvasOperations() { const preventOpeningNDV = !!localStorage.getItem('NodeView.preventOpeningNDV'); const editableWorkflow = computed(() => workflowsStore.workflow); - const editableWorkflowObject = computed(() => workflowsStore.workflowObject as Workflow); + + const editableWorkflowObject = computed(() => + workflowDocumentStore.value + ? convertToWorkflowAccessors(workflowDocumentStore.value) + : undefined, + ); const triggerNodes = computed(() => { return workflowsStore.workflowTriggerNodes; @@ -546,17 +552,21 @@ export function useCanvasOperations() { if (!previousNode || !newNode) { return; } - const workflowObject = workflowsStore.workflowObject; const inputNodeNames = replaceInputs - ? uniq(workflowObject.getParentNodes(previousNode.name, 'ALL', 1)) + ? uniq(workflowDocumentStore.value?.getParentNodes(previousNode.name, 'ALL', 1)) : []; const outputNodeNames = replaceOutputs - ? uniq(workflowObject.getChildNodes(previousNode.name, 'ALL', 1)) + ? uniq(workflowDocumentStore.value?.getChildNodes(previousNode.name, 'ALL', 1)) : []; const connectionPairs = [ - ...workflowObject.getConnectionsBetweenNodes(inputNodeNames, [previousNode.name]), - ...workflowObject.getConnectionsBetweenNodes([previousNode.name], outputNodeNames), + ...(workflowDocumentStore.value?.getConnectionsBetweenNodes(inputNodeNames, [ + previousNode.name, + ]) ?? []), + ...(workflowDocumentStore.value?.getConnectionsBetweenNodes( + [previousNode.name], + outputNodeNames, + ) ?? []), ]; if (trackHistory && trackBulk) { @@ -825,7 +835,7 @@ export function useCanvasOperations() { } function updatePositionForNodeWithMultipleInputs(node: INodeUi) { - const inputNodes = editableWorkflowObject.value.getParentNodesByDepth(node.name, 1); + const inputNodes = workflowDocumentStore.value?.getParentNodesByDepth(node.name, 1) ?? []; if (inputNodes.length > 1) { inputNodes.slice(1).forEach((inputNode, index) => { @@ -1247,7 +1257,7 @@ export function useCanvasOperations() { lastInteractedWithNode.type, lastInteractedWithNode.typeVersion, ); - const lastInteractedWithNodeObject = editableWorkflowObject.value.getNode( + const lastInteractedWithNodeObject = workflowDocumentStore.value?.getNodeByName( lastInteractedWithNode.name, ); @@ -1322,11 +1332,14 @@ export function useCanvasOperations() { } } - const lastInteractedWithNodeInputs = NodeHelpers.getNodeInputs( - editableWorkflowObject.value, - lastInteractedWithNodeObject, - lastInteractedWithNodeTypeDescription, - ); + const expression = workflowDocumentStore.value?.getExpressionHandler(); + const lastInteractedWithNodeInputs = expression + ? NodeHelpers.getNodeInputs( + { expression }, + lastInteractedWithNodeObject, + lastInteractedWithNodeTypeDescription, + ) + : []; const lastInteractedWithNodeInputTypes = NodeHelpers.getConnectionTypes( lastInteractedWithNodeInputs, ); @@ -1335,11 +1348,13 @@ export function useCanvasOperations() { lastInteractedWithNodeInputTypes || [] ).filter((input) => input !== NodeConnectionTypes.Main); - const lastInteractedWithNodeOutputs = NodeHelpers.getNodeOutputs( - editableWorkflowObject.value, - lastInteractedWithNodeObject, - lastInteractedWithNodeTypeDescription, - ); + const lastInteractedWithNodeOutputs = expression + ? NodeHelpers.getNodeOutputs( + { expression }, + lastInteractedWithNodeObject, + lastInteractedWithNodeTypeDescription, + ) + : []; const lastInteractedWithNodeOutputTypes = NodeHelpers.getConnectionTypes( lastInteractedWithNodeOutputs, ); @@ -1401,11 +1416,9 @@ export function useCanvasOperations() { // outputs here is to calculate the position, it is fine to assume // that they have no outputs and are so treated as a regular node // with only "main" outputs. - outputs = NodeHelpers.getNodeOutputs( - editableWorkflowObject.value, - node as INode, - nodeTypeDescription, - ); + outputs = expression + ? NodeHelpers.getNodeOutputs({ expression }, node as INode, nodeTypeDescription) + : []; } catch (e) {} const outputTypes = NodeHelpers.getConnectionTypes(outputs); @@ -1666,11 +1679,14 @@ export function useCanvasOperations() { // Step 2: Add all downstream connected nodes from initial candidates const candidateNames = new Set(initialCandidates.map((node) => node.name)); for (const candidate of initialCandidates) { - const downstream = workflowHelpers.getConnectedNodes( - 'downstream', - editableWorkflowObject.value, - candidate.name, - ); + const downstream = editableWorkflowObject.value + ? workflowHelpers.getConnectedNodes( + 'downstream', + editableWorkflowObject.value, + candidate.name, + ) + : []; + downstream // Filter the downstream nodes to find candidates that need to be shifted right. .filter((name) => { @@ -2182,19 +2198,20 @@ export function useCanvasOperations() { } const sourceNodeType = getNodeType(sourceNode); - const sourceWorkflowNode = editableWorkflowObject.value.getNode(sourceNode.name); + const sourceWorkflowNode = workflowDocumentStore.value?.getNodeByName(sourceNode.name); if (!sourceWorkflowNode) { return false; } let sourceNodeOutputs: Array = []; if (sourceNodeType) { - sourceNodeOutputs = - NodeHelpers.getNodeOutputs( - editableWorkflowObject.value, - sourceWorkflowNode, - sourceNodeType, - ) || []; + sourceNodeOutputs = workflowDocumentStore.value + ? NodeHelpers.getNodeOutputs( + { expression: workflowDocumentStore.value.getExpressionHandler() }, + sourceWorkflowNode, + sourceNodeType, + ) || [] + : []; } const sourceOutputsOfType = filterConnectionsByType(sourceNodeOutputs, sourceConnection.type); @@ -2215,19 +2232,20 @@ export function useCanvasOperations() { } const targetNodeType = getNodeType(targetNode); - const targetWorkflowNode = editableWorkflowObject.value.getNode(targetNode.name); + const targetWorkflowNode = workflowDocumentStore.value?.getNodeByName(targetNode.name); if (!targetWorkflowNode) { return false; } let targetNodeInputs: Array = []; if (targetNodeType) { - targetNodeInputs = - NodeHelpers.getNodeInputs( - editableWorkflowObject.value, - targetWorkflowNode, - targetNodeType, - ) || []; + targetNodeInputs = workflowDocumentStore.value + ? NodeHelpers.getNodeInputs( + { expression: workflowDocumentStore.value.getExpressionHandler() }, + targetWorkflowNode, + targetNodeType, + ) || [] + : []; } const targetInputsOfType = filterConnectionsByType(targetNodeInputs, targetConnection.type); @@ -2974,12 +2992,10 @@ export function useCanvasOperations() { return; } - const workflowObject = workflowsStore.workflowObject; // @TODO Check if we actually need workflowObject here - logsStore.toggleOpen(true); const payload = { - workflow_id: workflowObject.id, + workflow_id: workflowDocumentStore.value?.workflowId, button_type: source, }; diff --git a/packages/frontend/editor-ui/src/app/composables/usePushConnection/handlers/executionFinished.ts b/packages/frontend/editor-ui/src/app/composables/usePushConnection/handlers/executionFinished.ts index 50f33500bf7f6..030fb177a2755 100644 --- a/packages/frontend/editor-ui/src/app/composables/usePushConnection/handlers/executionFinished.ts +++ b/packages/frontend/editor-ui/src/app/composables/usePushConnection/handlers/executionFinished.ts @@ -274,7 +274,6 @@ export function handleExecutionFinishedWithWaitTill( const workflowsStore = useWorkflowsStore(); const settingsStore = useSettingsStore(); const workflowSaving = useWorkflowSaving(options); - const workflowObject = workflowsStore.workflowObject; const workflowDocumentStore = workflowId ? useWorkflowDocumentStore(createWorkflowDocumentId(workflowId)) @@ -297,7 +296,7 @@ export function handleExecutionFinishedWithWaitTill( } // Workflow did start but had been put to wait - useDocumentTitle().setDocumentTitle(workflowObject.name as string, 'IDLE'); + useDocumentTitle().setDocumentTitle(workflowDocumentStore?.name as string, 'IDLE'); } /** @@ -311,11 +310,13 @@ export function handleExecutionFinishedWithErrorOrCanceled( const i18n = useI18n(); const telemetry = useTelemetry(); const workflowsStore = useWorkflowsStore(); + const workflowDocumentStore = useWorkflowDocumentStore( + createWorkflowDocumentId(workflowsStore.workflowId), + ); const documentTitle = useDocumentTitle(); const workflowHelpers = useWorkflowHelpers(); - const workflowObject = workflowsStore.workflowObject; - documentTitle.setDocumentTitle(workflowObject.name as string, 'ERROR'); + documentTitle.setDocumentTitle(workflowDocumentStore.name as string, 'ERROR'); if ( runExecutionData.resultData.error?.name === 'ExpressionError' && @@ -342,7 +343,7 @@ export function handleExecutionFinishedWithErrorOrCanceled( error.context.nodeCause && ['paired_item_no_info', 'paired_item_invalid_info'].includes(error.context.type as string) ) { - const node = workflowObject.getNode(error.context.nodeCause as string); + const node = workflowDocumentStore.getNodeByName(error.context.nodeCause as string); if (node) { const workflowDocumentStore = workflowsStore.workflowId @@ -412,12 +413,11 @@ export function handleExecutionFinishedWithSuccessOrOther( const toast = useToast(); const i18n = useI18n(); const nodeTypesStore = useNodeTypesStore(); - const workflowObject = workflowsStore.workflowObject; - const workflowName = workflowObject.name ?? ''; const workflowDocumentStore = workflowsStore.workflowId ? useWorkflowDocumentStore(createWorkflowDocumentId(workflowsStore.workflowId)) : undefined; + const workflowName = workflowDocumentStore?.name ?? ''; useDocumentTitle().setDocumentTitle(workflowName, 'IDLE'); diff --git a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts index 6f96c3706c6c7..6f4e56d14f1c8 100644 --- a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts +++ b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts @@ -12,7 +12,6 @@ import type { ExtractableErrorResult, IConnections, INode, - Workflow, } from 'n8n-workflow'; import { computed } from 'vue'; import { useToast } from './useToast'; @@ -52,8 +51,6 @@ export function useWorkflowExtraction() { buildAdjacencyList(workflowDocumentStore?.value?.connectionsBySourceNode ?? {}), ); - const workflowObject = computed(() => workflowsStore.workflowObject as Workflow); - function showError(message: string) { toast.showMessage({ type: 'error', @@ -333,8 +330,10 @@ export function useWorkflowExtraction() { if (!node) return true; // invariant broken -> abort onto error path const nodeType = useNodeTypesStore().getNodeType(node.type, node.typeVersion); if (!nodeType) return true; // invariant broken -> abort onto error path + const expression = workflowDocumentStore?.value?.getExpressionHandler(); + if (!expression) return true; - const ios = getIOs(workflowObject.value, node, nodeType); + const ios = getIOs({ expression }, node, nodeType); return ( ios.filter((x) => (typeof x === 'string' ? x === 'main' : x.type === 'main')).length <= 1 ); @@ -462,17 +461,17 @@ export function useWorkflowExtraction() { while (subGraphNames.includes(returnNodeName)) returnNodeName += '_1'; const directAfterEndNodeNames = end - ? workflowObject.value - .getChildNodes(end, 'main', 1) - .map((x) => workflowObject.value.getNode(x)?.name) - .filter((x) => x !== undefined) + ? (workflowDocumentStore?.value + ?.getChildNodes(end, 'main', 1) + .map((x) => workflowDocumentStore?.value?.getNodeByName(x)?.name) + .filter((x) => x !== undefined) ?? []) : []; const allAfterEndNodes = end - ? workflowObject.value - .getChildNodes(end, 'ALL') - .map((x) => workflowObject.value.getNode(x)) - .filter((x) => x !== null) + ? (workflowDocumentStore?.value + ?.getChildNodes(end, 'ALL') + .map((x) => workflowDocumentStore?.value?.getNodeByName(x) ?? null) + .filter((x) => x !== null) ?? []) : []; const { nodes, variables } = extractReferencesInNodeExpressions( diff --git a/packages/frontend/editor-ui/src/app/types/workflow.ts b/packages/frontend/editor-ui/src/app/types/workflow.ts index 35904867a3163..2108e69c71013 100644 --- a/packages/frontend/editor-ui/src/app/types/workflow.ts +++ b/packages/frontend/editor-ui/src/app/types/workflow.ts @@ -5,6 +5,7 @@ import type { Workflow } from 'n8n-workflow'; * Will be removed when workflowsStore.workflowObject migration is complete. */ export interface WorkflowObjectAccessors { + id: Workflow['id']; connectionsBySourceNode: Workflow['connectionsByDestinationNode']; expression: Workflow['expression']; pinData?: Workflow['pinData']; diff --git a/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.test.ts b/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.test.ts index 1de053c2663d7..020d7322d23d1 100644 --- a/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.test.ts +++ b/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.test.ts @@ -3,10 +3,15 @@ import NDVSubConnections from './NDVSubConnections.vue'; import { setActivePinia } from 'pinia'; import { createTestingPinia } from '@pinia/testing'; import type { INodeUi } from '@/Interface'; -import type { INodeTypeDescription, WorkflowParameters } from 'n8n-workflow'; -import { NodeConnectionTypes, Workflow } from 'n8n-workflow'; -import { nextTick } from 'vue'; +import type { INodeTypeDescription } from 'n8n-workflow'; +import { NodeConnectionTypes } from 'n8n-workflow'; +import { nextTick, shallowRef } from 'vue'; import { type Mock } from 'vitest'; +import { + createWorkflowDocumentId, + injectWorkflowDocumentStore, + useWorkflowDocumentStore, +} from '@/app/stores/workflowDocument.store'; const nodeType: INodeTypeDescription = { displayName: 'OpenAI', @@ -46,20 +51,7 @@ const node: INodeUi = { position: [1300, 540], }; -const workflow: WorkflowParameters = { - nodes: [node], - connections: {}, - pinData: {}, - active: false, - nodeTypes: { - getByName: vi.fn(), - getByNameAndVersion: vi.fn(), - getKnownTypes: vi.fn(), - }, -}; - const getNodeType = vi.fn(); -let mockWorkflowData = workflow; let mockGetNodeByName: Mock<(name: string) => INodeUi | null> = vi.fn(() => node); vi.mock('@/app/stores/nodeTypes.store', () => ({ @@ -68,11 +60,9 @@ vi.mock('@/app/stores/nodeTypes.store', () => ({ })), })); -vi.mock('@/app/stores/workflows.store', () => ({ - useWorkflowsStore: vi.fn(() => ({ - workflowObject: new Workflow(mockWorkflowData), - getNodeByName: mockGetNodeByName, - })), +vi.mock('@/app/stores/workflowDocument.store', async (importActual) => ({ + ...(await importActual()), + injectWorkflowDocumentStore: vi.fn(), })); describe('NDVSubConnections', () => { @@ -80,6 +70,12 @@ describe('NDVSubConnections', () => { vi.useFakeTimers(); setActivePinia(createTestingPinia()); vi.restoreAllMocks(); + vi.mocked(injectWorkflowDocumentStore).mockReturnValue( + shallowRef({ + ...useWorkflowDocumentStore(createWorkflowDocumentId('wf0')), + getNodeByName: mockGetNodeByName, + }), + ); }); it('should render container if possible connections', async () => { @@ -154,22 +150,6 @@ describe('NDVSubConnections', () => { type: 'modelSelector', }; - // Mock connected nodes - const mockWorkflow = { - ...workflow, - nodes: [multiConnectionNode], - connectionsByDestinationNode: { - ModelSelector: { - [NodeConnectionTypes.AiLanguageModel]: [ - null, // Main input (index 0) - no ai_languageModel connection - [{ node: 'OpenAI1', type: NodeConnectionTypes.AiLanguageModel, index: 0 }], // Model 1 (index 1) - [{ node: 'Claude', type: NodeConnectionTypes.AiLanguageModel, index: 0 }], // Model 2 (index 2) - [], // Model 3 (index 3) - no connection - ], - }, - }, - }; - // Mock additional nodes const openAI1Node: INodeUi = { ...node, @@ -184,8 +164,6 @@ describe('NDVSubConnections', () => { getNodeType.mockReturnValue(multiConnectionNodeType); - // Update mock data for this test - mockWorkflowData = mockWorkflow; mockGetNodeByName = vi.fn((name: string) => { if (name === 'ModelSelector') return multiConnectionNode; if (name === 'OpenAI1') return openAI1Node; diff --git a/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.vue b/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.vue index 7a448d8019029..2a4278e326fcf 100644 --- a/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.vue +++ b/packages/frontend/editor-ui/src/features/ndv/panel/components/NDVSubConnections.vue @@ -1,8 +1,10 @@