-
Norm {data.label}
+
+
>
diff --git a/test/pages/visProgPage/visualProgrammingUI/GraphReducer.test.ts b/test/pages/visProgPage/visualProgrammingUI/GraphReducer.test.ts
new file mode 100644
index 0000000..4473b82
--- /dev/null
+++ b/test/pages/visProgPage/visualProgrammingUI/GraphReducer.test.ts
@@ -0,0 +1,986 @@
+import type {Edge} from "@xyflow/react";
+import graphReducer, {
+ defaultGraphPreprocessor, defaultPhaseReducer,
+ orderPhases
+} from "../../../../src/pages/VisProgPage/visualProgrammingUI/GraphReducer.ts";
+import type {PreparedPhase} from "../../../../src/pages/VisProgPage/visualProgrammingUI/GraphReducerTypes.ts";
+import useFlowStore from "../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";
+import type {AppNode} from "../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx";
+
+// sets of default values for nodes and edges to be used for test cases
+type FlowState = {
+ name: string;
+ nodes: AppNode[];
+ edges: Edge[];
+};
+
+// predefined graphs for testing:
+const onlyOnePhase : FlowState = {
+ name: "onlyOnePhase",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'phase-1-end',
+ source: 'phase-1',
+ target: 'end',
+ }
+ ]
+};
+const onlyThreePhases : FlowState = {
+ name: "onlyThreePhases",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'phase-1-phase-2',
+ source: 'phase-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'phase-2-phase-3',
+ source: 'phase-2',
+ target: 'phase-3',
+ },
+ {
+ id: 'phase-3-end',
+ source: 'phase-3',
+ target: 'end',
+ }
+ ]
+};
+const onlySingleEdgeNorms : FlowState = {
+ name: "onlySingleEdgeNorms",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'norm-2',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'norm-1-phase-2',
+ source: 'norm-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'phase-1-phase-2',
+ source: 'phase-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'phase-2-phase-3',
+ source: 'phase-2',
+ target: 'phase-3',
+ },
+ {
+ id: 'norm-2-phase-3',
+ source: 'norm-2',
+ target: 'phase-3',
+ },
+ {
+ id: 'phase-3-end',
+ source: 'phase-3',
+ target: 'end',
+ }
+ ]
+};
+const multiEdgeNorms : FlowState = {
+ name: "multiEdgeNorms",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'norm-2',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ {
+ id: 'norm-3',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'norm-1-phase-2',
+ source: 'norm-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'norm-1-phase-3',
+ source: 'norm-1',
+ target: 'phase-3',
+ },
+ {
+ id: 'phase-1-phase-2',
+ source: 'phase-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'norm-3-phase-1',
+ source: 'norm-3',
+ target: 'phase-1',
+ },
+ {
+ id: 'phase-2-phase-3',
+ source: 'phase-2',
+ target: 'phase-3',
+ },
+ {
+ id: 'norm-2-phase-3',
+ source: 'norm-2',
+ target: 'phase-3',
+ },
+ {
+ id: 'norm-2-phase-2',
+ source: 'norm-2',
+ target: 'phase-2',
+ },
+ {
+ id: 'phase-3-end',
+ source: 'phase-3',
+ target: 'end',
+ }
+ ]
+};
+const onlyStartEnd : FlowState = {
+ name: "onlyStartEnd",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-end',
+ source: 'start',
+ target: 'end',
+ },
+ ]
+};
+
+// states that contain invalid programs for testing if correct errors are thrown:
+const phaseConnectsToInvalidNodeType : FlowState = {
+ name: "phaseConnectsToInvalidNodeType",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'default-1',
+ type: 'default',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm'},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'phase-1-default-1',
+ source: 'phase-1',
+ target: 'default-1',
+ },
+ ]
+};
+const phaseHasNoOutgoingConnections : FlowState = {
+ name: "phaseHasNoOutgoingConnections",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ ]
+};
+const phaseHasTooManyOutgoingConnections : FlowState = {
+ name: "phaseHasTooManyOutgoingConnections",
+ nodes: [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+ ],
+ edges:[
+ {
+ id: 'start-phase-1',
+ source: 'start',
+ target: 'phase-1',
+ },
+ {
+ id: 'phase-1-phase-2',
+ source: 'phase-1',
+ target: 'phase-2',
+ },
+ {
+ id: 'phase-1-end',
+ source: 'phase-1',
+ target: 'end',
+ },
+ {
+ id: 'phase-2-end',
+ source: 'phase-2',
+ target: 'end',
+ },
+ ]
+};
+
+describe('Graph Reducer Tests', () => {
+ describe('defaultGraphPreprocessor', () => {
+ test.each([
+ {
+ state: onlyOnePhase,
+ expected: [
+ {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [],
+ connectedGoals: [],
+ }]
+ },
+ {
+ state: onlyThreePhases,
+ expected: [
+ {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'phase-2',
+ connectedNorms: [],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ nextPhaseId: 'phase-3',
+ connectedNorms: [],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [],
+ connectedGoals: [],
+ }]
+ },
+ {
+ state: onlySingleEdgeNorms,
+ expected: [
+ {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'phase-2',
+ connectedNorms: [],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ nextPhaseId: 'phase-3',
+ connectedNorms: [{
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [{
+ id: 'norm-2',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ }]
+ },
+ {
+ state: multiEdgeNorms,
+ expected: [
+ {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'phase-2',
+ connectedNorms: [{
+ id: 'norm-3',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ nextPhaseId: 'phase-3',
+ connectedNorms: [{
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'norm-2',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ },
+ {
+ phaseNode: {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [{
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ },
+ {
+ id: 'norm-2',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ }]
+ },
+ {
+ state: onlyStartEnd,
+ expected: [],
+ }
+ ])(`tests state: $state.name`, ({state, expected}) => {
+ const output = defaultGraphPreprocessor(state.nodes, state.edges);
+ expect(output).toEqual(expected);
+ });
+ });
+ describe("orderPhases", () => {
+ test.each([
+ {
+ state: onlyOnePhase,
+ expected: {
+ phaseNodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ }],
+ connections: new Map
([["phase-1","end"]])
+ }
+ },
+ {
+ state: onlyThreePhases,
+ expected: {
+ phaseNodes: [
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ }],
+ connections: new Map([
+ ["phase-1","phase-2"],
+ ["phase-2","phase-3"],
+ ["phase-3","end"]
+ ])
+ }
+ },
+ {
+ state: onlySingleEdgeNorms,
+ expected: {
+ phaseNodes: [
+ {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'phase-2',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 2},
+ },
+ {
+ id: 'phase-3',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 3},
+ }],
+ connections: new Map([
+ ["phase-1","phase-2"],
+ ["phase-2","phase-3"],
+ ["phase-3","end"]
+ ])
+ }
+ },
+ {
+ state: onlyStartEnd,
+ expected: {
+ phaseNodes: [],
+ connections: new Map()
+ }
+ }
+ ])(`tests state: $state.name`, ({state, expected}) => {
+ const output = orderPhases(state.nodes, state.edges);
+ expect(output.phaseNodes).toEqual(expected.phaseNodes);
+ expect(output.connections).toEqual(expected.connections);
+ });
+ test.each([
+ {
+ state: phaseConnectsToInvalidNodeType,
+ expected: new Error('| INVALID PROGRAM | the node "default-1" that "phase-1" connects to is not a phase or end node')
+ },
+ {
+ state: phaseHasNoOutgoingConnections,
+ expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" doesn\'t have any outgoing connections')
+ },
+ {
+ state: phaseHasTooManyOutgoingConnections,
+ expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" connects to too many targets')
+ }
+ ])(`tests erroneous state: $state.name`, ({state, expected}) => {
+ const testForError = () => {
+ orderPhases(state.nodes, state.edges);
+ };
+ expect(testForError).toThrow(expected);
+ })
+ })
+ describe("defaultPhaseReducer", () => {
+ test("phaseReducer handles empty norms and goals without failing", () => {
+ const input : PreparedPhase = {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [],
+ connectedGoals: [],
+ }
+ const output = defaultPhaseReducer(input);
+ expect(output).toEqual({
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ });
+ });
+ test("defaultNormReducer reduces norms correctly", () => {
+ const input : PreparedPhase = {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [{
+ id: 'norm-1',
+ type: 'norm',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Norm', value: "generic"},
+ }],
+ connectedGoals: [],
+ }
+ const output = defaultPhaseReducer(input);
+ expect(output).toEqual({
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [{
+ id: 'norm-1',
+ name: 'Generic Norm',
+ value: "generic"
+ }],
+ goals: []
+ }
+ });
+ });
+ test("defaultGoalReducer reduces goals correctly", () => {
+ const input : PreparedPhase = {
+ phaseNode: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ nextPhaseId: 'end',
+ connectedNorms: [],
+ connectedGoals: [{
+ id: 'goal-1',
+ type: 'goal',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Goal', value: "generic"},
+ }],
+ }
+ const output = defaultPhaseReducer(input);
+ expect(output).toEqual({
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [],
+ goals: [{
+ id: 'goal-1',
+ name: 'Generic Goal',
+ value: "generic"
+ }]
+ }
+ });
+ });
+ })
+ describe("GraphReducer", () => {
+ test.each([
+ {
+ state: onlyOnePhase,
+ expected: [
+ {
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ }]
+ },
+ {
+ state: onlyThreePhases,
+ expected: [
+ {
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-2',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-2',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-3',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-3',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ }]
+ },
+ {
+ state: onlySingleEdgeNorms,
+ expected: [
+ {
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-2',
+ phaseData: {
+ norms: [],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-2',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-3',
+ phaseData: {
+ norms: [
+ {
+ id: 'norm-1',
+ name: 'Generic Norm',
+ value: "generic"
+ }
+ ],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-3',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [{
+ id: 'norm-2',
+ name: 'Generic Norm',
+ value: "generic"
+ }],
+ goals: []
+ }
+ }]
+ },
+ {
+ state: multiEdgeNorms,
+ expected: [
+ {
+ id: 'phase-1',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-2',
+ phaseData: {
+ norms: [{
+ id: 'norm-3',
+ name: 'Generic Norm',
+ value: "generic"
+ }],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-2',
+ name: 'Generic Phase',
+ nextPhaseId: 'phase-3',
+ phaseData: {
+ norms: [
+ {
+ id: 'norm-1',
+ name: 'Generic Norm',
+ value: "generic"
+ },
+ {
+ id: 'norm-2',
+ name: 'Generic Norm',
+ value: "generic"
+ }
+ ],
+ goals: []
+ }
+ },
+ {
+ id: 'phase-3',
+ name: 'Generic Phase',
+ nextPhaseId: 'end',
+ phaseData: {
+ norms: [{
+ id: 'norm-1',
+ name: 'Generic Norm',
+ value: "generic"
+ },
+ {
+ id: 'norm-2',
+ name: 'Generic Norm',
+ value: "generic"
+ }],
+ goals: []
+ }
+ }]
+ },
+ {
+ state: onlyStartEnd,
+ expected: [],
+ }
+ ])(`tests state: $state.name`, ({state, expected}) => {
+ useFlowStore.setState({nodes: state.nodes, edges: state.edges});
+ const output = graphReducer(); // uses default reducers
+ expect(output).toEqual(expected);
+ })
+ // we run the test for correct error handling for the entire graph reducer as well,
+ // to make sure no errors occur before we intend to handle the errors ourselves
+ test.each([
+ {
+ state: phaseConnectsToInvalidNodeType,
+ expected: new Error('| INVALID PROGRAM | the node "default-1" that "phase-1" connects to is not a phase or end node')
+ },
+ {
+ state: phaseHasNoOutgoingConnections,
+ expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" doesn\'t have any outgoing connections')
+ },
+ {
+ state: phaseHasTooManyOutgoingConnections,
+ expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" connects to too many targets')
+ }
+ ])(`tests erroneous state: $state.name`, ({state, expected}) => {
+ useFlowStore.setState({nodes: state.nodes, edges: state.edges});
+ const testForError = () => {
+ graphReducer();
+ };
+ expect(testForError).toThrow(expected);
+ })
+ })
+});
\ No newline at end of file
diff --git a/test/pages/visProgPage/visualProgrammingUI/VisProgStores.test.tsx b/test/pages/visProgPage/visualProgrammingUI/VisProgStores.test.tsx
index 9b3ab80..63fec3d 100644
--- a/test/pages/visProgPage/visualProgrammingUI/VisProgStores.test.tsx
+++ b/test/pages/visProgPage/visualProgrammingUI/VisProgStores.test.tsx
@@ -221,4 +221,132 @@ describe('FlowStore Functionality', () => {
});
});
});
+ describe('ReactFlow updateNodeData', () => {
+ test.each([
+ {
+ state: {
+ name: 'updateName',
+ nodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2'}
+ }]
+ },
+ input: {
+ id: 'phase-1',
+ changedData: {label: 'new name'}
+ },
+ expected: {
+ node: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'new name', number: '2'}
+ }
+ }
+ },
+ {
+ state: {
+ name: 'updateNumber',
+ nodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2'}
+ }]
+ },
+ input: {
+ id: 'phase-1',
+ changedData: {number: '3'}
+ },
+ expected: {
+ node: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '3'}
+ }
+ }
+ },
+ {
+ state: {
+ name: 'updateNameAndNumber',
+ nodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2'}
+ }]
+ },
+ input: {
+ id: 'phase-1',
+ changedData: {label: 'new name', number: '3'}
+ },
+ expected: {
+ node: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'new name', number: '3'}
+ }
+ }
+ },
+ {
+ state: {
+ name: 'AddNewEntry',
+ nodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2'}
+ }]
+ },
+ input: {
+ id: 'phase-1',
+ changedData: {newEntry: 20}
+ },
+ expected: {
+ node: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2', newEntry: 20}
+ }
+ }
+ },
+ {
+ state: {
+ name: 'AddNewEntryAndUpdateOneValue_UnorderedInput',
+ nodes: [{
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '2'}
+ }]
+ },
+ input: {
+ id: 'phase-1',
+ changedData: {newEntry: 20, number: '3'}
+ },
+ expected: {
+ node: {
+ id: 'phase-1',
+ type: 'phase',
+ position: {x: 0, y: 300},
+ data: {label: 'name', number: '3', newEntry: 20}
+ }
+ }
+ }
+ ])(`tests state: $state.name`, ({state, input,expected}) => {
+ useFlowStore.setState({ nodes: state.nodes })
+ const {updateNodeData} = useFlowStore.getState();
+ act(() => {
+ updateNodeData(input.id, input.changedData);
+ })
+ const updatedState = useFlowStore.getState();
+ expect(updatedState.nodes).toHaveLength(1);
+ expect(updatedState.nodes[0]).toMatchObject(expected.node);
+ })
+ })
});
diff --git a/test/pages/visProgPage/visualProgrammingUI/components/DragDropSidebar.test.tsx b/test/pages/visProgPage/visualProgrammingUI/components/DragDropSidebar.test.tsx
index ae9b88c..a92adb3 100644
--- a/test/pages/visProgPage/visualProgrammingUI/components/DragDropSidebar.test.tsx
+++ b/test/pages/visProgPage/visualProgrammingUI/components/DragDropSidebar.test.tsx
@@ -24,8 +24,8 @@ describe('Drag-and-Drop sidebar', () => {
})
const updatedState = useFlowStore.getState();
expect(updatedState.nodes.length).toBe(2);
- expect(updatedState.nodes[0].id).toBe(`${nodeType}-0`);
- expect(updatedState.nodes[1].id).toBe(`${nodeType}-1`);
+ expect(updatedState.nodes[0].id).toBe(`${nodeType}-1`);
+ expect(updatedState.nodes[1].id).toBe(`${nodeType}-2`);
});
test('throws error on unexpected node type', () => {
expect(() => addNode('I do not Exist', {x:100, y:100})).toThrow("Node I do not Exist not found");