fix: incorrect phase reduction order

This commit is contained in:
Gerla, J. (Justin)
2026-01-06 15:12:00 +00:00
committed by Pim Hutting
parent 9c80391fea
commit 9b3414ba98
13 changed files with 520 additions and 79 deletions

View File

@@ -1,8 +1,10 @@
import type { Node, Edge, Connection } from '@xyflow/react'
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores';
import type { PhaseNodeData } from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode";
import { getByTestId, render } from '@testing-library/react';
import type {PhaseNode, PhaseNodeData} from "../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode";
import {act, getByTestId, render} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import VisProgPage from '../../../../../src/pages/VisProgPage/VisProg';
import {mockReactFlow} from "../../../../setupFlowTests.ts";
class ResizeObserver {
@@ -98,4 +100,195 @@ describe('PhaseNode', () => {
expect(p1_data.children.length == 1);
expect(p2_data.children.length == 2);
});
});
});
// --| Helper functions |--
function createPhaseNode(
id: string,
overrides: Partial<PhaseNodeData> = {},
): Node<PhaseNodeData> {
return {
id: id,
type: 'phase',
position: { x: 0, y: 0 },
data: {
label: 'Phase',
droppable: true,
children: [],
hasReduce: true,
nextPhaseId: null,
isFirstPhase: false,
...overrides,
},
}
}
function createNode(id: string, type: string): Node {
return {
id: id,
type: type,
position: { x: 0, y: 0 },
data: {},
}
}
function connect(source: string, target: string): Connection {
return {
source: source,
target: target,
sourceHandle: null,
targetHandle: null
};
}
function edge(source: string, target: string): Edge {
return {
id: `${source}-${target}`,
source: source,
target: target,
}
}
// --| Connection Tests |--
describe('PhaseNode Connection logic', () => {
beforeAll(() => {
mockReactFlow();
});
describe('PhaseConnections', () => {
test('connecting start => phase sets isFirstPhase to true', () => {
const phase = createPhaseNode('phase-1')
const start = createNode('start', 'start')
useFlowStore.setState({ nodes: [phase, start] })
// verify it starts of false
expect(phase.data.isFirstPhase).toBe(false);
act(() => {
useFlowStore.getState().onConnect(connect('start', 'phase-1'))
})
const updatedPhase = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedPhase.data.isFirstPhase).toBe(true)
})
test('connecting task => phase adds child', () => {
const phase = createPhaseNode('phase-1')
const norm = createNode('norm-1', 'norm')
useFlowStore.setState({ nodes: [phase, norm] })
act(() => {
useFlowStore.getState().onConnect(connect('norm-1', 'phase-1'))
})
const updatedPhase = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedPhase.data.children).toEqual(['norm-1'])
})
test('connecting phase => phase sets nextPhaseId', () => {
const p1 = createPhaseNode('phase-1')
const p2 = createPhaseNode('phase-2')
useFlowStore.setState({ nodes: [p1, p2] })
act(() => {
useFlowStore.getState().onConnect(connect('phase-1', 'phase-2'))
})
const updatedP1 = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedP1.data.nextPhaseId).toBe('phase-2')
})
test('connecting phase to end => phase sets nextPhaseId to "end"', () => {
const phase = createPhaseNode('phase-1')
const end = createNode('end', 'end')
useFlowStore.setState({ nodes: [phase, end] })
act(() => {
useFlowStore.getState().onConnect(connect('phase-1', 'end'))
})
const updatedPhase = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedPhase.data.nextPhaseId).toBe('end')
})
})
describe('PhaseDisconnections', () => {
test('disconnecting task => phase removes child', () => {
const phase = createPhaseNode('phase-1', { children: ['norm-1'] })
const norm = createNode('norm-1', 'norm')
useFlowStore.setState({
nodes: [phase, norm],
edges: [edge('norm-1', 'phase-1')]
})
act(() => {
useFlowStore.getState().onEdgesDelete([edge('norm-1', 'phase-1')])
})
const updatedPhase = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedPhase.data.children).toEqual([])
})
test('disconnecting start => phase sets isFirstPhase to false', () => {
const phase = createPhaseNode('phase-1', { isFirstPhase: true })
const start = createNode('start', 'start')
useFlowStore.setState({
nodes: [phase, start],
edges: [edge('start', 'phase-1')]
})
act(() => {
useFlowStore.getState().onEdgesDelete([edge('start', 'phase-1')])
})
const updatedPhase = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedPhase.data.isFirstPhase).toBe(false)
})
test('disconnecting phase => phase sets nextPhaseId to null', () => {
const p1 = createPhaseNode('phase-1', { nextPhaseId: 'phase-2' })
const p2 = createPhaseNode('phase-2')
useFlowStore.setState({
nodes: [p1, p2],
edges: [edge('phase-1', 'phase-2')]
})
act(() => {
useFlowStore.getState().onEdgesDelete([edge('phase-1', 'phase-2')])
})
const updatedP1 = useFlowStore
.getState()
.nodes.find((n) => n.id === 'phase-1') as PhaseNode
expect(updatedP1.data.nextPhaseId).toBeNull()
})
})
})