import { describe, beforeEach } from '@jest/globals'; import { screen } from '@testing-library/react'; import { renderWithProviders } from '../../../../test-utils/test-utils.tsx'; import type { XYPosition } from '@xyflow/react'; 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'; describe('NormNode', () => { beforeEach(() => { jest.clearAllMocks(); }); function createNode(id: string, type: string, position: XYPosition, data: Record, deletable? : boolean) { const defaultData = NodeDefaults[type as keyof typeof NodeDefaults] return { id: id, type: type, position: position, data: {...defaultData, ...data}, deletable: deletable } } /** * Reduces the graph into its phases' information and recursively calls their reducing function */ function graphReducer() { const { nodes } = useFlowStore.getState(); return nodes .filter((n) => n.type == 'phase') .map((n) => { const reducer = NodeReduces['phase']; return reducer(n, nodes) }); } function getAllTypes() { return Object.entries(NodeTypes).map(([t])=>t) } describe('Rendering', () => { test.each(getAllTypes())('it should render %s node with the default data', (nodeType) => { const lengthBefore = screen.getAllByText(/.*/).length; const newNode = createNode(nodeType + "1", nodeType, {x: 200, y:200}, {}); const found = Object.entries(NodeTypes).find(([t]) => t === nodeType); const uiElement = found ? found[1] : null; expect(uiElement).not.toBeNull(); const props = { id: newNode.id, type: newNode.type as string, data: newNode.data as any, selected: false, isConnectable: true, zIndex: 0, dragging: false, selectable: true, deletable: true, draggable: true, positionAbsoluteX: 0, positionAbsoluteY: 0, }; renderWithProviders(createElement(uiElement as React.ComponentType, props)); const lengthAfter = screen.getAllByText(/.*/).length; expect(lengthBefore + 1 === lengthAfter); }); }); describe('Connecting', () => { test.each(getAllTypes())('it should call the connect function when %s node is connected', (nodeType) => { // Create two nodes - one of the current type and one to connect to const sourceNode = createNode('source-1', nodeType, {x: 100, y: 100}, {}); const targetNode = createNode('target-1', 'end', {x: 300, y: 100}, {}); // Add nodes to store useFlowStore.setState({ nodes: [sourceNode, targetNode] }); // Spy on the connect functions const sourceConnectSpy = jest.spyOn(NodeConnections.Sources, nodeType as keyof typeof NodeConnections.Sources); const targetConnectSpy = jest.spyOn(NodeConnections.Targets, 'end'); // Simulate connection useFlowStore.getState().onConnect({ source: 'source-1', target: 'target-1', sourceHandle: null, targetHandle: null, }); // Verify the connect functions were called expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode.id); expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode.id); sourceConnectSpy.mockRestore(); targetConnectSpy.mockRestore(); }); }); describe('Reducing', () => { test.each(getAllTypes())('it should correctly call/ not call the reduce function when %s node is in a phase', (nodeType) => { // Create a phase node and a node of the current type const phaseNode = createNode('phase-1', 'phase', {x: 200, y: 100}, { label: 'Test Phase', children: [] }); const testNode = createNode('node-1', nodeType, {x: 100, y: 100}, {}); // Add the test node as a child of the phase (phaseNode.data as any).children.push(testNode.id); // Add nodes to store useFlowStore.setState({ nodes: [phaseNode, testNode] }); // Spy on the reduce functions const phaseReduceSpy = jest.spyOn(NodeReduces, 'phase'); const nodeReduceSpy = jest.spyOn(NodeReduces, nodeType as keyof typeof NodeReduces); // Simulate reducing - using the graphReducer const result = graphReducer(); // Verify the reduce functions were called 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() && nodeType !== 'phase') { // Node is NOT in phase, so it should NOT be called expect(nodeReduceSpy).not.toHaveBeenCalled(); } else { // Node IS in phase, so it SHOULD be called expect(nodeReduceSpy).toHaveBeenCalled(); } // Verify the correct structure is present using NodesInPhase expect(result).toHaveLength(nodeType !== 'phase' ? 1 : 2); expect(result[0]).toHaveProperty('id', 'phase-1'); expect(result[0]).toHaveProperty('label', 'Test Phase'); // Restore mocks phaseReduceSpy.mockRestore(); nodeReduceSpy.mockRestore(); }); }); });