Merge remote-tracking branch 'origin/dev' into feat/save-load-nodes
This commit is contained in:
104
test/pages/connectedRobots/ConnectedRobots.test.tsx
Normal file
104
test/pages/connectedRobots/ConnectedRobots.test.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import { render, screen, act, cleanup, waitFor } from '@testing-library/react';
|
||||
import ConnectedRobots from '../../../src/pages/ConnectedRobots/ConnectedRobots';
|
||||
|
||||
// Mock event source
|
||||
const mockInstances: MockEventSource[] = [];
|
||||
class MockEventSource {
|
||||
url: string;
|
||||
onmessage: ((event: MessageEvent) => void) | null = null;
|
||||
closed = false;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
mockInstances.push(this);
|
||||
}
|
||||
|
||||
sendMessage(data: string) {
|
||||
// Trigger whatever the component listens to
|
||||
this.onmessage?.({ data } as MessageEvent);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// mock event source generation with fake function that returns our fake mock source
|
||||
beforeAll(() => {
|
||||
// Cast globalThis to a type exposing EventSource and assign a mocked constructor.
|
||||
(globalThis as unknown as { EventSource?: typeof EventSource }).EventSource =
|
||||
jest.fn((url: string) => new MockEventSource(url)) as unknown as typeof EventSource;
|
||||
});
|
||||
|
||||
// clean after tests
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
jest.restoreAllMocks();
|
||||
mockInstances.length = 0;
|
||||
});
|
||||
|
||||
describe('ConnectedRobots', () => {
|
||||
test('renders initial state correctly', () => {
|
||||
render(<ConnectedRobots />);
|
||||
|
||||
// Check initial texts (before connection)
|
||||
expect(screen.getByText('Is robot currently connected?')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Robot is currently:\s*checking/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/If checking continues, make sure CB is properly loaded/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('updates to connected when message data is true', async () => {
|
||||
render(<ConnectedRobots />);
|
||||
const eventSource = mockInstances[0];
|
||||
expect(eventSource).toBeDefined();
|
||||
|
||||
// Check state after getting 'true' message
|
||||
await act(async () => {
|
||||
eventSource.sendMessage('true');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/connected! 🟢/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('updates to not connected when message data is false', async () => {
|
||||
render(<ConnectedRobots />);
|
||||
const eventSource = mockInstances[0];
|
||||
|
||||
// Check statew after getting 'false' message
|
||||
await act(async () => {
|
||||
eventSource.sendMessage('false');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/not connected.*🔴/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('handles invalid JSON gracefully', async () => {
|
||||
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
render(<ConnectedRobots />);
|
||||
const eventSource = mockInstances[0];
|
||||
|
||||
await act(async () => {
|
||||
eventSource.sendMessage('not-json');
|
||||
});
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Ping message not in correct format:',
|
||||
'not-json'
|
||||
);
|
||||
});
|
||||
|
||||
test('closes EventSource on unmount', () => {
|
||||
render(<ConnectedRobots />);
|
||||
const eventSource = mockInstances[0];
|
||||
const closeSpy = jest.spyOn(eventSource, 'close');
|
||||
cleanup();
|
||||
expect(closeSpy).toHaveBeenCalled();
|
||||
expect(eventSource.closed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,986 +1,5 @@
|
||||
import type {Edge} from "@xyflow/react";
|
||||
import graphReducer, {
|
||||
defaultGraphPreprocessor, defaultPhaseReducer,
|
||||
orderPhases
|
||||
} from "../../../../src/pages/VisProgPage/visualProgrammingUI/GraphReducer.ts";
|
||||
import type {PreparedPhase} from "../../../../src/pages/VisProgPage/visualProgrammingUI/GraphReducerTypes.ts";
|
||||
import useFlowStore from "../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";
|
||||
import type {AppNode} from "../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgTypes.tsx";
|
||||
|
||||
// sets of default values for nodes and edges to be used for test cases
|
||||
type FlowState = {
|
||||
name: string;
|
||||
nodes: AppNode[];
|
||||
edges: Edge[];
|
||||
};
|
||||
|
||||
// predefined graphs for testing:
|
||||
const onlyOnePhase : FlowState = {
|
||||
name: "onlyOnePhase",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-end',
|
||||
source: 'phase-1',
|
||||
target: 'end',
|
||||
}
|
||||
]
|
||||
};
|
||||
const onlyThreePhases : FlowState = {
|
||||
name: "onlyThreePhases",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-phase-2',
|
||||
source: 'phase-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'phase-2-phase-3',
|
||||
source: 'phase-2',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'phase-3-end',
|
||||
source: 'phase-3',
|
||||
target: 'end',
|
||||
}
|
||||
]
|
||||
};
|
||||
const onlySingleEdgeNorms : FlowState = {
|
||||
name: "onlySingleEdgeNorms",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'norm-1-phase-2',
|
||||
source: 'norm-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-phase-2',
|
||||
source: 'phase-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'phase-2-phase-3',
|
||||
source: 'phase-2',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'norm-2-phase-3',
|
||||
source: 'norm-2',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'phase-3-end',
|
||||
source: 'phase-3',
|
||||
target: 'end',
|
||||
}
|
||||
]
|
||||
};
|
||||
const multiEdgeNorms : FlowState = {
|
||||
name: "multiEdgeNorms",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
{
|
||||
id: 'norm-3',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'norm-1-phase-2',
|
||||
source: 'norm-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'norm-1-phase-3',
|
||||
source: 'norm-1',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-phase-2',
|
||||
source: 'phase-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'norm-3-phase-1',
|
||||
source: 'norm-3',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'phase-2-phase-3',
|
||||
source: 'phase-2',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'norm-2-phase-3',
|
||||
source: 'norm-2',
|
||||
target: 'phase-3',
|
||||
},
|
||||
{
|
||||
id: 'norm-2-phase-2',
|
||||
source: 'norm-2',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'phase-3-end',
|
||||
source: 'phase-3',
|
||||
target: 'end',
|
||||
}
|
||||
]
|
||||
};
|
||||
const onlyStartEnd : FlowState = {
|
||||
name: "onlyStartEnd",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-end',
|
||||
source: 'start',
|
||||
target: 'end',
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// states that contain invalid programs for testing if correct errors are thrown:
|
||||
const phaseConnectsToInvalidNodeType : FlowState = {
|
||||
name: "phaseConnectsToInvalidNodeType",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'default-1',
|
||||
type: 'default',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm'},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-default-1',
|
||||
source: 'phase-1',
|
||||
target: 'default-1',
|
||||
},
|
||||
]
|
||||
};
|
||||
const phaseHasNoOutgoingConnections : FlowState = {
|
||||
name: "phaseHasNoOutgoingConnections",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
]
|
||||
};
|
||||
const phaseHasTooManyOutgoingConnections : FlowState = {
|
||||
name: "phaseHasTooManyOutgoingConnections",
|
||||
nodes: [
|
||||
{
|
||||
id: 'start',
|
||||
type: 'start',
|
||||
position: {x: 0, y: 0},
|
||||
data: {label: 'start'}
|
||||
},
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'end',
|
||||
type: 'end',
|
||||
position: {x: 0, y: 300},
|
||||
data: {label: 'End'}
|
||||
}
|
||||
],
|
||||
edges:[
|
||||
{
|
||||
id: 'start-phase-1',
|
||||
source: 'start',
|
||||
target: 'phase-1',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-phase-2',
|
||||
source: 'phase-1',
|
||||
target: 'phase-2',
|
||||
},
|
||||
{
|
||||
id: 'phase-1-end',
|
||||
source: 'phase-1',
|
||||
target: 'end',
|
||||
},
|
||||
{
|
||||
id: 'phase-2-end',
|
||||
source: 'phase-2',
|
||||
target: 'end',
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
describe('Graph Reducer Tests', () => {
|
||||
describe('defaultGraphPreprocessor', () => {
|
||||
test.each([
|
||||
{
|
||||
state: onlyOnePhase,
|
||||
expected: [
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlyThreePhases,
|
||||
expected: [
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'phase-2',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
nextPhaseId: 'phase-3',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlySingleEdgeNorms,
|
||||
expected: [
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'phase-2',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
nextPhaseId: 'phase-3',
|
||||
connectedNorms: [{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [{
|
||||
id: 'norm-2',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: multiEdgeNorms,
|
||||
expected: [
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'phase-2',
|
||||
connectedNorms: [{
|
||||
id: 'norm-3',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
nextPhaseId: 'phase-3',
|
||||
connectedNorms: [{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
},
|
||||
{
|
||||
phaseNode: {
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlyStartEnd,
|
||||
expected: [],
|
||||
}
|
||||
])(`tests state: $state.name`, ({state, expected}) => {
|
||||
const output = defaultGraphPreprocessor(state.nodes, state.edges);
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
describe('not yet implemented', () => {
|
||||
test('nothing yet', () => {
|
||||
expect(true);
|
||||
});
|
||||
describe("orderPhases", () => {
|
||||
test.each([
|
||||
{
|
||||
state: onlyOnePhase,
|
||||
expected: {
|
||||
phaseNodes: [{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
}],
|
||||
connections: new Map<string,string>([["phase-1","end"]])
|
||||
}
|
||||
},
|
||||
{
|
||||
state: onlyThreePhases,
|
||||
expected: {
|
||||
phaseNodes: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
}],
|
||||
connections: new Map<string,string>([
|
||||
["phase-1","phase-2"],
|
||||
["phase-2","phase-3"],
|
||||
["phase-3","end"]
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
state: onlySingleEdgeNorms,
|
||||
expected: {
|
||||
phaseNodes: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 2},
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 3},
|
||||
}],
|
||||
connections: new Map<string,string>([
|
||||
["phase-1","phase-2"],
|
||||
["phase-2","phase-3"],
|
||||
["phase-3","end"]
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
state: onlyStartEnd,
|
||||
expected: {
|
||||
phaseNodes: [],
|
||||
connections: new Map<string,string>()
|
||||
}
|
||||
}
|
||||
])(`tests state: $state.name`, ({state, expected}) => {
|
||||
const output = orderPhases(state.nodes, state.edges);
|
||||
expect(output.phaseNodes).toEqual(expected.phaseNodes);
|
||||
expect(output.connections).toEqual(expected.connections);
|
||||
});
|
||||
test.each([
|
||||
{
|
||||
state: phaseConnectsToInvalidNodeType,
|
||||
expected: new Error('| INVALID PROGRAM | the node "default-1" that "phase-1" connects to is not a phase or end node')
|
||||
},
|
||||
{
|
||||
state: phaseHasNoOutgoingConnections,
|
||||
expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" doesn\'t have any outgoing connections')
|
||||
},
|
||||
{
|
||||
state: phaseHasTooManyOutgoingConnections,
|
||||
expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" connects to too many targets')
|
||||
}
|
||||
])(`tests erroneous state: $state.name`, ({state, expected}) => {
|
||||
const testForError = () => {
|
||||
orderPhases(state.nodes, state.edges);
|
||||
};
|
||||
expect(testForError).toThrow(expected);
|
||||
})
|
||||
})
|
||||
describe("defaultPhaseReducer", () => {
|
||||
test("phaseReducer handles empty norms and goals without failing", () => {
|
||||
const input : PreparedPhase = {
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [],
|
||||
}
|
||||
const output = defaultPhaseReducer(input);
|
||||
expect(output).toEqual({
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
});
|
||||
});
|
||||
test("defaultNormReducer reduces norms correctly", () => {
|
||||
const input : PreparedPhase = {
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [{
|
||||
id: 'norm-1',
|
||||
type: 'norm',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Norm', value: "generic"},
|
||||
}],
|
||||
connectedGoals: [],
|
||||
}
|
||||
const output = defaultPhaseReducer(input);
|
||||
expect(output).toEqual({
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [{
|
||||
id: 'norm-1',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}],
|
||||
goals: []
|
||||
}
|
||||
});
|
||||
});
|
||||
test("defaultGoalReducer reduces goals correctly", () => {
|
||||
const input : PreparedPhase = {
|
||||
phaseNode: {
|
||||
id: 'phase-1',
|
||||
type: 'phase',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Phase', number: 1},
|
||||
},
|
||||
nextPhaseId: 'end',
|
||||
connectedNorms: [],
|
||||
connectedGoals: [{
|
||||
id: 'goal-1',
|
||||
type: 'goal',
|
||||
position: {x: 0, y: 150},
|
||||
data: {label: 'Generic Goal', value: "generic"},
|
||||
}],
|
||||
}
|
||||
const output = defaultPhaseReducer(input);
|
||||
expect(output).toEqual({
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: [{
|
||||
id: 'goal-1',
|
||||
name: 'Generic Goal',
|
||||
value: "generic"
|
||||
}]
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
describe("GraphReducer", () => {
|
||||
test.each([
|
||||
{
|
||||
state: onlyOnePhase,
|
||||
expected: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlyThreePhases,
|
||||
expected: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-2',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-3',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlySingleEdgeNorms,
|
||||
expected: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-2',
|
||||
phaseData: {
|
||||
norms: [],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-3',
|
||||
phaseData: {
|
||||
norms: [
|
||||
{
|
||||
id: 'norm-1',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}
|
||||
],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [{
|
||||
id: 'norm-2',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}],
|
||||
goals: []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: multiEdgeNorms,
|
||||
expected: [
|
||||
{
|
||||
id: 'phase-1',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-2',
|
||||
phaseData: {
|
||||
norms: [{
|
||||
id: 'norm-3',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-2',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'phase-3',
|
||||
phaseData: {
|
||||
norms: [
|
||||
{
|
||||
id: 'norm-1',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}
|
||||
],
|
||||
goals: []
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'phase-3',
|
||||
name: 'Generic Phase',
|
||||
nextPhaseId: 'end',
|
||||
phaseData: {
|
||||
norms: [{
|
||||
id: 'norm-1',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
},
|
||||
{
|
||||
id: 'norm-2',
|
||||
name: 'Generic Norm',
|
||||
value: "generic"
|
||||
}],
|
||||
goals: []
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
state: onlyStartEnd,
|
||||
expected: [],
|
||||
}
|
||||
])(`tests state: $state.name`, ({state, expected}) => {
|
||||
useFlowStore.setState({nodes: state.nodes, edges: state.edges});
|
||||
const output = graphReducer(); // uses default reducers
|
||||
expect(output).toEqual(expected);
|
||||
})
|
||||
// we run the test for correct error handling for the entire graph reducer as well,
|
||||
// to make sure no errors occur before we intend to handle the errors ourselves
|
||||
test.each([
|
||||
{
|
||||
state: phaseConnectsToInvalidNodeType,
|
||||
expected: new Error('| INVALID PROGRAM | the node "default-1" that "phase-1" connects to is not a phase or end node')
|
||||
},
|
||||
{
|
||||
state: phaseHasNoOutgoingConnections,
|
||||
expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" doesn\'t have any outgoing connections')
|
||||
},
|
||||
{
|
||||
state: phaseHasTooManyOutgoingConnections,
|
||||
expected: new Error('| INVALID PROGRAM | the source handle of "phase-1" connects to too many targets')
|
||||
}
|
||||
])(`tests erroneous state: $state.name`, ({state, expected}) => {
|
||||
useFlowStore.setState({nodes: state.nodes, edges: state.edges});
|
||||
const testForError = () => {
|
||||
graphReducer();
|
||||
};
|
||||
expect(testForError).toThrow(expected);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
import { mockReactFlow } from '../../../../setupFlowTests.ts';
|
||||
import {act} from "@testing-library/react";
|
||||
import useFlowStore from "../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx";
|
||||
import {addNode} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/components/DragDropSidebar.tsx";
|
||||
|
||||
|
||||
beforeAll(() => {
|
||||
mockReactFlow();
|
||||
describe('Not implemented', () => {
|
||||
test('nothing yet', () => {
|
||||
expect(true)
|
||||
});
|
||||
});
|
||||
|
||||
describe('Drag-and-Drop sidebar', () => {
|
||||
test.each(['phase', 'phase'])('new nodes get added correctly', (nodeType: string) => {
|
||||
act(()=> {
|
||||
addNode(nodeType, {x:100, y:100});
|
||||
})
|
||||
const updatedState = useFlowStore.getState();
|
||||
expect(updatedState.nodes.length).toBe(1);
|
||||
expect(updatedState.nodes[0].type).toBe(nodeType);
|
||||
});
|
||||
test.each(['phase', 'norm'])('new nodes get correct Id', (nodeType) => {
|
||||
act(()=> {
|
||||
addNode(nodeType, {x:100, y:100});
|
||||
addNode(nodeType, {x:100, y:100});
|
||||
})
|
||||
const updatedState = useFlowStore.getState();
|
||||
expect(updatedState.nodes.length).toBe(2);
|
||||
expect(updatedState.nodes[0].id).toBe(`${nodeType}-1`);
|
||||
expect(updatedState.nodes[1].id).toBe(`${nodeType}-2`);
|
||||
});
|
||||
test('throws error on unexpected node type', () => {
|
||||
expect(() => addNode('I do not Exist', {x:100, y:100})).toThrow("Node I do not Exist not found");
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user