// This program has been developed by students from the bachelor Computer Science at Utrecht // University within the Software Project course. // © Copyright Utrecht University (Department of Information and Computing Sciences) import { describe, it, expect, jest, beforeEach } from '@jest/globals'; import {type Connection, getOutgoers, type Node} from '@xyflow/react'; import {ruleResult} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/HandleRuleLogic.ts"; import {BasicBeliefReduce} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx"; import { BeliefGlobalReduce, noBeliefCycles, noMatchingLeftRightBelief } from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BeliefGlobals.ts"; import { InferredBeliefReduce } from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx"; import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx"; import * as BasicModule from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode'; import * as InferredModule from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx'; import * as FlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx'; describe('BeliefGlobalReduce', () => { const nodes: Node[] = []; beforeEach(() => { jest.clearAllMocks(); }); it('delegates to BasicBeliefReduce for basic_belief nodes', () => { const spy = jest .spyOn(BasicModule, 'BasicBeliefReduce') .mockReturnValue('basic-result' as any); const node = { id: '1', type: 'basic_belief' } as Node; const result = BeliefGlobalReduce(node, nodes); expect(spy).toHaveBeenCalledWith(node, nodes); expect(result).toBe('basic-result'); }); it('delegates to InferredBeliefReduce for inferred_belief nodes', () => { const spy = jest .spyOn(InferredModule, 'InferredBeliefReduce') .mockReturnValue('inferred-result' as any); const node = { id: '2', type: 'inferred_belief' } as Node; const result = BeliefGlobalReduce(node, nodes); expect(spy).toHaveBeenCalledWith(node, nodes); expect(result).toBe('inferred-result'); }); it('returns undefined for unknown node types', () => { const node = { id: '3', type: 'other' } as Node; const result = BeliefGlobalReduce(node, nodes); expect(result).toBeUndefined(); expect(BasicBeliefReduce).not.toHaveBeenCalled(); expect(InferredBeliefReduce).not.toHaveBeenCalled(); }); }); describe('noMatchingLeftRightBelief rule', () => { let getStateSpy: ReturnType; beforeEach(() => { jest.clearAllMocks(); getStateSpy = jest.spyOn(FlowStore.default, 'getState'); }); it('is satisfied when target node is not an inferred belief', () => { getStateSpy.mockReturnValue({ nodes: [{ id: 't1', type: 'basic_belief' }], } as any); const result = noMatchingLeftRightBelief( { source: 's1', target: 't1' } as Connection, null as any ); expect(result).toBe(ruleResult.satisfied); }); it('is satisfied when inferred belief has no matching left/right', () => { getStateSpy.mockReturnValue({ nodes: [ { id: 't1', type: 'inferred_belief', data: { inferredBelief: { left: 'a', right: 'b', }, }, }, ], } as any); const result = noMatchingLeftRightBelief( { source: 'c', target: 't1' } as Connection, null as any ); expect(result).toBe(ruleResult.satisfied); }); it('is NOT satisfied when source matches left input', () => { getStateSpy.mockReturnValue({ nodes: [ { id: 't1', type: 'inferred_belief', data: { inferredBelief: { left: 's1', right: 's2', }, }, }, ], } as any); const result = noMatchingLeftRightBelief( { source: 's1', target: 't1' } as Connection, null as any ); expect(result.isSatisfied).toBe(false); if (!(result.isSatisfied)) { expect(result.message).toContain( 'Connecting one belief to both input handles of an inferred belief node is not allowed' ); } }); it('is NOT satisfied when source matches right input', () => { getStateSpy.mockReturnValue({ nodes: [ { id: 't1', type: 'inferred_belief', data: { inferredBelief: { left: 's1', right: 's2', }, }, }, ], } as any); const result = noMatchingLeftRightBelief( { source: 's2', target: 't1' } as Connection, null as any ); expect(result.isSatisfied).toBe(false); if (!(result.isSatisfied)) { expect(result.message).toContain( 'Connecting one belief to both input handles of an inferred belief node is not allowed' ); } }); }); jest.mock('@xyflow/react', () => ({ getOutgoers: jest.fn(), getConnectedEdges: jest.fn(), // include if some tests require it })); describe('noBeliefCycles rule', () => { beforeEach(() => { jest.clearAllMocks(); }); it('returns notSatisfied when source === target', () => { const result = noBeliefCycles({ source: 'n1', target: 'n1' } as any, null as any); expect(result.isSatisfied).toBe(false); if (!(result.isSatisfied)) { expect(result.message).toContain('Cyclical connection exists'); } }); it('returns satisfied when there are no outgoing inferred beliefs', () => { jest.spyOn(useFlowStore, 'getState').mockReturnValue({ nodes: [{ id: 'n1', type: 'inferred_belief' }], edges: [], } as any); (getOutgoers as jest.Mock).mockReturnValue([]); const result = noBeliefCycles({ source: 'n1', target: 'n2' } as any, null as any); expect(result).toBe(ruleResult.satisfied); }); it('returns notSatisfied for direct cycle', () => { jest.spyOn(useFlowStore, 'getState').mockReturnValue({ nodes: [ { id: 'n1', type: 'inferred_belief' }, { id: 'n2', type: 'inferred_belief' }, ], edges: [{ source: 'n2', target: 'n1' }], } as any); // @ts-expect-error is acting up (getOutgoers as jest.Mock).mockImplementation(({ id }) => { if (id === 'n2') return [{ id: 'n1', type: 'inferred_belief' }]; return []; }); const result = noBeliefCycles({ source: 'n1', target: 'n2' } as any, null as any); expect(result.isSatisfied).toBe(false); if (!(result.isSatisfied)) { expect(result.message).toContain('Cyclical connection exists'); } }); it('returns notSatisfied for indirect cycle', () => { jest.spyOn(useFlowStore, 'getState').mockReturnValue({ nodes: [ { id: 'A', type: 'inferred_belief' }, { id: 'B', type: 'inferred_belief' }, { id: 'C', type: 'inferred_belief' }, ], edges: [ { source: 'A', target: 'B' }, { source: 'B', target: 'C' }, { source: 'C', target: 'A' }, ], } as any); // @ts-expect-error is acting up (getOutgoers as jest.Mock).mockImplementation(({ id }) => { const mapping: Record = { A: [{ id: 'B', type: 'inferred_belief' }], B: [{ id: 'C', type: 'inferred_belief' }], C: [{ id: 'A', type: 'inferred_belief' }], }; return mapping[id] || []; }); const result = noBeliefCycles({ source: 'A', target: 'B' } as any, null as any); expect(result.isSatisfied).toBe(false); if (!(result.isSatisfied)) { expect(result.message).toContain('Cyclical connection exists'); } }); it('returns satisfied when no cycle exists in a multi-node graph', () => { jest.spyOn(useFlowStore, 'getState').mockReturnValue({ nodes: [ { id: 'A', type: 'inferred_belief' }, { id: 'B', type: 'inferred_belief' }, { id: 'C', type: 'inferred_belief' }, ], edges: [ { source: 'A', target: 'B' }, { source: 'B', target: 'C' }, ], } as any); // @ts-expect-error is acting up (getOutgoers as jest.Mock).mockImplementation(({ id }) => { const mapping: Record = { A: [{ id: 'B', type: 'inferred_belief' }], B: [{ id: 'C', type: 'inferred_belief' }], C: [], }; return mapping[id] || []; }); const result = noBeliefCycles({ source: 'A', target: 'B' } as any, null as any); expect(result).toBe(ruleResult.satisfied); }); });