diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index e5dfc6cdca..299bacb8da 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -1,5 +1,6 @@ import isDevelopment from '#is-development'; import { assign } from './actions.ts'; +import { executeCancel } from './actions/cancel.ts'; import { executeRaise } from './actions/raise.ts'; import { executeSendTo } from './actions/send.ts'; import { createEmptyActor } from './actors/index.ts'; @@ -661,6 +662,9 @@ export class StateMachine< actorScope.defer = (fn) => fn(); try { switch (action.type) { + case 'xstate.cancel': + executeCancel(actorScope, action.params as any); + return; case 'xstate.raise': if (typeof (action as any).params.delay !== 'number') { return; diff --git a/packages/core/src/actions/cancel.ts b/packages/core/src/actions/cancel.ts index 96d36993ad..d0bcc79cb5 100644 --- a/packages/core/src/actions/cancel.ts +++ b/packages/core/src/actions/cancel.ts @@ -32,7 +32,10 @@ function resolveCancel( return [snapshot, resolvedSendId]; } -function executeCancel(actorScope: AnyActorScope, resolvedSendId: string) { +export function executeCancel( + actorScope: AnyActorScope, + resolvedSendId: string +) { actorScope.defer(() => { actorScope.system.scheduler.cancel(actorScope.self, resolvedSendId); }); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f7433276d8..1c13e74dc0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -2634,6 +2634,7 @@ export interface ExecutableSpawnAction extends ExecutableActionObject { }; } +// TODO: cover all that can be actually returned export type SpecialExecutableAction = | ExecutableSpawnAction | ExecutableRaiseAction diff --git a/packages/core/test/transition.test.ts b/packages/core/test/transition.test.ts index 522b7ace68..72e775423e 100644 --- a/packages/core/test/transition.test.ts +++ b/packages/core/test/transition.test.ts @@ -1,6 +1,7 @@ import { sleep } from '@xstate-repo/jest-utils'; import { assign, + cancel, createActor, createMachine, enqueueActions, @@ -244,6 +245,37 @@ describe('transition function', () => { await waitFor(actor, (s) => s.matches('b')); }); + it('cancel action should be returned and can be executed', async () => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + entry: raise({ type: 'NEXT' }, { delay: 10, id: 'myRaise' }), + on: { + NEXT: { + target: 'b', + actions: cancel('myRaise') + } + } + }, + b: {} + } + }); + + const [state] = initialTransition(machine); + + expect(state.value).toEqual('a'); + + const [, actions] = transition(machine, state, { type: 'NEXT' }); + + actions.forEach((action) => { + machine.executeAction(action); + }); + + // TODO: tweak the assertion + expect(actions.map((a) => a.type)).toEqual({}); + }); + // Copied from getSnapshot.test.ts it('should calculate the next snapshot for transition logic', () => {