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

This commit is contained in:
Alex Grozav 2025-01-31 15:08:31 +02:00 committed by GitHub
parent 2774f35969
commit c7a15d5980
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 203 additions and 2 deletions

View file

@ -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() {

View file

@ -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,