240 lines
5.4 KiB
TypeScript
240 lines
5.4 KiB
TypeScript
import {act} from '@testing-library/react';
|
|
import useFlowStore from '../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
|
|
import { mockReactFlow } from '../../../setupFlowTests.ts';
|
|
|
|
|
|
beforeAll(() => {
|
|
mockReactFlow();
|
|
});
|
|
|
|
describe("UndoRedo Middleware", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
test("pushSnapshot adds a snapshot to past and clears future", () => {
|
|
const store = useFlowStore;
|
|
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: [],
|
|
past: [],
|
|
future: [{
|
|
nodes: [
|
|
{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
},
|
|
],
|
|
edges: []
|
|
}],
|
|
});
|
|
|
|
act(() => {
|
|
store.getState().pushSnapshot();
|
|
})
|
|
|
|
const state = store.getState();
|
|
expect(state.past.length).toBe(1);
|
|
expect(state.past[0]).toEqual({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: []
|
|
});
|
|
expect(state.future).toEqual([]);
|
|
});
|
|
|
|
test("pushSnapshot does nothing during batch action", () => {
|
|
const store = useFlowStore;
|
|
|
|
act(() => {
|
|
store.setState({ isBatchAction: true });
|
|
store.getState().pushSnapshot();
|
|
})
|
|
|
|
expect(store.getState().past.length).toBe(0);
|
|
});
|
|
|
|
test("undo restores last snapshot and pushes current snapshot to future", () => {
|
|
const store = useFlowStore;
|
|
|
|
// initial state
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: []
|
|
});
|
|
|
|
act(() => {
|
|
store.getState().pushSnapshot();
|
|
|
|
// modified state
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'B',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'B'}
|
|
}],
|
|
edges: []
|
|
});
|
|
|
|
store.getState().undo();
|
|
})
|
|
|
|
expect(store.getState().nodes).toEqual([{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}]);
|
|
expect(store.getState().future.length).toBe(1);
|
|
expect(store.getState().future[0]).toEqual({
|
|
nodes: [{
|
|
id: 'B',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'B'}
|
|
}],
|
|
edges: []
|
|
});
|
|
});
|
|
|
|
test("undo does nothing when past is empty", () => {
|
|
const store = useFlowStore;
|
|
|
|
store.setState({past: []});
|
|
|
|
act(() => { store.getState().undo(); });
|
|
|
|
expect(store.getState().nodes).toEqual([]);
|
|
expect(store.getState().future).toEqual([]);
|
|
});
|
|
|
|
test("redo restores last future snapshot and pushes current to past", () => {
|
|
const store = useFlowStore;
|
|
|
|
// initial
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: []
|
|
});
|
|
|
|
act(() => {
|
|
store.getState().pushSnapshot();
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'B',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'B'}
|
|
}],
|
|
edges: []
|
|
});
|
|
|
|
|
|
store.getState().undo();
|
|
|
|
// redo should restore node with id 'B'
|
|
store.getState().redo();
|
|
})
|
|
|
|
expect(store.getState().nodes).toEqual([{
|
|
id: 'B',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'B'}
|
|
}]);
|
|
expect(store.getState().past.length).toBe(1); // snapshot A stored
|
|
expect(store.getState().past[0]).toEqual({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: []
|
|
});
|
|
});
|
|
|
|
test("redo does nothing when future is empty", () => {
|
|
const store = useFlowStore;
|
|
|
|
store.setState({past: []});
|
|
act(() => { store.getState().redo(); });
|
|
|
|
expect(store.getState().nodes).toEqual([]);
|
|
});
|
|
|
|
test("beginBatchAction pushes snapshot and sets isBatchAction=true", () => {
|
|
const store = useFlowStore;
|
|
|
|
store.setState({
|
|
nodes: [{
|
|
id: 'A',
|
|
type: 'default',
|
|
position: {x: 0, y: 0},
|
|
data: {label: 'A'}
|
|
}],
|
|
edges: []
|
|
});
|
|
|
|
act(() => { store.getState().beginBatchAction(); });
|
|
|
|
expect(store.getState().isBatchAction).toBe(true);
|
|
expect(store.getState().past.length).toBe(1);
|
|
});
|
|
|
|
test("endBatchAction sets isBatchAction=false after timeout", () => {
|
|
const store = useFlowStore;
|
|
|
|
store.setState({ isBatchAction: true });
|
|
act(() => { store.getState().endBatchAction(); });
|
|
|
|
// isBatchAction should remain true before the timer has advanced
|
|
expect(store.getState().isBatchAction).toBe(true);
|
|
|
|
jest.advanceTimersByTime(10);
|
|
|
|
// it should now be set to false as the timer has advanced enough
|
|
expect(store.getState().isBatchAction).toBe(false);
|
|
});
|
|
|
|
test("multiple beginBatchAction calls clear the timeout", () => {
|
|
const store = useFlowStore;
|
|
|
|
act(() => {
|
|
store.getState().beginBatchAction();
|
|
store.getState().endBatchAction(); // starts timeout
|
|
store.getState().beginBatchAction(); // should clear previous timeout
|
|
});
|
|
|
|
|
|
jest.advanceTimersByTime(10);
|
|
|
|
// After advancing the timers, isBatchAction should still be true,
|
|
// as the timeout should have been cleared
|
|
expect(store.getState().isBatchAction).toBe(true);
|
|
});
|
|
});
|