Files
pepperplus-ui/test/pages/visProgPage/visualProgrammingUI/nodes/UniversalNodes.test.tsx
2026-01-06 15:12:00 +00:00

151 lines
5.4 KiB
TypeScript

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<string, unknown>, 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<any>, 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();
});
});
});