diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts index 4b4a3ed..8df25cc 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts @@ -6,6 +6,7 @@ import type { NormNodeData } from "./NormNode"; export const NormNodeDefaults: NormNodeData = { label: "Norm Node", droppable: true, + conditions: [], norm: "", hasReduce: true, critical: false, diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx index 348b95d..371ab77 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx @@ -8,6 +8,7 @@ import { Toolbar } from '../components/NodeComponents'; import styles from '../../VisProg.module.css'; import { TextField } from '../../../../components/TextField'; import useFlowStore from '../VisProgStores'; +import { BasicBeliefReduce } from './BasicBeliefNode'; /** * The default data dot a phase node @@ -19,6 +20,7 @@ import useFlowStore from '../VisProgStores'; export type NormNodeData = { label: string; droppable: boolean; + conditions: string[]; // List of (basic) belief nodes' ids. norm: string; hasReduce: boolean; critical: boolean; @@ -67,7 +69,14 @@ export default function NormNode(props: NodeProps) { onChange={(e) => setAchieved(e.target.checked)} /> + + {data.conditions.length > 0 && (
+ +
)} + + + ; }; @@ -78,14 +87,29 @@ export default function NormNode(props: NodeProps) { * @param node The Node Properties of this node. * @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; - return { - id: node.id, - label: data.label, - norm: data.norm, - critical: data.critical, - } + + // conditions nodes - make sure to check for empty arrays + let conditionNodes: Node[] = []; + if (data.conditions) + conditionNodes = nodes.filter((node) => data.conditions.includes(node.id)); + + // Build the result object + const result: Record = { + 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,12 @@ export function NormReduce(node: Node, _nodes: Node[]) { * @param _sourceNodeId the source of the received connection */ 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); + } } /** diff --git a/test/pages/visProgPage/visualProgrammingUI/nodes/NormNode.test.tsx b/test/pages/visProgPage/visualProgrammingUI/nodes/NormNode.test.tsx index a9848b2..272efbc 100644 --- a/test/pages/visProgPage/visualProgrammingUI/nodes/NormNode.test.tsx +++ b/test/pages/visProgPage/visualProgrammingUI/nodes/NormNode.test.tsx @@ -10,8 +10,9 @@ import NormNode, { import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores'; import type { Node } from '@xyflow/react'; 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', () => { let user: ReturnType; @@ -26,12 +27,7 @@ describe('NormNode', () => { id: 'norm-1', type: 'norm', position: { x: 0, y: 0 }, - data: { - label: 'Test Norm', - droppable: true, - norm: '', - hasReduce: true, - }, + data: NormNodeDefaults, }; renderWithProviders( @@ -60,6 +56,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Be respectful to humans', @@ -94,8 +91,10 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, + conditions: [], norm: '', hasReduce: true, critical: false @@ -129,6 +128,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Dragged norm', @@ -165,6 +165,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -210,6 +211,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Initial norm text', @@ -261,6 +263,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -314,6 +317,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -358,6 +362,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -404,6 +409,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Safety Norm', droppable: true, norm: 'Never harm humans', @@ -418,6 +424,8 @@ describe('NormNode', () => { id: 'norm-1', label: 'Safety Norm', norm: 'Never harm humans', + critical: false, + basic_beliefs: [], }); }); @@ -427,6 +435,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Norm 1', droppable: true, norm: 'Be helpful', @@ -439,6 +448,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 100, y: 0 }, data: { + ...NormNodeDefaults, label: 'Norm 2', droppable: true, norm: 'Be honest', @@ -463,6 +473,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Empty Norm', droppable: true, norm: '', @@ -482,6 +493,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Custom Label', droppable: false, norm: 'Test norm', @@ -502,6 +514,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Test', @@ -514,6 +527,7 @@ describe('NormNode', () => { type: 'phase', position: { x: 100, y: 0 }, data: { + ...NormNodeDefaults, label: 'Phase 1', droppable: true, children: [], @@ -532,6 +546,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Test', @@ -544,6 +559,7 @@ describe('NormNode', () => { type: 'phase', position: { x: 100, y: 0 }, data: { + ...NormNodeDefaults, label: 'Phase 1', droppable: true, children: [], @@ -562,6 +578,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'Test', @@ -583,6 +600,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -634,6 +652,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: '', @@ -682,6 +701,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Norm 1', droppable: true, norm: 'Original norm 1', @@ -694,6 +714,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 100, y: 0 }, data: { + ...NormNodeDefaults, label: 'Norm 2', droppable: true, norm: 'Original norm 2', @@ -748,6 +769,7 @@ describe('NormNode', () => { type: 'norm', position: { x: 0, y: 0 }, data: { + ...NormNodeDefaults, label: 'Test Norm', droppable: true, norm: 'haa haa fuyaaah - link', @@ -778,21 +800,147 @@ describe('NormNode', () => { ); const input = screen.getByPlaceholderText('Pepper should ...'); + expect(input).toBeDefined() - await user.type(input, 'a'); + await user.type(input, 'a{enter}'); 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(() => { - 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(() => { - expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - link'); - }, { timeout: 3000 }); + expect(useFlowStore.getState().nodes[0].data.norm).toBe('haa haa fuyaaah - linkabc'); + }); }); }); + + 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: { + ...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: { + ...BasicBeliefNodeDefaults + } + }; + + useFlowStore.setState({ + nodes: [mockNode, mockBelief], + edges: [], + }); + + // Simulate connecting + NormConnectionTarget(mockNode, mockBelief.id); + BasicBeliefConnectionSource(mockBelief, mockNode.id) + + renderWithProviders( +
+ + +
+ ); + + 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: { + ...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: { + ...BasicBeliefNodeDefaults + } + }; + + const mockBelief2: Node = { + id: 'basic_belief-2', + type: 'basic_belief', + position: {x:300, y:300}, + data: { + ...BasicBeliefNodeDefaults + } + }; + + useFlowStore.setState({ + nodes: [mockNode, mockBelief1, mockBelief2], + edges: [], + }); + + // Simulate connecting + NormConnectionTarget(mockNode, mockBelief1.id); + NormConnectionTarget(mockNode, mockBelief2.id); + BasicBeliefConnectionSource(mockBelief1, mockNode.id); + BasicBeliefConnectionSource(mockBelief2, mockNode.id); + + const state = useFlowStore.getState(); + const updatedNorm = state.nodes.find(n => n.id === 'norm-1'); + expect(updatedNorm?.data.conditions).toBe(["basic_belief-1", "basic_belief-2"]); + }); + + + + }); }); \ No newline at end of file