deno.land / x / xstate@xstate@4.33.6 / test / predictableExec.test.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779import { createMachine, interpret, assign, spawn, AnyInterpreter} from '../src';import { raise, stop, send, sendParent } from '../src/actions';
describe('predictableExec', () => { it('should call mixed custom and builtin actions in the definitions order', () => { const actual: string[] = [];
const machine = createMachine({ initial: 'a', context: {}, predictableActionArguments: true, states: { a: { on: { NEXT: 'b' } }, b: { entry: [ () => { actual.push('custom'); }, assign(() => { actual.push('assign'); return {}; }) ] } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(actual).toEqual(['custom', 'assign']); });
it('should call initial custom actions when starting a service', () => { let called = false; const machine = createMachine({ predictableActionArguments: true, entry: () => { called = true; } });
expect(called).toBe(false);
interpret(machine).start();
expect(called).toBe(true); });
it('should resolve initial assign actions before starting a service', () => { const machine = createMachine({ predictableActionArguments: true, context: { called: false }, entry: [ assign({ called: true }) ] });
expect(interpret(machine).initialState.context.called).toBe(true); });
it('should call raised transition custom actions with raised event', () => { let eventArg: any; const machine = createMachine({ predictableActionArguments: true, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { on: { RAISED: { target: 'c', actions: (_ctx, ev) => (eventArg = ev) } }, entry: raise('RAISED') }, c: {} } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(eventArg.type).toBe('RAISED'); });
it('should call raised transition builtin actions with raised event', () => { let eventArg: any; const machine = createMachine({ predictableActionArguments: true, context: {}, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { on: { RAISED: { target: 'c', actions: assign((_ctx, ev) => { eventArg = ev; return {}; }) } }, entry: raise('RAISED') }, c: {} } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(eventArg.type).toBe('RAISED'); });
it('should call invoke creator with raised event', () => { let eventArg: any; const machine = createMachine({ predictableActionArguments: true, context: {}, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { on: { RAISED: 'c' }, entry: raise('RAISED') }, c: { invoke: { src: (_ctx, ev) => { eventArg = ev; return () => {}; } } } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(eventArg.type).toBe('RAISED'); });
it('invoked child should be available on the new state', () => { const machine = createMachine({ predictableActionArguments: true, context: {}, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { invoke: { id: 'myChild', src: () => () => {} } } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(service.state.children.myChild).toBeDefined(); });
it('invoked child should not be available on the state after leaving invoking state', () => { const machine = createMachine({ predictableActionArguments: true, context: {}, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { invoke: { id: 'myChild', src: () => () => {} }, on: { NEXT: 'c' } }, c: {} } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' }); service.send({ type: 'NEXT' });
expect(service.state.children.myChild).not.toBeDefined(); });
it('should automatically enable preserveActionOrder', () => { let calledWith = 0; const machine = createMachine({ predictableActionArguments: true, context: { counter: 0 }, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { entry: [ assign({ counter: 1 }), (context) => (calledWith = context.counter), assign({ counter: 2 }) ] } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(calledWith).toBe(1); });
it('should be able to restart a spawned actor within a single macrostep', () => { const actual: string[] = []; let invokeCounter = 0;
const machine = createMachine<any, any>({ predictableActionArguments: true, initial: 'active', context: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return { actorRef: spawn(() => { return () => { actual.push(`stop ${localId}`); }; }) }; }, states: { active: { on: { update: { actions: [ stop((ctx: any) => ctx.actorRef), assign({ actorRef: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return spawn(() => { return () => { actual.push(`stop ${localId}`); }; }); } }) ] } } } } });
const service = interpret(machine).start();
actual.length = 0;
service.send({ type: 'update' });
expect(actual).toEqual(['stop 1', 'start 2']); });
it('should be able to restart a named spawned actor within a single macrostep when stopping by ref', () => { const actual: string[] = []; let invokeCounter = 0;
const machine = createMachine<any, any>({ predictableActionArguments: true, initial: 'active', context: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return { actorRef: spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name') }; }, states: { active: { on: { update: { actions: [ stop((ctx: any) => ctx.actorRef), assign({ actorRef: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name'); } }) ] } } } } });
const service = interpret(machine).start();
actual.length = 0;
service.send({ type: 'update' });
expect(actual).toEqual(['stop 1', 'start 2']); });
it('should be able to restart a named spawned actor within a single macrostep when stopping by static name', () => { const actual: string[] = []; let invokeCounter = 0;
const machine = createMachine<any, any>({ predictableActionArguments: true, initial: 'active', context: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return { actorRef: spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name') }; }, states: { active: { on: { update: { actions: [ stop('my_name'), assign({ actorRef: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name'); } }) ] } } } } });
const service = interpret(machine).start();
actual.length = 0;
service.send({ type: 'update' });
expect(actual).toEqual(['stop 1', 'start 2']); });
it('should be able to restart a named spawned actor within a single macrostep when stopping by resolved name', () => { const actual: string[] = []; let invokeCounter = 0;
const machine = createMachine<any, any>({ predictableActionArguments: true, initial: 'active', context: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return { actorRef: spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name') }; }, states: { active: { on: { update: { actions: [ stop(() => 'my_name'), assign({ actorRef: () => { const localId = ++invokeCounter; actual.push(`start ${localId}`);
return spawn(() => { return () => { actual.push(`stop ${localId}`); }; }, 'my_name'); } }) ] } } } } });
const service = interpret(machine).start();
actual.length = 0;
service.send({ type: 'update' });
expect(actual).toEqual(['stop 1', 'start 2']); });
it('should be able to restart an invoke when reentering the invoking state', () => { const actual: string[] = []; let invokeCounter = 0;
const machine = createMachine({ predictableActionArguments: true, initial: 'inactive', states: { inactive: { on: { ACTIVATE: 'active' } }, active: { invoke: { src: () => { const localId = ++invokeCounter;
actual.push(`start ${localId}`);
return () => { return () => { actual.push(`stop ${localId}`); }; }; } }, on: { REENTER: { target: 'active', internal: false } } } } });
const service = interpret(machine).start();
service.send({ type: 'ACTIVATE' });
actual.length = 0;
service.send({ type: 'REENTER' });
expect(actual).toEqual(['stop 1', 'start 2']); });
// TODO: this might be tricky in v5 because this currently relies on `.exec` property being mutated to capture the context values in a closure it('initial actions should receive context updated only by preceeding assign actions', () => { const actual: number[] = [];
const machine = createMachine({ predictableActionArguments: true, context: { count: 0 }, entry: [ (ctx) => actual.push(ctx.count), assign({ count: 1 }), (ctx) => actual.push(ctx.count), assign({ count: 2 }), (ctx) => actual.push(ctx.count) ] });
interpret(machine).start();
expect(actual).toEqual([0, 1, 2]); });
it('`.nextState()` should not execute actions `predictableActionArguments`', () => { let spy = jest.fn();
const machine = createMachine({ predictableActionArguments: true, on: { TICK: { actions: spy } } });
const service = interpret(machine).start(); service.nextState({ type: 'TICK' });
expect(spy).not.toBeCalled(); });
it('should create invoke based on context updated by entry actions of the same state', () => { const machine = createMachine({ predictableActionArguments: true, context: { updated: false }, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { entry: assign({ updated: true }), invoke: { src: (ctx) => { expect(ctx.updated).toBe(true); return Promise.resolve(); } } } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' }); });
it('should deliver events sent from the entry actions to a service invoked in the same state', () => { let received: any;
const machine = createMachine({ predictableActionArguments: true, context: { updated: false }, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { entry: send({ type: 'KNOCK_KNOCK' }, { to: 'myChild' }), invoke: { id: 'myChild', src: () => (_sendBack, onReceive) => { onReceive((event) => { received = event; }); return () => {}; } } } } });
const service = interpret(machine).start(); service.send({ type: 'NEXT' });
expect(received).toEqual({ type: 'KNOCK_KNOCK' }); });
it('parent should be able to read the updated state of a child when receiving an event from it', (done) => { const child = createMachine({ predictableActionArguments: true, initial: 'a', states: { a: { // we need to clear the call stack before we send the event to the parent after: { 1: 'b' } }, b: { entry: sendParent({ type: 'CHILD_UPDATED' }) } } });
let service: AnyInterpreter;
const machine = createMachine({ predictableActionArguments: true, invoke: { id: 'myChild', src: child }, initial: 'initial', states: { initial: { on: { CHILD_UPDATED: [ { cond: () => service.state.children.myChild.getSnapshot().value === 'b', target: 'success' }, { target: 'fail' } ] } }, success: { type: 'final' }, fail: { type: 'final' } } });
service = interpret(machine) .onDone(() => { expect(service.state.value).toBe('success'); done(); }) .start(); });
it('should be possible to send immediate events to initially invoked actors', () => { const child = createMachine({ predictableActionArguments: true, on: { PING: { actions: sendParent({ type: 'PONG' }) } } });
const machine = createMachine({ predictableActionArguments: true, initial: 'waiting', states: { waiting: { invoke: { id: 'ponger', src: child }, entry: send({ type: 'PING' }, { to: 'ponger' }), on: { PONG: 'done' } }, done: { type: 'final' } } });
const service = interpret(machine).start();
expect(service.getSnapshot().value).toBe('done'); });
it('should execute actions when sending batched events', () => { let executed = false;
const machine = createMachine({ predictableActionArguments: true, initial: 'a', states: { a: { on: { NEXT: 'b' } }, b: { entry: () => (executed = true) } } });
const service = interpret(machine).start();
service.send([{ type: 'NEXT' }]);
expect(executed).toBe(true); });
it('should deliver events sent to other actors when using batched events', () => { let gotEvent = false;
const machine = createMachine({ predictableActionArguments: true, invoke: { id: 'myChild', src: () => (_sendBack, onReceive) => { onReceive(() => { gotEvent = true; }); } }, on: { PING_CHILD: { actions: send({ type: 'PING' }, { to: 'myChild' }) } } });
const service = interpret(machine).start();
service.send([{ type: 'PING_CHILD' }]);
expect(gotEvent).toBe(true); });});
Version Info