| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import { of } from 'rxjs';
- import { FSM, Transitions } from './finite-state-machine';
- describe('Finite State Machine', () => {
- type TestState = 'DoorsClosed' | 'DoorsOpen' | 'Moving';
- const transitions: Transitions<TestState> = {
- DoorsClosed: {
- to: ['Moving', 'DoorsOpen'],
- },
- DoorsOpen: {
- to: ['DoorsClosed'],
- },
- Moving: {
- to: ['DoorsClosed'],
- },
- };
- it('initialState works', () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>({ transitions }, initialState);
- expect(fsm.initialState).toBe(initialState);
- });
- it('getNextStates() works', () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>({ transitions }, initialState);
- expect(fsm.getNextStates()).toEqual(['Moving', 'DoorsOpen']);
- });
- it('allows valid transitions', () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>({ transitions }, initialState);
- fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe('Moving');
- fsm.transitionTo('DoorsClosed');
- expect(fsm.currentState).toBe('DoorsClosed');
- fsm.transitionTo('DoorsOpen');
- expect(fsm.currentState).toBe('DoorsOpen');
- fsm.transitionTo('DoorsClosed');
- expect(fsm.currentState).toBe('DoorsClosed');
- });
- it('does not allow invalid transitions', () => {
- const initialState = 'DoorsOpen';
- const fsm = new FSM<TestState>({ transitions }, initialState);
- fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe('DoorsOpen');
- fsm.transitionTo('DoorsClosed');
- expect(fsm.currentState).toBe('DoorsClosed');
- fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe('Moving');
- fsm.transitionTo('DoorsOpen');
- expect(fsm.currentState).toBe('Moving');
- });
- it('onTransitionStart() is invoked before a transition takes place', () => {
- const initialState = 'DoorsClosed';
- const spy = jest.fn();
- const data = 123;
- let currentStateDuringCallback = '';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: spy.mockImplementation(() => {
- currentStateDuringCallback = fsm.currentState;
- }),
- },
- initialState,
- );
- fsm.transitionTo('Moving', data);
- expect(spy).toHaveBeenCalledWith(initialState, 'Moving', data);
- expect(currentStateDuringCallback).toBe(initialState);
- });
- it('onTransitionEnd() is invoked after a transition takes place', () => {
- const initialState = 'DoorsClosed';
- const spy = jest.fn();
- const data = 123;
- let currentStateDuringCallback = '';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionEnd: spy.mockImplementation(() => {
- currentStateDuringCallback = fsm.currentState;
- }),
- },
- initialState,
- );
- fsm.transitionTo('Moving', data);
- expect(spy).toHaveBeenCalledWith(initialState, 'Moving', data);
- expect(currentStateDuringCallback).toBe('Moving');
- });
- it('onTransitionStart() cancels transition when it returns false', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => false,
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe(initialState);
- });
- it('onTransitionStart() cancels transition when it returns Promise<false>', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => Promise.resolve(false),
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe(initialState);
- });
- it('onTransitionStart() cancels transition when it returns Observable<false>', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => of(false),
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe(initialState);
- });
- it('onTransitionStart() cancels transition when it returns a string', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => 'foo',
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe(initialState);
- });
- it('onTransitionStart() allows transition when it returns true', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => true,
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe('Moving');
- });
- it('onTransitionStart() allows transition when it returns void', async () => {
- const initialState = 'DoorsClosed';
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => {
- /* empty */
- },
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(fsm.currentState).toBe('Moving');
- });
- it('onError() is invoked for invalid transitions', async () => {
- const initialState = 'DoorsOpen';
- const spy = jest.fn();
- const fsm = new FSM<TestState>(
- {
- transitions,
- onError: spy,
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(spy).toHaveBeenCalledWith(initialState, 'Moving', undefined);
- });
- it('onTransitionStart() invokes onError() if it returns a string', async () => {
- const initialState = 'DoorsClosed';
- const spy = jest.fn();
- const fsm = new FSM<TestState>(
- {
- transitions,
- onTransitionStart: () => 'error',
- onError: spy,
- },
- initialState,
- );
- await fsm.transitionTo('Moving');
- expect(spy).toHaveBeenCalledWith(initialState, 'Moving', 'error');
- });
- });
|