277 lines
8.5 KiB
TypeScript
277 lines
8.5 KiB
TypeScript
// 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<typeof jest.spyOn>;
|
|
|
|
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<string, any[]> = {
|
|
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<string, any[]> = {
|
|
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);
|
|
});
|
|
}); |