fix: edge-disconnections-are-not-reflected-in-reduced-program
This commit is contained in:
committed by
JobvAlewijk
parent
8149d67491
commit
58ab95eee1
@@ -1,4 +1,7 @@
|
||||
import {act} from '@testing-library/react';
|
||||
import type {Connection, Edge, Node} from "@xyflow/react";
|
||||
import { NodeDisconnections } from "../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts";
|
||||
import type {PhaseNodeData} from "../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx";
|
||||
import useFlowStore from '../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
|
||||
import { mockReactFlow } from '../../../setupFlowTests.ts';
|
||||
|
||||
@@ -6,18 +9,187 @@ beforeAll(() => {
|
||||
mockReactFlow();
|
||||
});
|
||||
|
||||
// default state values for testing,
|
||||
const normNode: Node = {
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Test Norm',
|
||||
droppable: true,
|
||||
norm: 'Test',
|
||||
hasReduce: true,
|
||||
},
|
||||
};
|
||||
|
||||
const phaseNode: Node = {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 1',
|
||||
droppable: true,
|
||||
children: ["norm-1"],
|
||||
hasReduce: true,
|
||||
},
|
||||
};
|
||||
|
||||
const testEdge: Edge = {
|
||||
id: 'xy-edge__1-2',
|
||||
source: 'norm-1',
|
||||
target: 'phase-1',
|
||||
sourceHandle: null,
|
||||
targetHandle: null,
|
||||
}
|
||||
|
||||
const testStateReconnectEnd = {
|
||||
nodes: [phaseNode, normNode],
|
||||
edges: [testEdge],
|
||||
}
|
||||
|
||||
const phaseNodeUnconnected = {
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 2',
|
||||
droppable: true,
|
||||
children: [],
|
||||
hasReduce: true,
|
||||
},
|
||||
};
|
||||
|
||||
const testConnection: Connection = {
|
||||
source: 'norm-1',
|
||||
target: 'phase-2',
|
||||
sourceHandle: null,
|
||||
targetHandle: null,
|
||||
}
|
||||
const testStateOnConnect = {
|
||||
nodes: [phaseNodeUnconnected, normNode],
|
||||
edges: [],
|
||||
}
|
||||
|
||||
describe('FlowStore Functionality', () => {
|
||||
describe('Node changes', () => {
|
||||
// currently just using a single function from the ReactFlow library,
|
||||
// so testing would mean we are testing already tested behavior.
|
||||
// if implementation gets modified tests should be added for custom behavior
|
||||
});
|
||||
describe('ReactFlow onEdgesDelete', () => {
|
||||
test('Deleted edge is reflected in removed phaseNode child', () => {
|
||||
const {onEdgesDelete} = useFlowStore.getState();
|
||||
|
||||
useFlowStore.setState({
|
||||
nodes: [{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 1',
|
||||
droppable: true,
|
||||
children: ["norm-1"],
|
||||
hasReduce: true,
|
||||
},
|
||||
},{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Test Norm',
|
||||
droppable: true,
|
||||
norm: 'Test',
|
||||
hasReduce: true,
|
||||
},
|
||||
}],
|
||||
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||
})
|
||||
|
||||
act(() => {
|
||||
onEdgesDelete([testEdge])
|
||||
});
|
||||
|
||||
const outcome = useFlowStore.getState();
|
||||
expect((outcome.nodes[0].data as PhaseNodeData).children.length).toBe(0);
|
||||
})
|
||||
test('Deleted edge is reflected in phaseNode,even if normNode was already deleted and caused edge removal', () => {
|
||||
const { onEdgesDelete } = useFlowStore.getState();
|
||||
useFlowStore.setState({
|
||||
nodes: [{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 1',
|
||||
droppable: true,
|
||||
children: ["norm-1"],
|
||||
hasReduce: true,
|
||||
},
|
||||
}],
|
||||
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||
})
|
||||
|
||||
act(() => {
|
||||
onEdgesDelete([testEdge]);
|
||||
})
|
||||
|
||||
const outcome = useFlowStore.getState();
|
||||
expect((outcome.nodes[0].data as PhaseNodeData).children.length).toBe(0);
|
||||
})
|
||||
test('edge removal resulting from deletion of targetNode calls only the connection function for the sourceNode', () => {
|
||||
const { onEdgesDelete } = useFlowStore.getState();
|
||||
useFlowStore.setState({
|
||||
nodes: [{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Test Norm',
|
||||
droppable: true,
|
||||
norm: 'Test',
|
||||
hasReduce: true,
|
||||
},
|
||||
}],
|
||||
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||
})
|
||||
|
||||
const targetDisconnectSpy = jest.spyOn(NodeDisconnections.Targets, 'phase');
|
||||
const sourceDisconnectSpy = jest.spyOn(NodeDisconnections.Sources, 'norm');
|
||||
|
||||
act(() => {
|
||||
onEdgesDelete([testEdge]);
|
||||
})
|
||||
|
||||
expect(sourceDisconnectSpy).toHaveBeenCalledWith(normNode, 'phase-1');
|
||||
expect(targetDisconnectSpy).not.toHaveBeenCalled();
|
||||
|
||||
sourceDisconnectSpy.mockRestore();
|
||||
targetDisconnectSpy.mockRestore();
|
||||
})
|
||||
})
|
||||
describe('Edge changes', () => {
|
||||
// currently just using a single function from the ReactFlow library,
|
||||
// so testing would mean we are testing already tested behavior.
|
||||
// if implementation gets modified tests should be added for custom behavior
|
||||
})
|
||||
describe('ReactFlow onConnect', () => {
|
||||
test('Adds connecting node to children of phaseNode', () => {
|
||||
const {onConnect} = useFlowStore.getState();
|
||||
useFlowStore.setState({
|
||||
nodes: testStateOnConnect.nodes,
|
||||
edges: testStateOnConnect.edges
|
||||
})
|
||||
|
||||
act(() => {
|
||||
onConnect(testConnection);
|
||||
})
|
||||
|
||||
const outcome = useFlowStore.getState();
|
||||
|
||||
// phaseNode adds the normNode to its children
|
||||
expect((outcome.nodes[0].data as PhaseNodeData).children).toEqual(['norm-1']);
|
||||
|
||||
})
|
||||
test('adds an edge when onConnect is triggered', () => {
|
||||
const {onConnect} = useFlowStore.getState();
|
||||
|
||||
@@ -39,6 +211,53 @@ describe('FlowStore Functionality', () => {
|
||||
});
|
||||
});
|
||||
describe('ReactFlow onReconnect', () => {
|
||||
test('PhaseNodes correctly change their children', () => {
|
||||
const {onReconnect} = useFlowStore.getState();
|
||||
useFlowStore.setState({
|
||||
nodes: [{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 1',
|
||||
droppable: true,
|
||||
children: ["norm-1"],
|
||||
hasReduce: true,
|
||||
},
|
||||
},{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 2',
|
||||
droppable: true,
|
||||
children: [],
|
||||
hasReduce: true,
|
||||
},
|
||||
},{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Test Norm',
|
||||
droppable: true,
|
||||
norm: 'Test',
|
||||
hasReduce: true,
|
||||
},
|
||||
}],
|
||||
edges: [testEdge],
|
||||
})
|
||||
|
||||
act(() => {
|
||||
onReconnect(testEdge, testConnection);
|
||||
})
|
||||
|
||||
const outcome = useFlowStore.getState();
|
||||
|
||||
// phaseNodes lose and gain children when norm node's connection is changed from phaseNode to PhaseNodeUnconnected
|
||||
expect((outcome.nodes[1].data as PhaseNodeData).children).toEqual(['norm-1']);
|
||||
expect((outcome.nodes[0].data as PhaseNodeData).children).toEqual([]);
|
||||
})
|
||||
test('reconnects an existing edge when onReconnect is triggered', () => {
|
||||
const {onReconnect} = useFlowStore.getState();
|
||||
const oldEdge = {
|
||||
@@ -93,36 +312,63 @@ describe('FlowStore Functionality', () => {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
test('successfully removes edge if no successful reconnect occurred', () => {
|
||||
const {onReconnectEnd} = useFlowStore.getState();
|
||||
useFlowStore.setState({edgeReconnectSuccessful: false});
|
||||
useFlowStore.setState({
|
||||
edgeReconnectSuccessful: false,
|
||||
edges: testStateReconnectEnd.edges,
|
||||
nodes: testStateReconnectEnd.nodes
|
||||
});
|
||||
|
||||
act(() => {
|
||||
onReconnectEnd(null, {id: 'xy-edge__A-B'});
|
||||
onReconnectEnd(null, testEdge);
|
||||
});
|
||||
|
||||
const updatedState = useFlowStore.getState();
|
||||
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
||||
expect(updatedState.edges).toHaveLength(0);
|
||||
expect(updatedState.nodes[0].data.children).toEqual([]);
|
||||
});
|
||||
|
||||
test('does not remove reconnecting edge if successful reconnect occurred', () => {
|
||||
const {onReconnectEnd} = useFlowStore.getState();
|
||||
useFlowStore.setState({
|
||||
edgeReconnectSuccessful: true,
|
||||
edges: [testEdge],
|
||||
nodes: [{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: { x: 100, y: 0 },
|
||||
data: {
|
||||
label: 'Phase 1',
|
||||
droppable: true,
|
||||
children: ["norm-1"],
|
||||
hasReduce: true,
|
||||
},
|
||||
},{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Test Norm',
|
||||
droppable: true,
|
||||
norm: 'Test',
|
||||
hasReduce: true,
|
||||
},
|
||||
}]
|
||||
});
|
||||
|
||||
act(() => {
|
||||
onReconnectEnd(null, {id: 'xy-edge__A-B'});
|
||||
onReconnectEnd(null, testEdge);
|
||||
});
|
||||
|
||||
const updatedState = useFlowStore.getState();
|
||||
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
||||
expect(updatedState.edges).toHaveLength(1);
|
||||
expect(updatedState.edges).toMatchObject([
|
||||
{
|
||||
id: 'xy-edge__A-B',
|
||||
source: 'A',
|
||||
target: 'B'
|
||||
}]
|
||||
);
|
||||
expect(updatedState.edges).toMatchObject([testEdge]);
|
||||
expect(updatedState.nodes[0].data.children).toEqual(["norm-1"]);
|
||||
});
|
||||
});
|
||||
describe('ReactFlow deleteNode', () => {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { describe, it, beforeEach } from '@jest/globals';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
||||
import NormNode, { NormReduce, NormConnects, type NormNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode'
|
||||
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||
import NormNode, {
|
||||
NormReduce,
|
||||
type NormNodeData,
|
||||
NormConnectionSource, NormConnectionTarget
|
||||
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode';
|
||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||
import type { Node } from '@xyflow/react';
|
||||
import '@testing-library/jest-dom'
|
||||
@@ -517,7 +521,7 @@ describe('NormNode', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
NormConnects(normNode, phaseNode, true);
|
||||
NormConnectionSource(normNode, phaseNode.id);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -547,7 +551,7 @@ describe('NormNode', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
NormConnects(normNode, phaseNode, false);
|
||||
NormConnectionTarget(normNode, phaseNode.id);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -565,7 +569,8 @@ describe('NormNode', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
NormConnects(normNode, normNode, true);
|
||||
NormConnectionTarget(normNode, normNode.id);
|
||||
NormConnectionSource(normNode, normNode.id);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,11 @@ import { describe, it } from '@jest/globals';
|
||||
import '@testing-library/jest-dom';
|
||||
import { screen } from '@testing-library/react';
|
||||
import type { Node } from '@xyflow/react';
|
||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
||||
import StartNode, { StartReduce, StartConnects } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode';
|
||||
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||
import StartNode, {
|
||||
StartConnectionSource, StartConnectionTarget,
|
||||
StartReduce
|
||||
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode';
|
||||
|
||||
|
||||
describe('StartNode', () => {
|
||||
@@ -91,8 +94,8 @@ describe('StartNode', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => StartConnects(startNode, otherNode, true)).not.toThrow();
|
||||
expect(() => StartConnects(startNode, otherNode, false)).not.toThrow();
|
||||
expect(() => StartConnectionSource(startNode, otherNode.id)).not.toThrow();
|
||||
expect(() => StartConnectionTarget(startNode, otherNode.id)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { describe, it, beforeEach } from '@jest/globals';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
||||
import TriggerNode, { TriggerReduce, TriggerConnects, TriggerNodeCanConnect, type TriggerNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode';
|
||||
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||
import TriggerNode, {
|
||||
TriggerReduce,
|
||||
TriggerNodeCanConnect,
|
||||
type TriggerNodeData,
|
||||
TriggerConnectionSource, TriggerConnectionTarget
|
||||
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode';
|
||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||
import type { Node } from '@xyflow/react';
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -233,8 +238,8 @@ describe('TriggerNode', () => {
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
TriggerConnects(node1, node2, true);
|
||||
TriggerConnects(node1, node2, false);
|
||||
TriggerConnectionSource(node1, node2.id);
|
||||
TriggerConnectionTarget(node1, node2.id);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, beforeEach } from '@jest/globals';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
||||
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||
import type { XYPosition } from '@xyflow/react';
|
||||
import { NodeTypes, NodeDefaults, NodeConnects, NodeReduces, NodesInPhase } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
||||
import { NodeTypes, NodeDefaults, NodeConnections, NodeReduces, NodesInPhase } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
||||
import '@testing-library/jest-dom'
|
||||
import { createElement } from 'react';
|
||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||
@@ -87,8 +87,8 @@ describe('NormNode', () => {
|
||||
useFlowStore.setState({ nodes: [sourceNode, targetNode] });
|
||||
|
||||
// Spy on the connect functions
|
||||
const sourceConnectSpy = jest.spyOn(NodeConnects, nodeType as keyof typeof NodeConnects);
|
||||
const targetConnectSpy = jest.spyOn(NodeConnects, 'end');
|
||||
const sourceConnectSpy = jest.spyOn(NodeConnections.Sources, nodeType as keyof typeof NodeConnections.Sources);
|
||||
const targetConnectSpy = jest.spyOn(NodeConnections.Targets, 'end');
|
||||
|
||||
// Simulate connection
|
||||
useFlowStore.getState().onConnect({
|
||||
@@ -99,8 +99,8 @@ describe('NormNode', () => {
|
||||
});
|
||||
|
||||
// Verify the connect functions were called
|
||||
expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode, true);
|
||||
expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode, false);
|
||||
expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode.id);
|
||||
expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode.id);
|
||||
|
||||
sourceConnectSpy.mockRestore();
|
||||
targetConnectSpy.mockRestore();
|
||||
@@ -130,7 +130,7 @@ describe('NormNode', () => {
|
||||
expect(phaseReduceSpy).toHaveBeenCalledWith(phaseNode, [phaseNode, testNode]);
|
||||
// Check if this node type is in NodesInPhase and returns false
|
||||
const nodesInPhaseFunc = NodesInPhase[nodeType as keyof typeof NodesInPhase];
|
||||
if (nodesInPhaseFunc && nodesInPhaseFunc() === false && nodeType !== 'phase') {
|
||||
if (nodesInPhaseFunc && !nodesInPhaseFunc() && nodeType !== 'phase') {
|
||||
// Node is NOT in phase, so it should NOT be called
|
||||
expect(nodeReduceSpy).not.toHaveBeenCalled();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user