mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
fix(editor): Use correct connection index when connecting adjancent nodes after deleting a node (#12973)
Some checks failed
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Has been cancelled
Some checks failed
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Has been cancelled
This commit is contained in:
parent
2774f35969
commit
c7a15d5980
|
@ -2624,6 +2624,204 @@ describe('useCanvasOperations', () => {
|
|||
expect(workflowsStore.setWorkflowPinData).toHaveBeenCalledWith({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('connectAdjacentNodes', () => {
|
||||
it('should connect nodes that were connected through the removed node', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
const historyStore = mockedStore(useHistoryStore);
|
||||
|
||||
// Create three nodes in a sequence: A -> B -> C
|
||||
const nodeA = createTestNode({ id: 'A', name: 'Node A', position: [0, 0] });
|
||||
const nodeB = createTestNode({ id: 'B', name: 'Node B', position: [100, 0] });
|
||||
const nodeC = createTestNode({ id: 'C', name: 'Node C', position: [200, 0] });
|
||||
|
||||
const nodeTypeDescription = mockNodeTypeDescription({
|
||||
name: nodeA.type,
|
||||
inputs: [NodeConnectionType.Main],
|
||||
outputs: [NodeConnectionType.Main],
|
||||
});
|
||||
|
||||
nodeTypesStore.getNodeType = vi.fn(() => nodeTypeDescription);
|
||||
|
||||
// Set up the workflow connections A -> B -> C
|
||||
workflowsStore.workflow.nodes = [nodeA, nodeB, nodeC];
|
||||
workflowsStore.workflow.connections = {
|
||||
[nodeA.name]: {
|
||||
main: [[{ node: nodeB.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
},
|
||||
[nodeB.name]: {
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
},
|
||||
};
|
||||
|
||||
// Mock store methods
|
||||
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
|
||||
workflowsStore.getNodeById.mockImplementation(
|
||||
(id: string) =>
|
||||
({
|
||||
[nodeA.id]: nodeA,
|
||||
[nodeB.id]: nodeB,
|
||||
[nodeC.id]: nodeC,
|
||||
})[id],
|
||||
);
|
||||
workflowsStore.getNodeByName.mockImplementation(
|
||||
(name: string) =>
|
||||
({
|
||||
[nodeA.name]: nodeA,
|
||||
[nodeB.name]: nodeB,
|
||||
[nodeC.name]: nodeC,
|
||||
})[name],
|
||||
);
|
||||
workflowsStore.outgoingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
});
|
||||
workflowsStore.incomingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeA.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
});
|
||||
|
||||
const { connectAdjacentNodes } = useCanvasOperations({ router });
|
||||
connectAdjacentNodes(nodeB.id, { trackHistory: true });
|
||||
|
||||
// Check that A was connected directly to C
|
||||
expect(workflowsStore.addConnection).toHaveBeenCalledWith({
|
||||
connection: [
|
||||
{ node: nodeA.name, type: NodeConnectionType.Main, index: 0 },
|
||||
{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 },
|
||||
],
|
||||
});
|
||||
|
||||
// Verify the connection was tracked in history
|
||||
expect(historyStore.pushCommandToUndo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should connect nodes that were connected through the removed node at different indices', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
const historyStore = mockedStore(useHistoryStore);
|
||||
|
||||
// Create three nodes in a sequence: A -> B -> C
|
||||
const nodeA = createTestNode({ id: 'A', name: 'Node A', position: [0, 0] });
|
||||
const nodeB = createTestNode({ id: 'B', name: 'Node B', position: [100, 0] });
|
||||
const nodeC = createTestNode({ id: 'C', name: 'Node C', position: [200, 0] });
|
||||
|
||||
const nodeTypeDescription = mockNodeTypeDescription({
|
||||
name: nodeA.type,
|
||||
inputs: [NodeConnectionType.Main, NodeConnectionType.Main],
|
||||
outputs: [NodeConnectionType.Main, NodeConnectionType.Main],
|
||||
});
|
||||
|
||||
nodeTypesStore.getNodeType = vi.fn(() => nodeTypeDescription);
|
||||
|
||||
// Set up the workflow connections A -> B -> C
|
||||
workflowsStore.workflow.nodes = [nodeA, nodeB, nodeC];
|
||||
workflowsStore.workflow.connections = {
|
||||
[nodeA.name]: {
|
||||
main: [[{ node: nodeB.name, type: NodeConnectionType.Main, index: 1 }]],
|
||||
},
|
||||
[nodeB.name]: {
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
},
|
||||
};
|
||||
|
||||
// Mock store methods
|
||||
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
|
||||
workflowsStore.getNodeById.mockImplementation(
|
||||
(id: string) =>
|
||||
({
|
||||
[nodeA.id]: nodeA,
|
||||
[nodeB.id]: nodeB,
|
||||
[nodeC.id]: nodeC,
|
||||
})[id],
|
||||
);
|
||||
workflowsStore.getNodeByName.mockImplementation(
|
||||
(name: string) =>
|
||||
({
|
||||
[nodeA.name]: nodeA,
|
||||
[nodeB.name]: nodeB,
|
||||
[nodeC.name]: nodeC,
|
||||
})[name],
|
||||
);
|
||||
workflowsStore.outgoingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 1 }]],
|
||||
});
|
||||
workflowsStore.incomingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeA.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
});
|
||||
|
||||
const { connectAdjacentNodes } = useCanvasOperations({ router });
|
||||
connectAdjacentNodes(nodeB.id, { trackHistory: true });
|
||||
|
||||
// Check that A was connected directly to C
|
||||
expect(workflowsStore.addConnection).toHaveBeenCalledWith({
|
||||
connection: [
|
||||
{ node: nodeA.name, type: NodeConnectionType.Main, index: 0 },
|
||||
{ node: nodeC.name, type: NodeConnectionType.Main, index: 1 },
|
||||
],
|
||||
});
|
||||
|
||||
// Verify the connection was tracked in history
|
||||
expect(historyStore.pushCommandToUndo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not create connections if middle node has no incoming connections', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
|
||||
// Create nodes: B -> C (no incoming to B)
|
||||
const nodeB = createTestNode({ id: 'B', name: 'Node B', position: [100, 0] });
|
||||
const nodeC = createTestNode({ id: 'C', name: 'Node C', position: [200, 0] });
|
||||
|
||||
workflowsStore.workflow.nodes = [nodeB, nodeC];
|
||||
workflowsStore.workflow.connections = {
|
||||
[nodeB.name]: {
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
},
|
||||
};
|
||||
|
||||
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
|
||||
workflowsStore.getNodeById.mockReturnValue(nodeB);
|
||||
workflowsStore.outgoingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeC.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
});
|
||||
workflowsStore.incomingConnectionsByNodeName.mockReturnValue({});
|
||||
|
||||
const { connectAdjacentNodes } = useCanvasOperations({ router });
|
||||
connectAdjacentNodes(nodeB.id);
|
||||
|
||||
expect(workflowsStore.addConnection).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not create connections if middle node has no outgoing connections', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
|
||||
// Create nodes: A -> B (no outgoing from B)
|
||||
const nodeA = createTestNode({ id: 'A', name: 'Node A', position: [0, 0] });
|
||||
const nodeB = createTestNode({ id: 'B', name: 'Node B', position: [100, 0] });
|
||||
|
||||
workflowsStore.workflow.nodes = [nodeA, nodeB];
|
||||
workflowsStore.workflow.connections = {
|
||||
[nodeA.name]: {
|
||||
main: [[{ node: nodeB.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
},
|
||||
};
|
||||
|
||||
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
|
||||
workflowsStore.getNodeById.mockReturnValue(nodeB);
|
||||
workflowsStore.outgoingConnectionsByNodeName.mockReturnValue({});
|
||||
workflowsStore.incomingConnectionsByNodeName.mockReturnValue({
|
||||
main: [[{ node: nodeA.name, type: NodeConnectionType.Main, index: 0 }]],
|
||||
});
|
||||
|
||||
const { connectAdjacentNodes } = useCanvasOperations({ router });
|
||||
connectAdjacentNodes(nodeB.id);
|
||||
|
||||
expect(workflowsStore.addConnection).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildImportNodes() {
|
||||
|
|
|
@ -281,12 +281,12 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
{
|
||||
node: incomingConnection.node,
|
||||
type,
|
||||
index: 0,
|
||||
index: incomingConnection.index,
|
||||
},
|
||||
{
|
||||
node: outgoingConnection.node,
|
||||
type,
|
||||
index: 0,
|
||||
index: outgoingConnection.index,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
@ -297,11 +297,13 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
sourceHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Output,
|
||||
type,
|
||||
index: incomingConnection.index,
|
||||
}),
|
||||
target: outgoingNodeId,
|
||||
targetHandle: createCanvasConnectionHandleString({
|
||||
mode: CanvasConnectionMode.Input,
|
||||
type,
|
||||
index: outgoingConnection.index,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -1972,6 +1974,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
revalidateNodeOutputConnections,
|
||||
isConnectionAllowed,
|
||||
filterConnectionsByNodes,
|
||||
connectAdjacentNodes,
|
||||
importWorkflowData,
|
||||
fetchWorkflowDataFromUrl,
|
||||
resetWorkspace,
|
||||
|
|
Loading…
Reference in a new issue