Skip to main content
Module

x/xstate/test/transient.test.ts

State machines and statecharts for the modern web.
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
import { Machine, createMachine, interpret } from '../src/index';import { assign, raise } from '../src/actions';
const greetingContext = { hour: 10 };const greetingMachine = Machine<typeof greetingContext>({ key: 'greeting', initial: 'pending', context: greetingContext, states: { pending: { on: { '': [ { target: 'morning', cond: (ctx) => ctx.hour < 12 }, { target: 'afternoon', cond: (ctx) => ctx.hour < 18 }, { target: 'evening' } ] } }, morning: {}, afternoon: {}, evening: {} }, on: { CHANGE: { actions: assign({ hour: 20 }) }, RECHECK: '#greeting' }});
describe('transient states (eventless transitions)', () => { const updateMachine = Machine<{ data: boolean; status?: string }>({ initial: 'G', states: { G: { on: { UPDATE_BUTTON_CLICKED: 'E' } }, E: { on: { // eventless transition '': [ { target: 'D', cond: ({ data }) => !data }, // no data returned { target: 'B', cond: ({ status }) => status === 'Y' }, { target: 'C', cond: ({ status }) => status === 'X' }, { target: 'F' } // default, or just the string 'F' ] } }, D: {}, B: {}, C: {}, F: {} } });
it('should choose the first candidate target that matches the cond (D)', () => { const nextState = updateMachine.transition('G', 'UPDATE_BUTTON_CLICKED', { data: false }); expect(nextState.value).toEqual('D'); });
it('should choose the first candidate target that matches the cond (B)', () => { const nextState = updateMachine.transition('G', 'UPDATE_BUTTON_CLICKED', { data: true, status: 'Y' }); expect(nextState.value).toEqual('B'); });
it('should choose the first candidate target that matches the cond (C)', () => { const nextState = updateMachine.transition('G', 'UPDATE_BUTTON_CLICKED', { data: true, status: 'X' }); expect(nextState.value).toEqual('C'); });
it('should choose the final candidate without a cond if none others match', () => { const nextState = updateMachine.transition('G', 'UPDATE_BUTTON_CLICKED', { data: true, status: 'other' }); expect(nextState.value).toEqual('F'); });
it('should carry actions from previous transitions within same step', () => { const machine = Machine({ initial: 'A', states: { A: { onExit: 'exit_A', on: { TIMER: { target: 'T', actions: ['timer'] } } }, T: { on: { '': [{ target: 'B' }] } }, B: { onEntry: 'enter_B' } } });
const state = machine.transition('A', 'TIMER');
expect(state.actions.map((a) => a.type)).toEqual([ 'exit_A', 'timer', 'enter_B' ]); });
it('should execute all internal events one after the other', () => { const machine = Machine({ type: 'parallel', states: { A: { initial: 'A1', states: { A1: { on: { E: 'A2' } }, A2: { onEntry: raise('INT1') } } },
B: { initial: 'B1', states: { B1: { on: { E: 'B2' } }, B2: { onEntry: raise('INT2') } } },
C: { initial: 'C1', states: { C1: { on: { INT1: 'C2', INT2: 'C3' } }, C2: { on: { INT2: 'C4' } }, C3: { on: { INT1: 'C4' } }, C4: {} } } } });
const state = machine.transition(machine.initialState, 'E');
expect(state.value).toEqual({ A: 'A2', B: 'B2', C: 'C4' }); });
it('should execute all eventless transitions in the same microstep', () => { const machine = Machine({ type: 'parallel', states: { A: { initial: 'A1', states: { A1: { on: { E: 'A2' // the external event } }, A2: { on: { '': 'A3' } }, A3: { on: { '': { target: 'A4', in: 'B.B3' } } }, A4: {} } },
B: { initial: 'B1', states: { B1: { on: { E: 'B2' } }, B2: { on: { '': { target: 'B3', in: 'A.A2' } } }, B3: { on: { '': { target: 'B4', in: 'A.A3' } } }, B4: {} } } } });
const state = machine.transition(machine.initialState, 'E');
expect(state.value).toEqual({ A: 'A4', B: 'B4' }); });
it('should execute all eventless transitions in the same microstep (with `always`)', () => { const machine = Machine({ type: 'parallel', states: { A: { initial: 'A1', states: { A1: { on: { E: 'A2' // the external event } }, A2: { always: 'A3' }, A3: { always: { target: 'A4', in: 'B.B3' } }, A4: {} } },
B: { initial: 'B1', states: { B1: { on: { E: 'B2' } }, B2: { always: { target: 'B3', in: 'A.A2' } }, B3: { always: { target: 'B4', in: 'A.A3' } }, B4: {} } } } });
const state = machine.transition(machine.initialState, 'E');
expect(state.value).toEqual({ A: 'A4', B: 'B4' }); });
it('should check for automatic transitions even after microsteps are done', () => { const machine = Machine({ type: 'parallel', states: { A: { initial: 'A1', states: { A1: { on: { A: 'A2' } }, A2: {} } }, B: { initial: 'B1', states: { B1: { on: { '': { target: 'B2', cond: (_xs, _e, { state: s }) => s.matches('A.A2') } } }, B2: {} } }, C: { initial: 'C1', states: { C1: { on: { '': { target: 'C2', in: 'A.A2' } } }, C2: {} } } } });
let state = machine.initialState; // A1, B1, C1 state = machine.transition(state, 'A'); // A2, B2, C2 expect(state.value).toEqual({ A: 'A2', B: 'B2', C: 'C2' }); });
it('should check for automatic transitions even after microsteps are done (with `always`)', () => { const machine = Machine({ type: 'parallel', states: { A: { initial: 'A1', states: { A1: { on: { A: 'A2' } }, A2: {} } }, B: { initial: 'B1', states: { B1: { always: { target: 'B2', cond: (_xs, _e, { state: s }) => s.matches('A.A2') } }, B2: {} } }, C: { initial: 'C1', states: { C1: { always: { target: 'C2', in: 'A.A2' } }, C2: {} } } } });
let state = machine.initialState; // A1, B1, C1 state = machine.transition(state, 'A'); // A2, B2, C2 expect(state.value).toEqual({ A: 'A2', B: 'B2', C: 'C2' }); });
it('should determine the resolved initial state from the transient state', () => { expect(greetingMachine.initialState.value).toEqual('morning'); });
// TODO: determine proper behavior here - // Should an internal transition on the parent node activate the parent node // or all previous state nodes? xit('should determine the resolved state from a root transient state', () => { const morningState = greetingMachine.initialState; expect(morningState.value).toEqual('morning'); const stillMorningState = greetingMachine.transition( morningState, 'CHANGE' ); expect(stillMorningState.value).toEqual('morning'); const eveningState = greetingMachine.transition( stillMorningState, 'RECHECK' ); expect(eveningState.value).toEqual('evening'); });
it('should select eventless transition before processing raised events', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { entry: raise('BAR'), on: { '': 'c', BAR: 'd' } }, c: { on: { BAR: 'e' } }, d: {}, e: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('e'); });
it('should select eventless transition before processing raised events (with `always`)', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { entry: raise('BAR'), always: 'c', on: { BAR: 'd' } }, c: { on: { BAR: 'e' } }, d: {}, e: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('e'); });
it('should select eventless transition for array `.on` config', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { on: [{ event: '', target: 'pass' }] }, pass: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('pass'); });
it('should not select wildcard for eventless transition', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { on: { '*': 'fail' } }, fail: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('b'); });
it('should not select wildcard for eventless transition (array `.on`)', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { on: [ { event: '*', target: 'fail' }, { event: '', target: 'pass' } ] }, fail: {}, pass: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('pass'); });
it('should not select wildcard for eventless transition (with `always`)', () => { const machine = Machine({ initial: 'a', states: { a: { on: { FOO: 'b' } }, b: { always: 'pass', on: [{ event: '*', target: 'fail' }] }, fail: {}, pass: {} } });
const state = machine.transition('a', 'FOO'); expect(state.value).toBe('pass'); });
it('should work with transient transition on root', (done) => { const machine = createMachine<any, any, any>({ id: 'machine', initial: 'first', context: { count: 0 }, states: { first: { on: { ADD: { actions: assign({ count: (ctx) => ctx.count + 1 }) } } }, success: { type: 'final' } }, on: { '': [ { target: '.success', cond: (ctx) => { return ctx.count > 0; } } ] } });
const service = interpret(machine).onDone(() => { done(); });
service.start();
service.send('ADD'); });
it('should work with transient transition on root (with `always`)', (done) => { const machine = createMachine<any, any, any>({ id: 'machine', initial: 'first', context: { count: 0 }, states: { first: { on: { ADD: { actions: assign({ count: (ctx) => ctx.count + 1 }) } } }, success: { type: 'final' } },
always: [ { target: '.success', cond: (ctx) => { return ctx.count > 0; } } ] });
const service = interpret(machine).onDone(() => { done(); });
service.start();
service.send('ADD'); });
it("shouldn't crash when invoking a machine with initial transient transition depending on custom data", () => { const timerMachine = Machine({ initial: 'initial', states: { initial: { always: [ { target: `finished`, cond: (ctx) => ctx.duration < 1000 }, { target: `active` } ] }, active: {}, finished: { type: 'final' } } });
const machine = Machine({ initial: 'active', context: { customDuration: 3000 }, states: { active: { invoke: { src: timerMachine, data: { duration: (context: any) => context.customDuration } } } } });
const service = interpret(machine); expect(() => service.start()).not.toThrow(); });
it('should be taken even in absence of other transitions', () => { let shouldMatch = false;
const machine = createMachine({ initial: 'a', states: { a: { always: { target: 'b', // TODO: in v5 remove `shouldMatch` and replace this guard with: // cond: (ctx, ev) => ev.type === 'WHATEVER' cond: () => shouldMatch } }, b: {} } }); const service = interpret(machine).start();
shouldMatch = true; service.send({ type: 'WHATEVER' });
expect(service.state.value).toBe('b'); });
it('should select subsequent transient transitions even in absence of other transitions', () => { let shouldMatch = false;
const machine = createMachine({ initial: 'a', states: { a: { always: { target: 'b', // TODO: in v5 remove `shouldMatch` and replace this guard with: // cond: (ctx, ev) => ev.type === 'WHATEVER' cond: () => shouldMatch } }, b: { always: { target: 'c', cond: () => true } }, c: {} } });
const service = interpret(machine).start();
shouldMatch = true; service.send({ type: 'WHATEVER' });
expect(service.state.value).toBe('c'); });});