fix: edge-disconnections-are-not-reflected-in-reduced-program
This commit is contained in:
committed by
JobvAlewijk
parent
8149d67491
commit
58ab95eee1
@@ -39,6 +39,7 @@ const selector = (state: FlowState) => ({
|
|||||||
nodes: state.nodes,
|
nodes: state.nodes,
|
||||||
edges: state.edges,
|
edges: state.edges,
|
||||||
onNodesChange: state.onNodesChange,
|
onNodesChange: state.onNodesChange,
|
||||||
|
onEdgesDelete: state.onEdgesDelete,
|
||||||
onEdgesChange: state.onEdgesChange,
|
onEdgesChange: state.onEdgesChange,
|
||||||
onConnect: state.onConnect,
|
onConnect: state.onConnect,
|
||||||
onReconnectStart: state.onReconnectStart,
|
onReconnectStart: state.onReconnectStart,
|
||||||
@@ -62,6 +63,7 @@ const VisProgUI = () => {
|
|||||||
const {
|
const {
|
||||||
nodes, edges,
|
nodes, edges,
|
||||||
onNodesChange,
|
onNodesChange,
|
||||||
|
onEdgesDelete,
|
||||||
onEdgesChange,
|
onEdgesChange,
|
||||||
onConnect,
|
onConnect,
|
||||||
onReconnect,
|
onReconnect,
|
||||||
@@ -91,6 +93,7 @@ const VisProgUI = () => {
|
|||||||
defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
|
defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
|
||||||
nodeTypes={NodeTypes}
|
nodeTypes={NodeTypes}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesDelete={onEdgesDelete}
|
||||||
onEdgesChange={onEdgesChange}
|
onEdgesChange={onEdgesChange}
|
||||||
onReconnect={onReconnect}
|
onReconnect={onReconnect}
|
||||||
onReconnectStart={onReconnectStart}
|
onReconnectStart={onReconnectStart}
|
||||||
|
|||||||
@@ -1,14 +1,50 @@
|
|||||||
import StartNode, { StartConnects, StartReduce } from "./nodes/StartNode";
|
import EndNode, {
|
||||||
import EndNode, { EndConnects, EndReduce } from "./nodes/EndNode";
|
EndConnectionTarget,
|
||||||
import PhaseNode, { PhaseConnects, PhaseReduce } from "./nodes/PhaseNode";
|
EndConnectionSource,
|
||||||
import NormNode, { NormConnects, NormReduce } from "./nodes/NormNode";
|
EndDisconnectionTarget,
|
||||||
|
EndDisconnectionSource,
|
||||||
|
EndReduce
|
||||||
|
} from "./nodes/EndNode";
|
||||||
import { EndNodeDefaults } from "./nodes/EndNode.default";
|
import { EndNodeDefaults } from "./nodes/EndNode.default";
|
||||||
|
import StartNode, {
|
||||||
|
StartConnectionTarget,
|
||||||
|
StartConnectionSource,
|
||||||
|
StartDisconnectionTarget,
|
||||||
|
StartDisconnectionSource,
|
||||||
|
StartReduce
|
||||||
|
} from "./nodes/StartNode";
|
||||||
import { StartNodeDefaults } from "./nodes/StartNode.default";
|
import { StartNodeDefaults } from "./nodes/StartNode.default";
|
||||||
|
import PhaseNode, {
|
||||||
|
PhaseConnectionTarget,
|
||||||
|
PhaseConnectionSource,
|
||||||
|
PhaseDisconnectionTarget,
|
||||||
|
PhaseDisconnectionSource,
|
||||||
|
PhaseReduce
|
||||||
|
} from "./nodes/PhaseNode";
|
||||||
import { PhaseNodeDefaults } from "./nodes/PhaseNode.default";
|
import { PhaseNodeDefaults } from "./nodes/PhaseNode.default";
|
||||||
|
import NormNode, {
|
||||||
|
NormConnectionTarget,
|
||||||
|
NormConnectionSource,
|
||||||
|
NormDisconnectionTarget,
|
||||||
|
NormDisconnectionSource,
|
||||||
|
NormReduce
|
||||||
|
} from "./nodes/NormNode";
|
||||||
import { NormNodeDefaults } from "./nodes/NormNode.default";
|
import { NormNodeDefaults } from "./nodes/NormNode.default";
|
||||||
import GoalNode, { GoalConnects, GoalReduce } from "./nodes/GoalNode";
|
import GoalNode, {
|
||||||
|
GoalConnectionTarget,
|
||||||
|
GoalConnectionSource,
|
||||||
|
GoalDisconnectionTarget,
|
||||||
|
GoalDisconnectionSource,
|
||||||
|
GoalReduce
|
||||||
|
} from "./nodes/GoalNode";
|
||||||
import { GoalNodeDefaults } from "./nodes/GoalNode.default";
|
import { GoalNodeDefaults } from "./nodes/GoalNode.default";
|
||||||
import TriggerNode, { TriggerConnects, TriggerReduce } from "./nodes/TriggerNode";
|
import TriggerNode, {
|
||||||
|
TriggerConnectionTarget,
|
||||||
|
TriggerConnectionSource,
|
||||||
|
TriggerDisconnectionTarget,
|
||||||
|
TriggerDisconnectionSource,
|
||||||
|
TriggerReduce
|
||||||
|
} from "./nodes/TriggerNode";
|
||||||
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
|
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,15 +96,51 @@ export const NodeReduces = {
|
|||||||
/**
|
/**
|
||||||
* Connection functions for each node type.
|
* Connection functions for each node type.
|
||||||
*
|
*
|
||||||
* These functions define how nodes of a particular type can connect to other nodes.
|
* These functions define any additional actions a node may perform
|
||||||
|
* when a new connection is made
|
||||||
*/
|
*/
|
||||||
export const NodeConnects = {
|
export const NodeConnections = {
|
||||||
start: StartConnects,
|
Targets: {
|
||||||
end: EndConnects,
|
start: StartConnectionTarget,
|
||||||
phase: PhaseConnects,
|
end: EndConnectionTarget,
|
||||||
norm: NormConnects,
|
phase: PhaseConnectionTarget,
|
||||||
goal: GoalConnects,
|
norm: NormConnectionTarget,
|
||||||
trigger: TriggerConnects,
|
goal: GoalConnectionTarget,
|
||||||
|
trigger: TriggerConnectionTarget,
|
||||||
|
},
|
||||||
|
Sources: {
|
||||||
|
start: StartConnectionSource,
|
||||||
|
end: EndConnectionSource,
|
||||||
|
phase: PhaseConnectionSource,
|
||||||
|
norm: NormConnectionSource,
|
||||||
|
goal: GoalConnectionSource,
|
||||||
|
trigger: TriggerConnectionSource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnection functions for each node type.
|
||||||
|
*
|
||||||
|
* These functions define any additional actions a node may perform
|
||||||
|
* when a connection is disconnected
|
||||||
|
*/
|
||||||
|
export const NodeDisconnections = {
|
||||||
|
Targets: {
|
||||||
|
start: StartDisconnectionTarget,
|
||||||
|
end: EndDisconnectionTarget,
|
||||||
|
phase: PhaseDisconnectionTarget,
|
||||||
|
norm: NormDisconnectionTarget,
|
||||||
|
goal: GoalDisconnectionTarget,
|
||||||
|
trigger: TriggerDisconnectionTarget,
|
||||||
|
},
|
||||||
|
Sources: {
|
||||||
|
start: StartDisconnectionSource,
|
||||||
|
end: EndDisconnectionSource,
|
||||||
|
phase: PhaseDisconnectionSource,
|
||||||
|
norm: NormDisconnectionSource,
|
||||||
|
goal: GoalDisconnectionSource,
|
||||||
|
trigger: TriggerDisconnectionSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import {
|
|||||||
type XYPosition,
|
type XYPosition,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import type { FlowState } from './VisProgTypes';
|
import type { FlowState } from './VisProgTypes';
|
||||||
import { NodeDefaults, NodeConnects, NodeDeletes } from './NodeRegistry';
|
import {
|
||||||
|
NodeDefaults,
|
||||||
|
NodeConnections as NodeCs,
|
||||||
|
NodeDisconnections as NodeDs,
|
||||||
|
NodeDeletes
|
||||||
|
} from './NodeRegistry';
|
||||||
import { UndoRedo } from "./EditorUndoRedo.ts";
|
import { UndoRedo } from "./EditorUndoRedo.ts";
|
||||||
|
|
||||||
|
|
||||||
@@ -71,10 +76,25 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
|
|||||||
*/
|
*/
|
||||||
onNodesChange: (changes) => set({nodes: applyNodeChanges(changes, get().nodes)}),
|
onNodesChange: (changes) => set({nodes: applyNodeChanges(changes, get().nodes)}),
|
||||||
|
|
||||||
|
onEdgesDelete: (edges) => {
|
||||||
|
|
||||||
|
// we make sure any affected nodes get updated to reflect removal of edges
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
const nodes = get().nodes;
|
||||||
|
|
||||||
|
const sourceNode = nodes.find((n) => n.id == edge.source);
|
||||||
|
const targetNode = nodes.find((n) => n.id == edge.target);
|
||||||
|
|
||||||
|
if (sourceNode) { NodeDs.Sources[sourceNode.type as keyof typeof NodeDs.Sources](sourceNode, edge.target); }
|
||||||
|
if (targetNode) { NodeDs.Targets[targetNode.type as keyof typeof NodeDs.Targets](targetNode, edge.source); }
|
||||||
|
});
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Handles changes to edges triggered by ReactFlow.
|
* Handles changes to edges triggered by ReactFlow.
|
||||||
*/
|
*/
|
||||||
onEdgesChange: (changes) => set({ edges: applyEdgeChanges(changes, get().edges) }),
|
onEdgesChange: (changes) => {
|
||||||
|
set({ edges: applyEdgeChanges(changes, get().edges) })
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles creating a new connection between nodes.
|
* Handles creating a new connection between nodes.
|
||||||
@@ -82,32 +102,16 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
|
|||||||
*/
|
*/
|
||||||
onConnect: (connection) => {
|
onConnect: (connection) => {
|
||||||
get().pushSnapshot();
|
get().pushSnapshot();
|
||||||
|
set({edges: addEdge(connection, get().edges)});
|
||||||
|
|
||||||
const edges = addEdge(connection, get().edges);
|
// We make sure to perform any required data updates on the newly connected nodes
|
||||||
const nodes = get().nodes;
|
const nodes = get().nodes;
|
||||||
// connection has: { source, sourceHandle, target, targetHandle }
|
|
||||||
// Let's find the source and target ID's.
|
|
||||||
const sourceNode = nodes.find((n) => n.id == connection.source);
|
const sourceNode = nodes.find((n) => n.id == connection.source);
|
||||||
const targetNode = nodes.find((n) => n.id == connection.target);
|
const targetNode = nodes.find((n) => n.id == connection.target);
|
||||||
|
|
||||||
// In case the nodes weren't found, return basic functionality.
|
if (sourceNode) { NodeCs.Sources[sourceNode.type as keyof typeof NodeCs.Sources](sourceNode, connection.target); }
|
||||||
if ( sourceNode == undefined
|
if (targetNode) { NodeCs.Targets[targetNode.type as keyof typeof NodeCs.Targets](targetNode, connection.source); }
|
||||||
|| targetNode == undefined
|
|
||||||
|| sourceNode.type == undefined
|
|
||||||
|| targetNode.type == undefined
|
|
||||||
){
|
|
||||||
set({ nodes, edges });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should find out how their data changes by calling their respective functions.
|
|
||||||
const sourceConnectFunction = NodeConnects[sourceNode.type as keyof typeof NodeConnects]
|
|
||||||
const targetConnectFunction = NodeConnects[targetNode.type as keyof typeof NodeConnects]
|
|
||||||
|
|
||||||
// We're going to have to update their data based on how they want to update it.
|
|
||||||
sourceConnectFunction(sourceNode, targetNode, true)
|
|
||||||
targetConnectFunction(targetNode, sourceNode, false)
|
|
||||||
set({ nodes, edges });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,6 +120,22 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
|
|||||||
onReconnect: (oldEdge, newConnection) => {
|
onReconnect: (oldEdge, newConnection) => {
|
||||||
get().edgeReconnectSuccessful = true;
|
get().edgeReconnectSuccessful = true;
|
||||||
set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) });
|
set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) });
|
||||||
|
|
||||||
|
// We make sure to perform any required data updates on the newly reconnected nodes
|
||||||
|
const nodes = get().nodes;
|
||||||
|
|
||||||
|
const oldSourceNode = nodes.find((n) => n.id == oldEdge.source)!;
|
||||||
|
const oldTargetNode = nodes.find((n) => n.id == oldEdge.target)!;
|
||||||
|
const newSourceNode = nodes.find((n) => n.id == newConnection.source)!;
|
||||||
|
const newTargetNode = nodes.find((n) => n.id == newConnection.target)!;
|
||||||
|
|
||||||
|
if (oldSourceNode === newSourceNode && oldTargetNode === newTargetNode) return;
|
||||||
|
|
||||||
|
NodeCs.Sources[newSourceNode.type as keyof typeof NodeCs.Sources](newSourceNode, newConnection.target);
|
||||||
|
NodeCs.Targets[newTargetNode.type as keyof typeof NodeCs.Targets](newTargetNode, newConnection.source);
|
||||||
|
|
||||||
|
NodeDs.Sources[oldSourceNode.type as keyof typeof NodeDs.Sources](oldSourceNode, oldEdge.target);
|
||||||
|
NodeDs.Targets[oldTargetNode.type as keyof typeof NodeDs.Targets](oldTargetNode, oldEdge.source);
|
||||||
},
|
},
|
||||||
|
|
||||||
onReconnectStart: () => {
|
onReconnectStart: () => {
|
||||||
@@ -128,11 +148,21 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
|
|||||||
* if it is not reconnected to a node after detaching it
|
* if it is not reconnected to a node after detaching it
|
||||||
*
|
*
|
||||||
* @param _evt - the event
|
* @param _evt - the event
|
||||||
* @param {{id: string}} edge - the described edge
|
* @param edge - the described edge
|
||||||
*/
|
*/
|
||||||
onReconnectEnd: (_evt, edge) => {
|
onReconnectEnd: (_evt, edge) => {
|
||||||
if (!get().edgeReconnectSuccessful) {
|
if (!get().edgeReconnectSuccessful) {
|
||||||
|
// delete the edge from the flowState
|
||||||
set({ edges: get().edges.filter((e) => e.id !== edge.id) });
|
set({ edges: get().edges.filter((e) => e.id !== edge.id) });
|
||||||
|
|
||||||
|
// update node data to reflect the dropped edge
|
||||||
|
const nodes = get().nodes;
|
||||||
|
|
||||||
|
const sourceNode = nodes.find((n) => n.id == edge.source)!;
|
||||||
|
const targetNode = nodes.find((n) => n.id == edge.target)!;
|
||||||
|
|
||||||
|
NodeDs.Sources[sourceNode.type as keyof typeof NodeDs.Sources](sourceNode, edge.target);
|
||||||
|
NodeDs.Targets[targetNode.type as keyof typeof NodeDs.Targets](targetNode, edge.source);
|
||||||
}
|
}
|
||||||
set({ edgeReconnectSuccessful: true });
|
set({ edgeReconnectSuccessful: true });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// VisProgTypes.ts
|
// VisProgTypes.ts
|
||||||
import type { Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect, Node } from '@xyflow/react';
|
import type {Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect, Node, OnEdgesDelete} from '@xyflow/react';
|
||||||
import type { NodeTypes } from './NodeRegistry';
|
import type { NodeTypes } from './NodeRegistry';
|
||||||
import type {FlowSnapshot} from "./EditorUndoRedo.ts";
|
import type {FlowSnapshot} from "./EditorUndoRedo.ts";
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ export type FlowState = {
|
|||||||
/** Handler for changes to nodes triggered by ReactFlow */
|
/** Handler for changes to nodes triggered by ReactFlow */
|
||||||
onNodesChange: OnNodesChange;
|
onNodesChange: OnNodesChange;
|
||||||
|
|
||||||
|
onEdgesDelete: OnEdgesDelete;
|
||||||
|
|
||||||
/** Handler for changes to edges triggered by ReactFlow */
|
/** Handler for changes to edges triggered by ReactFlow */
|
||||||
onEdgesChange: OnEdgesChange;
|
onEdgesChange: OnEdgesChange;
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ export type FlowState = {
|
|||||||
* @param _ - event or unused parameter
|
* @param _ - event or unused parameter
|
||||||
* @param edge - the edge that finished reconnecting
|
* @param edge - the edge that finished reconnecting
|
||||||
*/
|
*/
|
||||||
onReconnectEnd: (_: unknown, edge: { id: string }) => void;
|
onReconnectEnd: (_: unknown, edge: Edge) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a node and any connected edges.
|
* Deletes a node and any connected edges.
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import {
|
|||||||
type Node,
|
type Node,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { Toolbar } from '../components/NodeComponents';
|
import { Toolbar } from '../components/NodeComponents';
|
||||||
import styles from '../../VisProg.module.css';
|
import styles from '../../VisProg.module.css';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The typing of this node's data
|
* The typing of this node's data
|
||||||
@@ -51,10 +52,37 @@ export function EndReduce(node: Node, _nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any connection functionality that should get called when a connection is made to this node type (end)
|
* This function is called whenever a connection is made with this node type as the target
|
||||||
* @param _thisNode the node of which the functionality gets called
|
* @param _thisNode the node of this node type which function is called
|
||||||
* @param _otherNode the other node which has connected
|
* @param _sourceNodeId the source of the received 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 EndConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function EndConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function EndDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function EndDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
@@ -75,8 +75,8 @@ export default function GoalNode({id, data}: 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[]) {
|
||||||
const data = node.data as GoalNodeData;
|
const data = node.data as GoalNodeData;
|
||||||
@@ -89,11 +89,37 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called whenever a connection is made with this node type (Goal)
|
* This function is called whenever a connection is made with this node type as the target
|
||||||
* @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 _sourceNodeId the source of the received connection
|
||||||
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
|
|
||||||
*/
|
*/
|
||||||
export function GoalConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
export function GoalConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
// Replace this for connection logic
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function GoalConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function GoalDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function GoalDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,8 @@ 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[]) {
|
||||||
const data = node.data as NormNodeData;
|
const data = node.data as NormNodeData;
|
||||||
@@ -73,10 +73,37 @@ export function NormReduce(node: Node, _nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called whenever a connection is made with this node type (Norm)
|
* This function is called whenever a connection is made with this node type as the target
|
||||||
* @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 _sourceNodeId the source of the received connection
|
||||||
* @param _isThisSource whether this instance of the node was the source in the connection, true = yes.
|
|
||||||
*/
|
*/
|
||||||
export function NormConnects(_thisNode: Node, _otherNode: Node, _isThisSource: boolean) {
|
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function NormConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function NormDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,11 @@ import {
|
|||||||
Handle,
|
Handle,
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
Position,
|
Position,
|
||||||
type Node,
|
type Node
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { Toolbar } from '../components/NodeComponents';
|
import { Toolbar } from '../components/NodeComponents';
|
||||||
import styles from '../../VisProg.module.css';
|
import styles from '../../VisProg.module.css';
|
||||||
import { NodeReduces, NodesInPhase, NodeTypes } from '../NodeRegistry';
|
import { NodeReduces, NodesInPhase, NodeTypes} from '../NodeRegistry';
|
||||||
import useFlowStore from '../VisProgStores';
|
import useFlowStore from '../VisProgStores';
|
||||||
import { TextField } from '../../../../components/TextField';
|
import { TextField } from '../../../../components/TextField';
|
||||||
|
|
||||||
@@ -104,14 +104,45 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called whenever a connection is made with this node type (phase)
|
* This function is called whenever a connection is made with this node type as the target (phase)
|
||||||
* @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 _sourceNodeId the source of the received connection
|
||||||
* @param isThisSource whether this instance of the node was the source in the connection, true = yes.
|
|
||||||
*/
|
*/
|
||||||
export function PhaseConnects(thisNode: Node, otherNode: Node, isThisSource: boolean) {
|
export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
const node = thisNode as PhaseNode
|
const node = _thisNode as PhaseNode
|
||||||
const data = node.data as PhaseNodeData
|
const data = node.data as PhaseNodeData
|
||||||
if (!isThisSource)
|
// we only add none phase nodes to the children
|
||||||
data.children.push(otherNode.id)
|
if (!(useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'phase'))) {
|
||||||
|
data.children.push(_sourceNodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source (phase)
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target (phase)
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function PhaseDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
const node = _thisNode as PhaseNode
|
||||||
|
const data = node.data as PhaseNodeData
|
||||||
|
data.children = data.children.filter((child) => { if (child != _sourceNodeId) return child; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source (phase)
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function PhaseDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
@@ -51,10 +51,37 @@ 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 as the target
|
||||||
* @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 _sourceNodeId the source of the received connection
|
||||||
* @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 StartConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function StartConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function StartDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function StartDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
@@ -102,13 +102,39 @@ 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 as the target
|
||||||
* @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 _sourceNodeId the source of the received connection
|
||||||
* @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 TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is made with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the created connection
|
||||||
|
*/
|
||||||
|
export function TriggerConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the target
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
|
*/
|
||||||
|
export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called whenever a connection is disconnected with this node type as the source
|
||||||
|
* @param _thisNode the node of this node type which function is called
|
||||||
|
* @param _targetNodeId the target of the diconnected connection
|
||||||
|
*/
|
||||||
|
export function TriggerDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
||||||
|
// no additional connection logic exists yet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definitions for the possible triggers, being keywords and emotions
|
// Definitions for the possible triggers, being keywords and emotions
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import {act} from '@testing-library/react';
|
import {act} from '@testing-library/react';
|
||||||
|
import type {Connection, Edge, Node} from "@xyflow/react";
|
||||||
|
import { NodeDisconnections } from "../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts";
|
||||||
|
import type {PhaseNodeData} from "../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx";
|
||||||
import useFlowStore from '../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
|
import useFlowStore from '../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
|
||||||
import { mockReactFlow } from '../../../setupFlowTests.ts';
|
import { mockReactFlow } from '../../../setupFlowTests.ts';
|
||||||
|
|
||||||
@@ -6,18 +9,187 @@ beforeAll(() => {
|
|||||||
mockReactFlow();
|
mockReactFlow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// default state values for testing,
|
||||||
|
const normNode: Node = {
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'Test',
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const phaseNode: Node = {
|
||||||
|
id: 'phase-1',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 1',
|
||||||
|
droppable: true,
|
||||||
|
children: ["norm-1"],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const testEdge: Edge = {
|
||||||
|
id: 'xy-edge__1-2',
|
||||||
|
source: 'norm-1',
|
||||||
|
target: 'phase-1',
|
||||||
|
sourceHandle: null,
|
||||||
|
targetHandle: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const testStateReconnectEnd = {
|
||||||
|
nodes: [phaseNode, normNode],
|
||||||
|
edges: [testEdge],
|
||||||
|
}
|
||||||
|
|
||||||
|
const phaseNodeUnconnected = {
|
||||||
|
id: 'phase-2',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 2',
|
||||||
|
droppable: true,
|
||||||
|
children: [],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConnection: Connection = {
|
||||||
|
source: 'norm-1',
|
||||||
|
target: 'phase-2',
|
||||||
|
sourceHandle: null,
|
||||||
|
targetHandle: null,
|
||||||
|
}
|
||||||
|
const testStateOnConnect = {
|
||||||
|
nodes: [phaseNodeUnconnected, normNode],
|
||||||
|
edges: [],
|
||||||
|
}
|
||||||
|
|
||||||
describe('FlowStore Functionality', () => {
|
describe('FlowStore Functionality', () => {
|
||||||
describe('Node changes', () => {
|
describe('Node changes', () => {
|
||||||
// currently just using a single function from the ReactFlow library,
|
// currently just using a single function from the ReactFlow library,
|
||||||
// so testing would mean we are testing already tested behavior.
|
// so testing would mean we are testing already tested behavior.
|
||||||
// if implementation gets modified tests should be added for custom behavior
|
// if implementation gets modified tests should be added for custom behavior
|
||||||
});
|
});
|
||||||
|
describe('ReactFlow onEdgesDelete', () => {
|
||||||
|
test('Deleted edge is reflected in removed phaseNode child', () => {
|
||||||
|
const {onEdgesDelete} = useFlowStore.getState();
|
||||||
|
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [{
|
||||||
|
id: 'phase-1',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 1',
|
||||||
|
droppable: true,
|
||||||
|
children: ["norm-1"],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
},{
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'Test',
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||||
|
})
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
onEdgesDelete([testEdge])
|
||||||
|
});
|
||||||
|
|
||||||
|
const outcome = useFlowStore.getState();
|
||||||
|
expect((outcome.nodes[0].data as PhaseNodeData).children.length).toBe(0);
|
||||||
|
})
|
||||||
|
test('Deleted edge is reflected in phaseNode,even if normNode was already deleted and caused edge removal', () => {
|
||||||
|
const { onEdgesDelete } = useFlowStore.getState();
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [{
|
||||||
|
id: 'phase-1',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 1',
|
||||||
|
droppable: true,
|
||||||
|
children: ["norm-1"],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||||
|
})
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
onEdgesDelete([testEdge]);
|
||||||
|
})
|
||||||
|
|
||||||
|
const outcome = useFlowStore.getState();
|
||||||
|
expect((outcome.nodes[0].data as PhaseNodeData).children.length).toBe(0);
|
||||||
|
})
|
||||||
|
test('edge removal resulting from deletion of targetNode calls only the connection function for the sourceNode', () => {
|
||||||
|
const { onEdgesDelete } = useFlowStore.getState();
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [{
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'Test',
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
edges: [], // edges is empty as onEdgesDelete is triggered after the edges are deleted
|
||||||
|
})
|
||||||
|
|
||||||
|
const targetDisconnectSpy = jest.spyOn(NodeDisconnections.Targets, 'phase');
|
||||||
|
const sourceDisconnectSpy = jest.spyOn(NodeDisconnections.Sources, 'norm');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
onEdgesDelete([testEdge]);
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(sourceDisconnectSpy).toHaveBeenCalledWith(normNode, 'phase-1');
|
||||||
|
expect(targetDisconnectSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
sourceDisconnectSpy.mockRestore();
|
||||||
|
targetDisconnectSpy.mockRestore();
|
||||||
|
})
|
||||||
|
})
|
||||||
describe('Edge changes', () => {
|
describe('Edge changes', () => {
|
||||||
// currently just using a single function from the ReactFlow library,
|
// currently just using a single function from the ReactFlow library,
|
||||||
// so testing would mean we are testing already tested behavior.
|
// so testing would mean we are testing already tested behavior.
|
||||||
// if implementation gets modified tests should be added for custom behavior
|
// if implementation gets modified tests should be added for custom behavior
|
||||||
})
|
})
|
||||||
describe('ReactFlow onConnect', () => {
|
describe('ReactFlow onConnect', () => {
|
||||||
|
test('Adds connecting node to children of phaseNode', () => {
|
||||||
|
const {onConnect} = useFlowStore.getState();
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: testStateOnConnect.nodes,
|
||||||
|
edges: testStateOnConnect.edges
|
||||||
|
})
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
onConnect(testConnection);
|
||||||
|
})
|
||||||
|
|
||||||
|
const outcome = useFlowStore.getState();
|
||||||
|
|
||||||
|
// phaseNode adds the normNode to its children
|
||||||
|
expect((outcome.nodes[0].data as PhaseNodeData).children).toEqual(['norm-1']);
|
||||||
|
|
||||||
|
})
|
||||||
test('adds an edge when onConnect is triggered', () => {
|
test('adds an edge when onConnect is triggered', () => {
|
||||||
const {onConnect} = useFlowStore.getState();
|
const {onConnect} = useFlowStore.getState();
|
||||||
|
|
||||||
@@ -39,6 +211,53 @@ describe('FlowStore Functionality', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ReactFlow onReconnect', () => {
|
describe('ReactFlow onReconnect', () => {
|
||||||
|
test('PhaseNodes correctly change their children', () => {
|
||||||
|
const {onReconnect} = useFlowStore.getState();
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [{
|
||||||
|
id: 'phase-1',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 1',
|
||||||
|
droppable: true,
|
||||||
|
children: ["norm-1"],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
},{
|
||||||
|
id: 'phase-2',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 2',
|
||||||
|
droppable: true,
|
||||||
|
children: [],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
},{
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'Test',
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
edges: [testEdge],
|
||||||
|
})
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
onReconnect(testEdge, testConnection);
|
||||||
|
})
|
||||||
|
|
||||||
|
const outcome = useFlowStore.getState();
|
||||||
|
|
||||||
|
// phaseNodes lose and gain children when norm node's connection is changed from phaseNode to PhaseNodeUnconnected
|
||||||
|
expect((outcome.nodes[1].data as PhaseNodeData).children).toEqual(['norm-1']);
|
||||||
|
expect((outcome.nodes[0].data as PhaseNodeData).children).toEqual([]);
|
||||||
|
})
|
||||||
test('reconnects an existing edge when onReconnect is triggered', () => {
|
test('reconnects an existing edge when onReconnect is triggered', () => {
|
||||||
const {onReconnect} = useFlowStore.getState();
|
const {onReconnect} = useFlowStore.getState();
|
||||||
const oldEdge = {
|
const oldEdge = {
|
||||||
@@ -93,36 +312,63 @@ describe('FlowStore Functionality', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test('successfully removes edge if no successful reconnect occurred', () => {
|
test('successfully removes edge if no successful reconnect occurred', () => {
|
||||||
const {onReconnectEnd} = useFlowStore.getState();
|
const {onReconnectEnd} = useFlowStore.getState();
|
||||||
useFlowStore.setState({edgeReconnectSuccessful: false});
|
useFlowStore.setState({
|
||||||
|
edgeReconnectSuccessful: false,
|
||||||
|
edges: testStateReconnectEnd.edges,
|
||||||
|
nodes: testStateReconnectEnd.nodes
|
||||||
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
onReconnectEnd(null, {id: 'xy-edge__A-B'});
|
onReconnectEnd(null, testEdge);
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedState = useFlowStore.getState();
|
const updatedState = useFlowStore.getState();
|
||||||
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
||||||
expect(updatedState.edges).toHaveLength(0);
|
expect(updatedState.edges).toHaveLength(0);
|
||||||
|
expect(updatedState.nodes[0].data.children).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not remove reconnecting edge if successful reconnect occurred', () => {
|
test('does not remove reconnecting edge if successful reconnect occurred', () => {
|
||||||
const {onReconnectEnd} = useFlowStore.getState();
|
const {onReconnectEnd} = useFlowStore.getState();
|
||||||
|
useFlowStore.setState({
|
||||||
|
edgeReconnectSuccessful: true,
|
||||||
|
edges: [testEdge],
|
||||||
|
nodes: [{
|
||||||
|
id: 'phase-1',
|
||||||
|
type: 'phase',
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Phase 1',
|
||||||
|
droppable: true,
|
||||||
|
children: ["norm-1"],
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
},{
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'Test',
|
||||||
|
hasReduce: true,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
onReconnectEnd(null, {id: 'xy-edge__A-B'});
|
onReconnectEnd(null, testEdge);
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedState = useFlowStore.getState();
|
const updatedState = useFlowStore.getState();
|
||||||
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
expect(updatedState.edgeReconnectSuccessful).toBe(true);
|
||||||
expect(updatedState.edges).toHaveLength(1);
|
expect(updatedState.edges).toHaveLength(1);
|
||||||
expect(updatedState.edges).toMatchObject([
|
expect(updatedState.edges).toMatchObject([testEdge]);
|
||||||
{
|
expect(updatedState.nodes[0].data.children).toEqual(["norm-1"]);
|
||||||
id: 'xy-edge__A-B',
|
|
||||||
source: 'A',
|
|
||||||
target: 'B'
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ReactFlow deleteNode', () => {
|
describe('ReactFlow deleteNode', () => {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { describe, it, beforeEach } from '@jest/globals';
|
import { describe, it, beforeEach } from '@jest/globals';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||||
import NormNode, { NormReduce, NormConnects, type NormNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode'
|
import NormNode, {
|
||||||
|
NormReduce,
|
||||||
|
type NormNodeData,
|
||||||
|
NormConnectionSource, NormConnectionTarget
|
||||||
|
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode';
|
||||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||||
import type { Node } from '@xyflow/react';
|
import type { Node } from '@xyflow/react';
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
@@ -517,7 +521,7 @@ describe('NormNode', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
NormConnects(normNode, phaseNode, true);
|
NormConnectionSource(normNode, phaseNode.id);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -547,7 +551,7 @@ describe('NormNode', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
NormConnects(normNode, phaseNode, false);
|
NormConnectionTarget(normNode, phaseNode.id);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -565,7 +569,8 @@ describe('NormNode', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
NormConnects(normNode, normNode, true);
|
NormConnectionTarget(normNode, normNode.id);
|
||||||
|
NormConnectionSource(normNode, normNode.id);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import { describe, it } from '@jest/globals';
|
|||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import type { Node } from '@xyflow/react';
|
import type { Node } from '@xyflow/react';
|
||||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||||
import StartNode, { StartReduce, StartConnects } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode';
|
import StartNode, {
|
||||||
|
StartConnectionSource, StartConnectionTarget,
|
||||||
|
StartReduce
|
||||||
|
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode';
|
||||||
|
|
||||||
|
|
||||||
describe('StartNode', () => {
|
describe('StartNode', () => {
|
||||||
@@ -91,8 +94,8 @@ describe('StartNode', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(() => StartConnects(startNode, otherNode, true)).not.toThrow();
|
expect(() => StartConnectionSource(startNode, otherNode.id)).not.toThrow();
|
||||||
expect(() => StartConnects(startNode, otherNode, false)).not.toThrow();
|
expect(() => StartConnectionTarget(startNode, otherNode.id)).not.toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { describe, it, beforeEach } from '@jest/globals';
|
import { describe, it, beforeEach } from '@jest/globals';
|
||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||||
import TriggerNode, { TriggerReduce, TriggerConnects, TriggerNodeCanConnect, type TriggerNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode';
|
import TriggerNode, {
|
||||||
|
TriggerReduce,
|
||||||
|
TriggerNodeCanConnect,
|
||||||
|
type TriggerNodeData,
|
||||||
|
TriggerConnectionSource, TriggerConnectionTarget
|
||||||
|
} from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode';
|
||||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||||
import type { Node } from '@xyflow/react';
|
import type { Node } from '@xyflow/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
@@ -233,8 +238,8 @@ describe('TriggerNode', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
TriggerConnects(node1, node2, true);
|
TriggerConnectionSource(node1, node2.id);
|
||||||
TriggerConnects(node1, node2, false);
|
TriggerConnectionTarget(node1, node2.id);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { describe, beforeEach } from '@jest/globals';
|
import { describe, beforeEach } from '@jest/globals';
|
||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { renderWithProviders } from '../.././/./../../test-utils/test-utils';
|
import { renderWithProviders } from '../../../../test-utils/test-utils.tsx';
|
||||||
import type { XYPosition } from '@xyflow/react';
|
import type { XYPosition } from '@xyflow/react';
|
||||||
import { NodeTypes, NodeDefaults, NodeConnects, NodeReduces, NodesInPhase } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
import { NodeTypes, NodeDefaults, NodeConnections, NodeReduces, NodesInPhase } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/NodeRegistry';
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||||
@@ -87,8 +87,8 @@ describe('NormNode', () => {
|
|||||||
useFlowStore.setState({ nodes: [sourceNode, targetNode] });
|
useFlowStore.setState({ nodes: [sourceNode, targetNode] });
|
||||||
|
|
||||||
// Spy on the connect functions
|
// Spy on the connect functions
|
||||||
const sourceConnectSpy = jest.spyOn(NodeConnects, nodeType as keyof typeof NodeConnects);
|
const sourceConnectSpy = jest.spyOn(NodeConnections.Sources, nodeType as keyof typeof NodeConnections.Sources);
|
||||||
const targetConnectSpy = jest.spyOn(NodeConnects, 'end');
|
const targetConnectSpy = jest.spyOn(NodeConnections.Targets, 'end');
|
||||||
|
|
||||||
// Simulate connection
|
// Simulate connection
|
||||||
useFlowStore.getState().onConnect({
|
useFlowStore.getState().onConnect({
|
||||||
@@ -99,8 +99,8 @@ describe('NormNode', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Verify the connect functions were called
|
// Verify the connect functions were called
|
||||||
expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode, true);
|
expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode.id);
|
||||||
expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode, false);
|
expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode.id);
|
||||||
|
|
||||||
sourceConnectSpy.mockRestore();
|
sourceConnectSpy.mockRestore();
|
||||||
targetConnectSpy.mockRestore();
|
targetConnectSpy.mockRestore();
|
||||||
@@ -130,7 +130,7 @@ describe('NormNode', () => {
|
|||||||
expect(phaseReduceSpy).toHaveBeenCalledWith(phaseNode, [phaseNode, testNode]);
|
expect(phaseReduceSpy).toHaveBeenCalledWith(phaseNode, [phaseNode, testNode]);
|
||||||
// Check if this node type is in NodesInPhase and returns false
|
// Check if this node type is in NodesInPhase and returns false
|
||||||
const nodesInPhaseFunc = NodesInPhase[nodeType as keyof typeof NodesInPhase];
|
const nodesInPhaseFunc = NodesInPhase[nodeType as keyof typeof NodesInPhase];
|
||||||
if (nodesInPhaseFunc && nodesInPhaseFunc() === false && nodeType !== 'phase') {
|
if (nodesInPhaseFunc && !nodesInPhaseFunc() && nodeType !== 'phase') {
|
||||||
// Node is NOT in phase, so it should NOT be called
|
// Node is NOT in phase, so it should NOT be called
|
||||||
expect(nodeReduceSpy).not.toHaveBeenCalled();
|
expect(nodeReduceSpy).not.toHaveBeenCalled();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user