Conditional Norms
This commit is contained in:
committed by
Gerla, J. (Justin)
parent
0ad2d5935f
commit
4e9a048c90
@@ -28,17 +28,20 @@ import { UndoRedo } from "./EditorUndoRedo.ts";
|
|||||||
* @param deletable - Optional flag to indicate if the node can be deleted (can be deleted by default).
|
* @param deletable - Optional flag to indicate if the node can be deleted (can be deleted by default).
|
||||||
* @returns A fully initialized Node object ready to be added to the flow.
|
* @returns A fully initialized Node object ready to be added to the flow.
|
||||||
*/
|
*/
|
||||||
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]
|
||||||
|
return {
|
||||||
return {
|
id,
|
||||||
id: id,
|
type,
|
||||||
type: type,
|
position,
|
||||||
position: position,
|
deletable,
|
||||||
data: {...defaultData, ...data},
|
data: {
|
||||||
deletable: deletable
|
...JSON.parse(JSON.stringify(defaultData)),
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//* Initial nodes, created by using createNode. */
|
//* Initial nodes, created by using createNode. */
|
||||||
const initialNodes : Node[] = [
|
const initialNodes : Node[] = [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { NormNodeData } from "./NormNode";
|
|||||||
export const NormNodeDefaults: NormNodeData = {
|
export const NormNodeDefaults: NormNodeData = {
|
||||||
label: "Norm Node",
|
label: "Norm Node",
|
||||||
droppable: true,
|
droppable: true,
|
||||||
|
conditions: [],
|
||||||
norm: "",
|
norm: "",
|
||||||
hasReduce: true,
|
hasReduce: true,
|
||||||
critical: false,
|
critical: false,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Toolbar } from '../components/NodeComponents';
|
|||||||
import styles from '../../VisProg.module.css';
|
import styles from '../../VisProg.module.css';
|
||||||
import { TextField } from '../../../../components/TextField';
|
import { TextField } from '../../../../components/TextField';
|
||||||
import useFlowStore from '../VisProgStores';
|
import useFlowStore from '../VisProgStores';
|
||||||
|
import { BasicBeliefReduce } from './BasicBeliefNode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default data dot a phase node
|
* The default data dot a phase node
|
||||||
@@ -19,6 +20,7 @@ import useFlowStore from '../VisProgStores';
|
|||||||
export type NormNodeData = {
|
export type NormNodeData = {
|
||||||
label: string;
|
label: string;
|
||||||
droppable: boolean;
|
droppable: boolean;
|
||||||
|
conditions: string[]; // List of (basic) belief nodes' ids.
|
||||||
norm: string;
|
norm: string;
|
||||||
hasReduce: boolean;
|
hasReduce: boolean;
|
||||||
critical: boolean;
|
critical: boolean;
|
||||||
@@ -67,7 +69,14 @@ export default function NormNode(props: NodeProps<NormNode>) {
|
|||||||
onChange={(e) => setCritical(e.target.checked)}
|
onChange={(e) => setCritical(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{data.conditions.length > 0 && (<div className={"flex-row gap-md align-center"} data-testid="norm-condition-information">
|
||||||
|
<label htmlFor={checkbox_id}>{data.conditions.length} condition{data.conditions.length > 1 ? "s" : ""}/ belief{data.conditions.length > 1 ? "s" : ""} attached.</label>
|
||||||
|
</div>)}
|
||||||
|
|
||||||
|
|
||||||
<Handle type="source" position={Position.Right} id="norms"/>
|
<Handle type="source" position={Position.Right} id="norms"/>
|
||||||
|
<Handle type="target" position={Position.Bottom} id="norms"/>
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
@@ -78,14 +87,29 @@ export default function NormNode(props: NodeProps<NormNode>) {
|
|||||||
* @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;
|
||||||
return {
|
|
||||||
id: node.id,
|
// conditions nodes - make sure to check for empty arrays
|
||||||
label: data.label,
|
let conditionNodes: Node[] = [];
|
||||||
norm: data.norm,
|
if (data.conditions)
|
||||||
critical: data.critical,
|
conditionNodes = nodes.filter((node) => data.conditions.includes(node.id));
|
||||||
}
|
|
||||||
|
// Build the result object
|
||||||
|
const result: Record<string, unknown> = {
|
||||||
|
id: node.id,
|
||||||
|
label: data.label,
|
||||||
|
norm: data.norm,
|
||||||
|
critical: data.critical,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go over our conditionNodes. They should either be Basic (OR TODO: Inferred)
|
||||||
|
const reducer = BasicBeliefReduce;
|
||||||
|
result["basic_beliefs"] = conditionNodes.map((condition) => reducer(condition, nodes))
|
||||||
|
|
||||||
|
// When the Inferred is being implemented, you should follow the same kind of structure that PhaseNode has,
|
||||||
|
// dividing the conditions into basic and inferred, then calling the correct reducer on them.
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +118,11 @@ export function NormReduce(node: Node, _nodes: Node[]) {
|
|||||||
* @param _sourceNodeId the source of the received connection
|
* @param _sourceNodeId the source of the received connection
|
||||||
*/
|
*/
|
||||||
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
// no additional connection logic exists yet
|
const data = _thisNode.data as NormNodeData;
|
||||||
|
// If we got a belief connected, this is a condition for the norm.
|
||||||
|
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
||||||
|
data.conditions.push(_sourceNodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,7 +140,11 @@ export function NormConnectionSource(_thisNode: Node, _targetNodeId: string) {
|
|||||||
* @param _sourceNodeId the source of the disconnected connection
|
* @param _sourceNodeId the source of the disconnected connection
|
||||||
*/
|
*/
|
||||||
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
||||||
// no additional connection logic exists yet
|
const data = _thisNode.data as NormNodeData;
|
||||||
|
// If we got a belief connected, this is a condition for the norm.
|
||||||
|
if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
|
||||||
|
data.conditions = data.conditions.filter(id => id != _sourceNodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import 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'
|
||||||
|
import { NormNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts';
|
||||||
|
import { BasicBeliefNodeDefaults } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts';
|
||||||
|
import BasicBeliefNode, { BasicBeliefConnectionSource } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx';
|
||||||
|
|
||||||
describe('NormNode', () => {
|
describe('NormNode', () => {
|
||||||
let user: ReturnType<typeof userEvent.setup>;
|
let user: ReturnType<typeof userEvent.setup>;
|
||||||
@@ -26,12 +27,7 @@ describe('NormNode', () => {
|
|||||||
id: 'norm-1',
|
id: 'norm-1',
|
||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {...JSON.parse(JSON.stringify(NormNodeDefaults))},
|
||||||
label: 'Test Norm',
|
|
||||||
droppable: true,
|
|
||||||
norm: '',
|
|
||||||
hasReduce: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
@@ -60,6 +56,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Be respectful to humans',
|
norm: 'Be respectful to humans',
|
||||||
@@ -94,8 +91,10 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
|
conditions: [],
|
||||||
norm: '',
|
norm: '',
|
||||||
hasReduce: true,
|
hasReduce: true,
|
||||||
critical: false
|
critical: false
|
||||||
@@ -129,6 +128,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Dragged norm',
|
norm: 'Dragged norm',
|
||||||
@@ -165,6 +165,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -210,6 +211,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Initial norm text',
|
norm: 'Initial norm text',
|
||||||
@@ -261,6 +263,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -314,6 +317,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -358,6 +362,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -404,6 +409,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Safety Norm',
|
label: 'Safety Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Never harm humans',
|
norm: 'Never harm humans',
|
||||||
@@ -418,6 +424,8 @@ describe('NormNode', () => {
|
|||||||
id: 'norm-1',
|
id: 'norm-1',
|
||||||
label: 'Safety Norm',
|
label: 'Safety Norm',
|
||||||
norm: 'Never harm humans',
|
norm: 'Never harm humans',
|
||||||
|
critical: false,
|
||||||
|
basic_beliefs: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -427,6 +435,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Norm 1',
|
label: 'Norm 1',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Be helpful',
|
norm: 'Be helpful',
|
||||||
@@ -439,6 +448,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 100, y: 0 },
|
position: { x: 100, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Norm 2',
|
label: 'Norm 2',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Be honest',
|
norm: 'Be honest',
|
||||||
@@ -463,6 +473,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Empty Norm',
|
label: 'Empty Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -482,6 +493,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Custom Label',
|
label: 'Custom Label',
|
||||||
droppable: false,
|
droppable: false,
|
||||||
norm: 'Test norm',
|
norm: 'Test norm',
|
||||||
@@ -502,6 +514,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Test',
|
norm: 'Test',
|
||||||
@@ -514,6 +527,7 @@ describe('NormNode', () => {
|
|||||||
type: 'phase',
|
type: 'phase',
|
||||||
position: { x: 100, y: 0 },
|
position: { x: 100, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Phase 1',
|
label: 'Phase 1',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
children: [],
|
children: [],
|
||||||
@@ -532,6 +546,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Test',
|
norm: 'Test',
|
||||||
@@ -544,6 +559,7 @@ describe('NormNode', () => {
|
|||||||
type: 'phase',
|
type: 'phase',
|
||||||
position: { x: 100, y: 0 },
|
position: { x: 100, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Phase 1',
|
label: 'Phase 1',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
children: [],
|
children: [],
|
||||||
@@ -562,6 +578,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...NormNodeDefaults,
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Test',
|
norm: 'Test',
|
||||||
@@ -583,6 +600,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -634,6 +652,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: '',
|
norm: '',
|
||||||
@@ -682,6 +701,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Norm 1',
|
label: 'Norm 1',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Original norm 1',
|
norm: 'Original norm 1',
|
||||||
@@ -694,6 +714,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 100, y: 0 },
|
position: { x: 100, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
label: 'Norm 2',
|
label: 'Norm 2',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'Original norm 2',
|
norm: 'Original norm 2',
|
||||||
@@ -748,6 +769,7 @@ describe('NormNode', () => {
|
|||||||
type: 'norm',
|
type: 'norm',
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
data: {
|
data: {
|
||||||
|
...NormNodeDefaults,
|
||||||
label: 'Test Norm',
|
label: 'Test Norm',
|
||||||
droppable: true,
|
droppable: true,
|
||||||
norm: 'haa haa fuyaaah - link',
|
norm: 'haa haa fuyaaah - link',
|
||||||
@@ -778,21 +800,154 @@ describe('NormNode', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const input = screen.getByPlaceholderText('Pepper should ...');
|
const input = screen.getByPlaceholderText('Pepper should ...');
|
||||||
|
expect(input).toBeDefined()
|
||||||
|
|
||||||
await user.type(input, 'a');
|
await user.type(input, 'a{enter}');
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
|
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - linka');
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.type(input, 'b');
|
await user.type(input, 'b{enter}');
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
|
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - linkab');
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.type(input, 'c');
|
await user.type(input, 'c{enter}');
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link');
|
expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - linkabc');
|
||||||
}, { timeout: 3000 });
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Integration beliefs', () => {
|
||||||
|
it('should update visually when adding beliefs', async () => {
|
||||||
|
// Setup state
|
||||||
|
const mockNode: Node = {
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'haa haa fuyaaah - link',
|
||||||
|
hasReduce: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBelief: Node = {
|
||||||
|
id: 'basic_belief-1',
|
||||||
|
type: 'basic_belief',
|
||||||
|
position: {x:100, y:100},
|
||||||
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(BasicBeliefNodeDefaults))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [mockNode, mockBelief],
|
||||||
|
edges: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate connecting
|
||||||
|
NormConnectionTarget(mockNode, mockBelief.id);
|
||||||
|
BasicBeliefConnectionSource(mockBelief, mockNode.id)
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<div>
|
||||||
|
<NormNode
|
||||||
|
id={mockNode.id}
|
||||||
|
type={mockNode.type as string}
|
||||||
|
data={mockNode.data as any}
|
||||||
|
selected={false}
|
||||||
|
isConnectable={true}
|
||||||
|
zIndex={0}
|
||||||
|
dragging={false}
|
||||||
|
selectable={true}
|
||||||
|
deletable={true}
|
||||||
|
draggable={true}
|
||||||
|
positionAbsoluteX={0}
|
||||||
|
positionAbsoluteY={0}
|
||||||
|
/>
|
||||||
|
<BasicBeliefNode
|
||||||
|
id={mockBelief.id}
|
||||||
|
type={mockBelief.type as string}
|
||||||
|
data={mockBelief.data as any}
|
||||||
|
selected={false}
|
||||||
|
isConnectable={true}
|
||||||
|
zIndex={0}
|
||||||
|
dragging={false}
|
||||||
|
selectable={true}
|
||||||
|
deletable={true}
|
||||||
|
draggable={true}
|
||||||
|
positionAbsoluteX={0}
|
||||||
|
positionAbsoluteY={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('norm-condition-information')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the data when adding beliefs', async () => {
|
||||||
|
// Setup state
|
||||||
|
const mockNode: Node = {
|
||||||
|
id: 'norm-1',
|
||||||
|
type: 'norm',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(NormNodeDefaults)),
|
||||||
|
label: 'Test Norm',
|
||||||
|
droppable: true,
|
||||||
|
norm: 'haa haa fuyaaah - link',
|
||||||
|
hasReduce: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBelief1: Node = {
|
||||||
|
id: 'basic_belief-1',
|
||||||
|
type: 'basic_belief',
|
||||||
|
position: {x:100, y:100},
|
||||||
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(BasicBeliefNodeDefaults))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockBelief2: Node = {
|
||||||
|
id: 'basic_belief-2',
|
||||||
|
type: 'basic_belief',
|
||||||
|
position: {x:300, y:300},
|
||||||
|
data: {
|
||||||
|
...JSON.parse(JSON.stringify(BasicBeliefNodeDefaults))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useFlowStore.setState({
|
||||||
|
nodes: [mockNode, mockBelief1, mockBelief2],
|
||||||
|
edges: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate connecting
|
||||||
|
useFlowStore.getState().onConnect({
|
||||||
|
source: 'basic_belief-1',
|
||||||
|
target: 'norm-1',
|
||||||
|
sourceHandle: null,
|
||||||
|
targetHandle: null,
|
||||||
|
});
|
||||||
|
useFlowStore.getState().onConnect({
|
||||||
|
source: 'basic_belief-2',
|
||||||
|
target: 'norm-1',
|
||||||
|
sourceHandle: null,
|
||||||
|
targetHandle: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = useFlowStore.getState();
|
||||||
|
const updatedNorm = state.nodes.find(n => n.id === 'norm-1');
|
||||||
|
expect(updatedNorm?.data.conditions).toEqual(["basic_belief-1", "basic_belief-2"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -8,7 +8,7 @@ import { createElement } from 'react';
|
|||||||
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
|
||||||
|
|
||||||
|
|
||||||
describe('NormNode', () => {
|
describe('Universal Nodes', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
@@ -107,6 +107,50 @@ describe('NormNode', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Disconnecting', () => {
|
||||||
|
test.each(getAllTypes())('it should remove the correct data when something is disconnected on a %s node.', (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', 'basic_belief', {x: 300, y: 100}, {});
|
||||||
|
|
||||||
|
// Add nodes to store
|
||||||
|
useFlowStore.setState({ nodes: [sourceNode, targetNode] });
|
||||||
|
|
||||||
|
// Spy on the connect functions
|
||||||
|
const sourceConnectSpy = jest.spyOn(NodeConnections.Sources, nodeType as keyof typeof NodeConnections.Sources);
|
||||||
|
const targetConnectSpy = jest.spyOn(NodeConnections.Targets, 'basic_belief');
|
||||||
|
|
||||||
|
// Simulate connection
|
||||||
|
useFlowStore.getState().onConnect({
|
||||||
|
source: 'source-1',
|
||||||
|
target: 'target-1',
|
||||||
|
sourceHandle: null,
|
||||||
|
targetHandle: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Verify the connect functions were called
|
||||||
|
expect(sourceConnectSpy).toHaveBeenCalledWith(sourceNode, targetNode.id);
|
||||||
|
expect(targetConnectSpy).toHaveBeenCalledWith(targetNode, sourceNode.id);
|
||||||
|
|
||||||
|
// Find this connection, and delete it
|
||||||
|
const edge = useFlowStore.getState().edges[0];
|
||||||
|
useFlowStore.getState().onEdgesDelete([edge]);
|
||||||
|
|
||||||
|
// Find the nodes in the flow
|
||||||
|
const newSourceNode = useFlowStore.getState().nodes.find((node) => node.id == "source-1");
|
||||||
|
const newTargetNode = useFlowStore.getState().nodes.find((node) => node.id == "target-1");
|
||||||
|
|
||||||
|
// Expect them to be the same after deleting the edges
|
||||||
|
expect(newSourceNode).toBe(sourceNode);
|
||||||
|
expect(newTargetNode).toBe(targetNode);
|
||||||
|
|
||||||
|
// Restore our spies
|
||||||
|
sourceConnectSpy.mockRestore();
|
||||||
|
targetConnectSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Reducing', () => {
|
describe('Reducing', () => {
|
||||||
test.each(getAllTypes())('it should correctly call/ not call the reduce function when %s node is in a phase', (nodeType) => {
|
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
|
// Create a phase node and a node of the current type
|
||||||
|
|||||||
Reference in New Issue
Block a user