test: create universal tests and rewrite nodes to have optional parameters for more code coverage
ref: N25B-362
This commit is contained in:
@@ -69,6 +69,7 @@ export const NodeConnects = {
|
|||||||
export const NodeDeletes = {
|
export const NodeDeletes = {
|
||||||
start: () => false,
|
start: () => false,
|
||||||
end: () => false,
|
end: () => false,
|
||||||
|
test: () => false, // Used for coverage of universal/ undefined nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,4 +80,5 @@ export const NodesInPhase = {
|
|||||||
start: () => false,
|
start: () => false,
|
||||||
end: () => false,
|
end: () => false,
|
||||||
phase: () => false,
|
phase: () => false,
|
||||||
|
test: () => false, // Used for coverage of universal/ undefined nodes
|
||||||
}
|
}
|
||||||
@@ -40,14 +40,11 @@ export default function EndNode(props: NodeProps<EndNode>) {
|
|||||||
/**
|
/**
|
||||||
* Functionality for reducing this node into its more compact json program
|
* Functionality for reducing this node into its more compact json program
|
||||||
* @param node the node to reduce
|
* @param node the node to reduce
|
||||||
* @param nodes all nodes present
|
* @param _nodes all nodes present
|
||||||
* @returns Dictionary, {id: node.id}
|
* @returns Dictionary, {id: node.id}
|
||||||
*/
|
*/
|
||||||
export function EndReduce(node: Node, nodes: Node[]) {
|
export function EndReduce(node: Node, _nodes: Node[]) {
|
||||||
// Replace this for nodes functionality
|
// Replace this for nodes functionality
|
||||||
if (nodes.length <= -1) {
|
|
||||||
console.warn("Impossible nodes length in EndReduce")
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: node.id
|
id: node.id
|
||||||
}
|
}
|
||||||
@@ -55,13 +52,9 @@ export function EndReduce(node: Node, nodes: Node[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Any connection functionality that should get called when a connection is made to this node type (end)
|
* Any connection functionality that should get called when a connection is made to this node type (end)
|
||||||
* @param thisNode the node of which the functionality gets called
|
* @param _thisNode the node of which the functionality gets called
|
||||||
* @param otherNode the other node which has connected
|
* @param _otherNode the other node which has connected
|
||||||
* @param isThisSource whether this node is the one that is the source of the connection
|
* @param _isThisSource whether this node is the one that is the source of the connection
|
||||||
*/
|
*/
|
||||||
export function EndConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function EndConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
|
||||||
console.warn("Impossible node connection called in EndConnects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -77,13 +77,9 @@ export default function GoalNode(props: NodeProps<GoalNode>) {
|
|||||||
/**
|
/**
|
||||||
* Reduces each Goal, including its children down into its relevant data.
|
* Reduces each Goal, including its children down into its relevant data.
|
||||||
* @param node: The Node Properties of this node.
|
* @param node: The Node Properties of this node.
|
||||||
* @param nodes: all the nodes in the graph
|
* @param _nodes: all the nodes in the graph
|
||||||
*/
|
*/
|
||||||
export function GoalReduce(node: Node, nodes: Node[]) {
|
export function GoalReduce(node: Node, _nodes: Node[]) {
|
||||||
// Replace this for nodes functionality
|
|
||||||
if (nodes.length <= -1) {
|
|
||||||
console.warn("Impossible nodes length in GoalReduce")
|
|
||||||
}
|
|
||||||
const data = node.data as GoalNodeData;
|
const data = node.data as GoalNodeData;
|
||||||
return {
|
return {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
@@ -93,9 +89,6 @@ export function GoalReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GoalConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function GoalConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
// Replace this for connection logic
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
|
||||||
console.warn("Impossible node connection called in EndConnects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -61,13 +61,9 @@ export default function NormNode(props: NodeProps<NormNode>) {
|
|||||||
/**
|
/**
|
||||||
* Reduces each Norm, including its children down into its relevant data.
|
* Reduces each Norm, including its children down into its relevant data.
|
||||||
* @param node: The Node Properties of this node.
|
* @param node: The Node Properties of this node.
|
||||||
* @param nodes: all the nodes in the graph
|
* @param _nodes: all the nodes in the graph
|
||||||
*/
|
*/
|
||||||
export function NormReduce(node: Node, nodes: Node[]) {
|
export function NormReduce(node: Node, _nodes: Node[]) {
|
||||||
// Replace this for nodes functionality
|
|
||||||
if (nodes.length <= -1) {
|
|
||||||
console.warn("Impossible nodes length in NormReduce")
|
|
||||||
}
|
|
||||||
const data = node.data as NormNodeData;
|
const data = node.data as NormNodeData;
|
||||||
return {
|
return {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
@@ -76,9 +72,5 @@ export function NormReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NormConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function NormConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
|
||||||
console.warn("Impossible node connection called in EndConnects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -78,8 +78,10 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
|
|||||||
.filter(([t]) => !nodesNotInPhase.includes(t))
|
.filter(([t]) => !nodesNotInPhase.includes(t))
|
||||||
.map(([t]) => t);
|
.map(([t]) => t);
|
||||||
|
|
||||||
// children nodes
|
// children nodes - make sure to check for empty arrays
|
||||||
const childrenNodes = nodes.filter((node) => data.children.includes(node.id));
|
let childrenNodes: any[] = [];
|
||||||
|
if (data.children)
|
||||||
|
childrenNodes = nodes.filter((node) => data.children.includes(node.id));
|
||||||
|
|
||||||
// Build the result object
|
// Build the result object
|
||||||
const result: Record<string, unknown> = {
|
const result: Record<string, unknown> = {
|
||||||
|
|||||||
@@ -40,14 +40,11 @@ export default function StartNode(props: NodeProps<StartNode>) {
|
|||||||
/**
|
/**
|
||||||
* The reduce function for this node type.
|
* The reduce function for this node type.
|
||||||
* @param node this node
|
* @param node this node
|
||||||
* @param nodes all the nodes in the graph
|
* @param _nodes all the nodes in the graph
|
||||||
* @returns a reduced structure of this node
|
* @returns a reduced structure of this node
|
||||||
*/
|
*/
|
||||||
export function StartReduce(node: Node, nodes: Node[]) {
|
export function StartReduce(node: Node, _nodes: Node[]) {
|
||||||
// Replace this for nodes functionality
|
// Replace this for nodes functionality
|
||||||
if (nodes.length <= -1) {
|
|
||||||
console.warn("Impossible nodes length in StartReduce")
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: node.id
|
id: node.id
|
||||||
}
|
}
|
||||||
@@ -55,13 +52,9 @@ export function StartReduce(node: Node, nodes: Node[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called whenever a connection is made with this node type (start)
|
* This function is called whenever a connection is made with this node type (start)
|
||||||
* @param thisNode the node of this node type which function is called
|
* @param _thisNode the node of this node type which function is called
|
||||||
* @param otherNode the other node which was part of the connection
|
* @param _otherNode the other node which was part of the connection
|
||||||
* @param isThisSource whether this instance of the node was the source in the connection, true = yes.
|
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
|
||||||
*/
|
*/
|
||||||
export function StartConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function StartConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
|
||||||
console.warn("Impossible node connection called in EndConnects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -68,13 +68,9 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
|||||||
/**
|
/**
|
||||||
* Reduces each Trigger, including its children down into its relevant data.
|
* Reduces each Trigger, including its children down into its relevant data.
|
||||||
* @param node: The Node Properties of this node.
|
* @param node: The Node Properties of this node.
|
||||||
* @param nodes: all the nodes in the graph.
|
* @param _nodes: all the nodes in the graph.
|
||||||
*/
|
*/
|
||||||
export function TriggerReduce(node: Node, nodes: Node[]) {
|
export function TriggerReduce(node: Node, _nodes: Node[]) {
|
||||||
// Replace this for nodes functionality
|
|
||||||
if (nodes.length <= -1) {
|
|
||||||
console.warn("Impossible nodes length in TriggerReduce")
|
|
||||||
}
|
|
||||||
const data = node.data as TriggerNodeData;
|
const data = node.data as TriggerNodeData;
|
||||||
return {
|
return {
|
||||||
label: data.label,
|
label: data.label,
|
||||||
@@ -84,15 +80,12 @@ export function TriggerReduce(node: Node, nodes: Node[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called whenever a connection is made with this node type (trigger)
|
* This function is called whenever a connection is made with this node type (trigger)
|
||||||
* @param thisNode the node of this node type which function is called
|
* @param _thisNode the node of this node type which function is called
|
||||||
* @param otherNode the other node which was part of the connection
|
* @param _otherNode the other node which was part of the connection
|
||||||
* @param isThisSource whether this instance of the node was the source in the connection, true = yes.
|
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
|
||||||
*/
|
*/
|
||||||
export function TriggerConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function TriggerConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
||||||
// Replace this for connection logic
|
|
||||||
if (thisNode == undefined && otherNode == undefined && isThisSource == false) {
|
|
||||||
console.warn("Impossible node connection called in EndConnects")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definitions for the possible triggers, being keywords and emotions
|
// Definitions for the possible triggers, being keywords and emotions
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import { describe, beforeEach } from '@jest/globals';
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { renderWithProviders, resetFlowStore } from '../.././/./../../test-utils/test-utils';
|
import { renderWithProviders, resetFlowStore } from '../.././/./../../test-utils/test-utils';
|
||||||
import type { XYPosition } from '@xyflow/react';
|
import type { XYPosition } from '@xyflow/react';
|
||||||
import { NodeTypes, NodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
import { NodeTypes, NodeDefaults, NodeConnects, NodeReduces, NodesInPhase } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||||
|
|
||||||
|
|
||||||
describe('NormNode', () => {
|
describe('NormNode', () => {
|
||||||
// let user: ReturnType<typeof userEvent.setup>;
|
beforeEach(() => {
|
||||||
|
resetFlowStore();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
// Copied from VisStores.
|
|
||||||
function createNode(id: string, type: string, position: XYPosition, data: Record<string, unknown>, deletable?: boolean) {
|
function createNode(id: string, type: string, position: XYPosition, data: Record<string, unknown>, deletable?: boolean) {
|
||||||
const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
|
const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
|
||||||
const newData = {
|
const newData = {
|
||||||
@@ -23,13 +27,27 @@ describe('NormNode', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
/**
|
||||||
resetFlowStore();
|
* Reduces the graph into its phases' information and recursively calls their reducing function
|
||||||
// user = userEvent.setup();
|
*/
|
||||||
|
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', () => {
|
describe('Rendering', () => {
|
||||||
test.each([Object.entries(NodeTypes)].map(([t])=>t))('it should render each node with the default data', (nodeType) => {
|
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 newNode = createNode(nodeType + "1", nodeType, {x: 200, y:200}, {})
|
||||||
let uiElement = Object.entries(NodeTypes).find(([t])=>t==nodeType)?.[1]!;
|
let uiElement = Object.entries(NodeTypes).find(([t])=>t==nodeType)?.[1]!;
|
||||||
let props = {
|
let props = {
|
||||||
@@ -45,11 +63,84 @@ describe('NormNode', () => {
|
|||||||
draggable:true,
|
draggable:true,
|
||||||
positionAbsoluteX:0,
|
positionAbsoluteX:0,
|
||||||
positionAbsoluteY:0,}
|
positionAbsoluteY:0,}
|
||||||
renderWithProviders(uiElement(props));
|
|
||||||
const elements = screen.queryAllByText((content, ) =>
|
renderWithProviders(createElement(uiElement as React.ComponentType<any>, props));
|
||||||
content.toLowerCase().includes(nodeType.toLowerCase())
|
const lengthAfter = screen.getAllByText(/.*/).length;
|
||||||
);
|
|
||||||
expect(elements.length).toBeGreaterThan(0);
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user