import { describe, beforeEach } from '@jest/globals'; import { screen } from '@testing-library/react'; import { renderWithProviders, resetFlowStore } from '../.././/./../../test-utils/test-utils'; import type { XYPosition } from '@xyflow/react'; import { NodeTypes, NodeDefaults, NodeConnects, 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(() => { resetFlowStore(); jest.clearAllMocks(); }); function createNode(id: string, type: string, position: XYPosition, data: Record, deletable?: boolean) { const defaultData = JSON.parse(JSON.stringify(NodeDefaults[type as keyof typeof NodeDefaults])) const newData = { id: id, type: type, position: position, data: data, deletable: deletable, } return {...defaultData, ...newData} } /** * 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; let newNode = createNode(nodeType + "1", nodeType, {x: 200, y:200}, {}) let uiElement = Object.entries(NodeTypes).find(([t])=>t==nodeType)?.[1]!; let 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(NodeConnects, nodeType as keyof typeof NodeConnects); const targetConnectSpy = jest.spyOn(NodeConnects, '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, true); expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode, false); 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() === false && 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(); }); }); });