diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index 20036efae5..2cfa601666 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -47,7 +47,6 @@ import type { StateMachineDefinition, StateValue, TransitionDefinition, - UnknownActionObject, ResolvedStateMachineTypes, StateSchema, SnapshotStatus, @@ -389,7 +388,7 @@ export class StateMachine< preInitial, initEvent, actorScope, - [assign(assignment) as unknown as UnknownActionObject], + [assign(assignment)], internalQueue, undefined ) as SnapshotFrom; diff --git a/packages/core/src/StateNode.ts b/packages/core/src/StateNode.ts index 6948c08de0..5abff6f96a 100644 --- a/packages/core/src/StateNode.ts +++ b/packages/core/src/StateNode.ts @@ -4,7 +4,7 @@ import { NULL_EVENT, STATE_DELIMITER } from './constants.ts'; import { evaluateGuard } from './guards.ts'; import { memo } from './memo.ts'; import { - convertAction, + BuiltinAction, formatInitialTransition, formatTransition, formatTransitions, @@ -31,8 +31,7 @@ import type { AnyStateNodeConfig, ProvidedActor, NonReducibleUnknown, - EventDescriptor, - UnknownActionObject + EventDescriptor } from './types.ts'; import { createInvokeId, @@ -43,6 +42,21 @@ import { const EMPTY_OBJECT = {}; +const toSerializableAction = (action: UnknownAction) => { + if (typeof action === 'string') { + return { type: action }; + } + if (typeof action === 'function') { + if ('resolve' in action) { + return { type: (action as BuiltinAction).type }; + } + return { + type: action.name + }; + } + return action; +}; + interface StateNodeOptions< TContext extends MachineContext, TEvent extends EventObject @@ -85,9 +99,9 @@ export class StateNode< */ public history: false | 'shallow' | 'deep'; /** The action(s) to be executed upon entering the state node. */ - public entry: UnknownActionObject[]; + public entry: UnknownAction[]; /** The action(s) to be executed upon exiting the state node. */ - public exit: UnknownActionObject[]; + public exit: UnknownAction[]; /** The parent state node. */ public parent?: StateNode; /** The root machine node. */ @@ -196,32 +210,8 @@ export class StateNode< this.history = this.config.history === true ? 'shallow' : this.config.history || false; - this.entry = toArray(this.config.entry as UnknownAction).map( - (action, actionIndex) => { - const actionObject = convertAction( - action, - this, - undefined, - 'entry', - 0, - actionIndex - ); - return actionObject; - } - ); - this.exit = toArray(this.config.exit as UnknownAction).map( - (action, actionIndex) => { - const actionObject = convertAction( - action, - this, - undefined, - 'exit', - 0, - actionIndex - ); - return actionObject; - } - ); + this.entry = toArray(this.config.entry).slice(); + this.exit = toArray(this.config.exit).slice(); this.meta = this.config.meta; this.output = @@ -233,8 +223,8 @@ export class StateNode< public _initialize() { this.transitions = formatTransitions(this); if (this.config.always) { - this.always = toTransitionConfigArray(this.config.always).map((t, i) => - formatTransition(this, NULL_EVENT, t, i) + this.always = toTransitionConfigArray(this.config.always).map((t) => + formatTransition(this, NULL_EVENT, t) ); } @@ -254,13 +244,13 @@ export class StateNode< ? { target: this.initial.target, source: this, - actions: this.initial.actions, + actions: this.initial.actions.map(toSerializableAction), eventType: null as any, reenter: false, toJSON: () => ({ target: this.initial.target.map((t) => `#${t.id}`), source: `#${this.id}`, - actions: this.initial.actions, + actions: this.initial.actions.map(toSerializableAction), eventType: null as any }) } @@ -274,8 +264,8 @@ export class StateNode< ...t, actions: t.actions })), - entry: this.entry, - exit: this.exit, + entry: this.entry.map(toSerializableAction), + exit: this.exit.map(toSerializableAction), meta: this.meta, order: this.order || -1, output: this.output, @@ -311,9 +301,6 @@ export class StateNode< typeof src === 'string' ? src : `xstate.invoke.${createInvokeId(this.id, i)}`; - if (typeof src !== 'string') { - this.machine.implementations.actors[sourceName] = src; - } return { ...invokeConfig, @@ -390,7 +377,7 @@ export class StateNode< event: TEvent ): TransitionDefinition[] | undefined { const eventType = event.type; - const actions: UnknownActionObject[] = []; + const actions: UnknownAction[] = []; let selectedTransition: TransitionDefinition | undefined; diff --git a/packages/core/src/actions/assign.ts b/packages/core/src/actions/assign.ts index 515e3c04ae..e4d9bd45dc 100644 --- a/packages/core/src/actions/assign.ts +++ b/packages/core/src/actions/assign.ts @@ -179,9 +179,5 @@ export function assign< assign.resolve = resolveAssign; - assign.toJSON = () => ({ - ...assign - }); - return assign; } diff --git a/packages/core/src/actions/cancel.ts b/packages/core/src/actions/cancel.ts index 1af923ecfe..96d36993ad 100644 --- a/packages/core/src/actions/cancel.ts +++ b/packages/core/src/actions/cancel.ts @@ -102,9 +102,5 @@ export function cancel< cancel.resolve = resolveCancel; cancel.execute = executeCancel; - cancel.toJSON = () => ({ - ...cancel - }); - return cancel; } diff --git a/packages/core/src/actions/emit.ts b/packages/core/src/actions/emit.ts index 4c7ca4b2b5..1fad95846e 100644 --- a/packages/core/src/actions/emit.ts +++ b/packages/core/src/actions/emit.ts @@ -146,9 +146,5 @@ export function emit< emit.resolve = resolveEmit; emit.execute = executeEmit; - emit.toJSON = () => ({ - ...emit - }); - return emit; } diff --git a/packages/core/src/actions/enqueueActions.ts b/packages/core/src/actions/enqueueActions.ts index 8188095d0a..a1d2c83993 100644 --- a/packages/core/src/actions/enqueueActions.ts +++ b/packages/core/src/actions/enqueueActions.ts @@ -1,6 +1,5 @@ import isDevelopment from '#is-development'; import { Guard, evaluateGuard } from '../guards.ts'; -import { convertAction } from '../stateUtils.ts'; import { Action, ActionArgs, @@ -136,16 +135,7 @@ function resolveEnqueueActions( const enqueue: Parameters[0]['enqueue'] = function enqueue( action ) { - actions.push( - convertAction( - action as any, - snapshot.machine.root, - 'enqueue' + Math.random(), // TODO: this should come from state node ID which isn't provided - undefined, - 0, - actions.length - ) - ); + actions.push(action); }; enqueue.assign = (...args) => { actions.push(assign(...args)); @@ -333,9 +323,5 @@ export function enqueueActions< enqueueActions.collect = collect; enqueueActions.resolve = resolveEnqueueActions; - enqueueActions.toJSON = () => ({ - ...enqueueActions - }); - return enqueueActions; } diff --git a/packages/core/src/actions/log.ts b/packages/core/src/actions/log.ts index 5add904afc..a4552a2e49 100644 --- a/packages/core/src/actions/log.ts +++ b/packages/core/src/actions/log.ts @@ -96,9 +96,5 @@ export function log< log.resolve = resolveLog; log.execute = executeLog; - log.toJSON = () => ({ - ...log - }); - return log; } diff --git a/packages/core/src/actions/raise.ts b/packages/core/src/actions/raise.ts index 74ed0d05ae..6f8c76462e 100644 --- a/packages/core/src/actions/raise.ts +++ b/packages/core/src/actions/raise.ts @@ -86,7 +86,7 @@ function resolveRaise( ]; } -function executeRaise( +export function executeRaise( actorScope: AnyActorScope, params: { event: EventObject; @@ -174,10 +174,6 @@ export function raise< raise.resolve = resolveRaise; raise.execute = executeRaise; - raise.toJSON = () => ({ - ...raise - }); - return raise; } diff --git a/packages/core/src/actions/send.ts b/packages/core/src/actions/send.ts index bb38ed803e..4e5060ba41 100644 --- a/packages/core/src/actions/send.ts +++ b/packages/core/src/actions/send.ts @@ -145,7 +145,7 @@ function retryResolveSendTo( } } -function executeSendTo( +export function executeSendTo( actorScope: AnyActorScope, params: { to: AnyActorRef; @@ -270,10 +270,6 @@ export function sendTo< sendTo.retryResolve = retryResolveSendTo; sendTo.execute = executeSendTo; - sendTo.toJSON = () => ({ - ...sendTo - }); - return sendTo; } diff --git a/packages/core/src/actions/spawnChild.ts b/packages/core/src/actions/spawnChild.ts index 3df5f033da..b6f91455c5 100644 --- a/packages/core/src/actions/spawnChild.ts +++ b/packages/core/src/actions/spawnChild.ts @@ -107,8 +107,6 @@ function executeSpawn( } actorScope.defer(() => { - actorRef._parent = actorScope.self; - actorRef.system = actorScope.system; if (actorRef._processingStatus === ProcessingStatus.Stopped) { return; } @@ -222,7 +220,7 @@ export function spawnChild< } } - spawnChild.type = 'xstate.spawn'; + spawnChild.type = 'xstate.spawnChild'; spawnChild.id = id; spawnChild.systemId = systemId; spawnChild.src = src; @@ -232,9 +230,5 @@ export function spawnChild< spawnChild.resolve = resolveSpawn; spawnChild.execute = executeSpawn; - spawnChild.toJSON = () => ({ - ...spawnChild - }); - return spawnChild; } diff --git a/packages/core/src/actions/stopChild.ts b/packages/core/src/actions/stopChild.ts index 0f764ff813..18b7f48c31 100644 --- a/packages/core/src/actions/stopChild.ts +++ b/packages/core/src/actions/stopChild.ts @@ -115,10 +115,6 @@ export function stopChild< stop.resolve = resolveStop; stop.execute = executeStop; - stop.toJSON = () => ({ - ...stop - }); - return stop; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 104d4f5c93..3690d330e6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,6 @@ export { type Interpreter, type RequiredActorOptionsKeys as RequiredActorOptionsKeys } from './createActor.ts'; -// TODO: decide from where those should be exported export { createMachine } from './createMachine.ts'; export { getInitialSnapshot, getNextSnapshot } from './getNextSnapshot.ts'; export { and, not, or, stateIn } from './guards.ts'; @@ -34,7 +33,7 @@ export { pathToStateValue, toObserver } from './utils.ts'; -export { transition } from './transition.ts'; +export { transition, initialTransition } from './transition.ts'; export { waitFor } from './waitFor.ts'; declare global { diff --git a/packages/core/src/stateUtils.ts b/packages/core/src/stateUtils.ts index 4c95bf6a0c..651b9aaaf5 100644 --- a/packages/core/src/stateUtils.ts +++ b/packages/core/src/stateUtils.ts @@ -38,7 +38,6 @@ import { AnyTransitionConfig, ProvidedActor, AnyActorScope, - UnknownActionObject, AnyActorRef, ActionExecutor, ExecutableActionObject @@ -52,6 +51,8 @@ import { isErrorActorEvent } from './utils.ts'; import { createEmptyActor } from './actors/index.ts'; +import { executeRaise } from './actions/raise.ts'; +import { executeSendTo } from './actions/send.ts'; type StateNodeIterable< TContext extends MachineContext, @@ -287,10 +288,10 @@ export function getDelayedTransitions( stateNode.entry.push( raise(afterEvent, { id: eventType, - delay: delay as any // TODO: fix types - }) as unknown as UnknownActionObject + delay + }) ); - stateNode.exit.push(cancel(eventType) as unknown as UnknownActionObject); + stateNode.exit.push(cancel(eventType)); return eventType; }; @@ -308,14 +309,13 @@ export function getDelayedTransitions( delay: resolvedDelay })); }); - return delayedTransitions.map((delayedTransition, i) => { + return delayedTransitions.map((delayedTransition) => { const { delay } = delayedTransition; return { ...formatTransition( stateNode, delayedTransition.event, - delayedTransition, - i + delayedTransition ), delay }; @@ -325,8 +325,7 @@ export function getDelayedTransitions( export function formatTransition( stateNode: AnyStateNode, descriptor: string, - transitionConfig: AnyTransitionConfig, - transitionIndex: number + transitionConfig: AnyTransitionConfig ): AnyTransitionDefinition { const normalizedTarget = normalizeTarget(transitionConfig.target); const reenter = transitionConfig.reenter ?? false; @@ -341,16 +340,7 @@ export function formatTransition( const transition = { ...transitionConfig, - actions: toArray(transitionConfig.actions).map((action, actionIndex) => { - return convertAction( - action, - stateNode, - descriptor, - undefined, - transitionIndex, - actionIndex - ); - }), + actions: toArray(transitionConfig.actions), guard: transitionConfig.guard as never, target, source: stateNode, @@ -386,8 +376,8 @@ export function formatTransitions< const transitionsConfig = stateNode.config.on[descriptor]; transitions.set( descriptor, - toTransitionConfigArray(transitionsConfig).map((t, i) => - formatTransition(stateNode, descriptor, t, i) + toTransitionConfigArray(transitionsConfig).map((t) => + formatTransition(stateNode, descriptor, t) ) ); } @@ -396,8 +386,8 @@ export function formatTransitions< const descriptor = `xstate.done.state.${stateNode.id}`; transitions.set( descriptor, - toTransitionConfigArray(stateNode.config.onDone).map((t, i) => - formatTransition(stateNode, descriptor, t, i) + toTransitionConfigArray(stateNode.config.onDone).map((t) => + formatTransition(stateNode, descriptor, t) ) ); } @@ -406,8 +396,8 @@ export function formatTransitions< const descriptor = `xstate.done.actor.${invokeDef.id}`; transitions.set( descriptor, - toTransitionConfigArray(invokeDef.onDone).map((t, i) => - formatTransition(stateNode, descriptor, t, i) + toTransitionConfigArray(invokeDef.onDone).map((t) => + formatTransition(stateNode, descriptor, t) ) ); } @@ -415,8 +405,8 @@ export function formatTransitions< const descriptor = `xstate.error.actor.${invokeDef.id}`; transitions.set( descriptor, - toTransitionConfigArray(invokeDef.onError).map((t, i) => - formatTransition(stateNode, descriptor, t, i) + toTransitionConfigArray(invokeDef.onError).map((t) => + formatTransition(stateNode, descriptor, t) ) ); } @@ -424,8 +414,8 @@ export function formatTransitions< const descriptor = `xstate.snapshot.${invokeDef.id}`; transitions.set( descriptor, - toTransitionConfigArray(invokeDef.onSnapshot).map((t, i) => - formatTransition(stateNode, descriptor, t, i) + toTransitionConfigArray(invokeDef.onSnapshot).map((t) => + formatTransition(stateNode, descriptor, t) ) ); } @@ -466,11 +456,7 @@ export function formatInitialTransition< const transition: InitialTransitionDefinition = { source: stateNode, actions: - !_target || typeof _target === 'string' - ? [] - : toArray(_target.actions).map((action) => - convertAction(action, stateNode, 'xstate.init', undefined, 0, 0) - ), + !_target || typeof _target === 'string' ? [] : toArray(_target.actions), eventType: null as any, reenter: false, target: resolvedTarget ? [resolvedTarget] : [], @@ -1155,7 +1141,7 @@ function enterStates( (a, b) => a.order - b.order )) { mutStateNodeSet.add(stateNodeToEnter); - const actions: UnknownActionObject[] = []; + const actions: UnknownAction[] = []; // Add entry actions actions.push(...stateNodeToEnter.entry); @@ -1165,7 +1151,7 @@ function enterStates( spawnChild(invokeDef.src, { ...invokeDef, syncSnapshot: !!invokeDef.onSnapshot - }) as unknown as UnknownActionObject + }) ); } @@ -1474,12 +1460,7 @@ function exitStates( nextSnapshot, event, actorScope, - [ - ...s.exit, - ...(s.invoke.map((def) => - stopChild(def.id) - ) as unknown as UnknownActionObject[]) - ], + [...s.exit, ...s.invoke.map((def) => stopChild(def.id))], internalQueue, undefined ); @@ -1488,7 +1469,7 @@ function exitStates( return [nextSnapshot, changedHistory || historyValue] as const; } -interface BuiltinAction { +export interface BuiltinAction { (): void; type: `xstate.${string}`; resolve: ( @@ -1501,7 +1482,7 @@ interface BuiltinAction { ) => [ newState: AnyMachineSnapshot, params: unknown, - actions?: UnknownActionObject[] + actions?: UnknownAction[] ]; retryResolve: ( actorScope: AnyActorScope, @@ -1515,7 +1496,7 @@ function resolveAndExecuteActionsWithContext( currentSnapshot: AnyMachineSnapshot, event: AnyEventObject, actorScope: AnyActorScope, - actions: UnknownActionObject[], + actions: UnknownAction[], extra: { internalQueue: AnyEventObject[]; deferredActorIds: string[] | undefined; @@ -1567,7 +1548,12 @@ function resolveAndExecuteActionsWithContext( if (!resolvedAction || !('resolve' in resolvedAction)) { actorScope.actionExecutor({ - type: action.type, + type: + typeof action === 'string' + ? action + : typeof action === 'object' + ? action.type + : action.name || '(anonymous)', info: actionArgs, params: actionParams, exec: resolvedAction @@ -1619,7 +1605,7 @@ export function resolveActionsAndContext( currentSnapshot: AnyMachineSnapshot, event: AnyEventObject, actorScope: AnyActorScope, - actions: UnknownActionObject[], + actions: UnknownAction[], internalQueue: AnyEventObject[], deferredActorIds: string[] | undefined ): AnyMachineSnapshot { @@ -1772,9 +1758,7 @@ function stopChildren( nextState, event, actorScope, - Object.values(nextState.children).map( - (child: any) => stopChild(child) as unknown as UnknownActionObject - ), + Object.values(nextState.children).map((child: any) => stopChild(child)), [], undefined ); @@ -1834,29 +1818,6 @@ export function resolveStateValue( return getStateValue(rootNode, [...allStateNodes]); } -export function convertAction( - action: UnknownAction, - stateNode: AnyStateNode, - descriptor: string | undefined, - kind: 'entry' | 'exit' | undefined, - transitionIndex: number, - actionIndex: number -): UnknownActionObject { - if (typeof action === 'string') { - return { type: action }; - } - if (typeof action === 'function' && !('resolve' in action)) { - const type = `${stateNode.id}|${ - descriptor ?? kind - }:${transitionIndex}:${actionIndex}`; - stateNode.machine.implementations.actions[type] = action as any; - return { - type - }; - } - return action as any; -} - /** * Runs an executable action. Executable actions are returned from the * `transition(…)` function. @@ -1876,49 +1837,31 @@ export function executeAction( action: ExecutableActionObject, actor: AnyActorRef = createEmptyActor() ) { - const resolvedAction = resolveSpecialAction(action); - const resolvedInfo = { - ...action.info, - self: actor, - system: actor.system - }; - return resolvedAction.exec?.(resolvedInfo, action.params); -} + const actorScope = (actor as any)._actorScope as AnyActorScope; + const defer = actorScope.defer; + actorScope.defer = (fn) => fn(); + try { + switch (action.type) { + case 'xstate.raise': + if (typeof (action as any).params.delay !== 'number') { + return; + } + executeRaise(actorScope, action.params as any); + return; + case 'xstate.sendTo': + executeSendTo(actorScope, action.params as any); + return; + } -function resolveSpecialAction( - action: ExecutableActionObject -): ExecutableActionObject { - const resolvedAction = { ...action }; - switch (action.type) { - case 'xstate.raise': - if ((action.params as any).delay !== undefined) { - resolvedAction.exec = (info, _params) => { - info.system.scheduler.schedule( - info.self, - info.self, - (action.params as any).event, - (action.params as any).delay, - (action.params as any).id - ); - }; - return resolvedAction; - } - break; - case 'xstate.sendTo': - if ((action.params as any).delay !== undefined) { - resolvedAction.exec = (info, _params) => { - info.system.scheduler.schedule( - info.self, - (action.params as any).to, - (action.params as any).event, - (action.params as any).delay, - (action.params as any).id - ); - }; - return resolvedAction; - } - break; + action.exec?.( + { + ...action.info, + self: actor, + system: actor.system + }, + action.params + ); + } finally { + actorScope.defer = defer; } - - return action; } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b410608a64..f7433276d8 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -251,23 +251,6 @@ export type Action< TEmitted >; -export type ActionObject< - TContext extends MachineContext, - TExpressionEvent extends EventObject, - TAction extends ParameterizedObject -> = { - type: string; -} & ( // this way we could iterate over `TAction` (and `TGuard` in the `Guard` type) once and not twice // TODO: consider merging `NoRequiredParams` and `WithDynamicParams` into one - | NoRequiredParams - | WithDynamicParams -); - -export type UnknownActionObject = ActionObject< - MachineContext, - EventObject, - ParameterizedObject ->; - export type UnknownAction = Action< MachineContext, EventObject, @@ -1079,8 +1062,8 @@ export interface StateNodeDefinition< on: TransitionDefinitionMap; transitions: Array>; // TODO: establish what a definition really is - entry: UnknownActionObject[]; - exit: UnknownActionObject[]; + entry: UnknownAction[]; + exit: UnknownAction[]; meta: any; order: number; output?: StateNodeConfig< @@ -1685,14 +1668,14 @@ export interface TransitionDefinition< > { target: ReadonlyArray> | undefined; source: StateNode; - actions: readonly UnknownActionObject[]; + actions: readonly UnknownAction[]; reenter: boolean; guard?: UnknownGuard; eventType: EventDescriptor; toJSON: () => { target: string[] | undefined; source: string; - actions: readonly UnknownActionObject[]; + actions: readonly UnknownAction[]; guard?: UnknownGuard; eventType: EventDescriptor; meta?: Record; @@ -2642,12 +2625,12 @@ export interface ToExecutableAction } export interface ExecutableSpawnAction extends ExecutableActionObject { - type: 'xstate.spawn'; + type: 'xstate.spawnChild'; info: ActionArgs; params: { id: string; actorRef: AnyActorRef | undefined; - src: string; + src: string | AnyActorLogic; }; } diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index 11a7c6eacf..3a8213337b 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -4323,4 +4323,19 @@ describe('actions', () => { ] `); }); + + it('inline actions should not leak into provided actions object', async () => { + const actions = {}; + + const machine = createMachine( + { + entry: () => {} + }, + { actions } + ); + + createActor(machine).start(); + + expect(actions).toEqual({}); + }); }); diff --git a/packages/core/test/actor.test.ts b/packages/core/test/actor.test.ts index d21488fc00..65ff942554 100644 --- a/packages/core/test/actor.test.ts +++ b/packages/core/test/actor.test.ts @@ -1753,4 +1753,53 @@ describe('actors', () => { actor.start(); actor.send({ type: 'event' }); }); + + it('same-position invokes should not leak between machines', async () => { + const spy = jest.fn(); + + const sharedActors = {}; + + const m1 = createMachine( + { + invoke: { + src: fromPromise(async () => 'foo'), + onDone: { + actions: ({ event }) => spy(event.output) + } + } + }, + { actors: sharedActors } + ); + + createMachine( + { + invoke: { src: fromPromise(async () => 100) } + }, + { actors: sharedActors } + ); + + createActor(m1).start(); + + await sleep(1); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('foo'); + }); + + it('inline invokes should not leak into provided actors object', async () => { + const actors = {}; + + const machine = createMachine( + { + invoke: { + src: fromPromise(async () => 'foo') + } + }, + { actors } + ); + + createActor(machine).start(); + + expect(actors).toEqual({}); + }); }); diff --git a/packages/core/test/guards.test.ts b/packages/core/test/guards.test.ts index 1fdadf4e5f..099b6a6063 100644 --- a/packages/core/test/guards.test.ts +++ b/packages/core/test/guards.test.ts @@ -1010,6 +1010,48 @@ describe('guards - other', () => { expect(service.getSnapshot().value).toBe('c'); }); + + it('inline function guard should not leak into provided guards object', async () => { + const guards = {}; + + const machine = createMachine( + { + on: { + FOO: { + guard: () => false, + actions: () => {} + } + } + }, + { guards } + ); + + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'FOO' }); + + expect(guards).toEqual({}); + }); + + it('inline builtin guard should not leak into provided guards object', async () => { + const guards = {}; + + const machine = createMachine( + { + on: { + FOO: { + guard: not(() => false), + actions: () => {} + } + } + }, + { guards } + ); + + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'FOO' }); + + expect(guards).toEqual({}); + }); }); describe('not() guard', () => { diff --git a/packages/core/test/inspect.test.ts b/packages/core/test/inspect.test.ts index fc1cca7b71..87ec10e6eb 100644 --- a/packages/core/test/inspect.test.ts +++ b/packages/core/test/inspect.test.ts @@ -1032,7 +1032,7 @@ describe('inspect', () => { { "action": { "params": undefined, - "type": "(machine).loading|event:0:2", + "type": "(anonymous)", }, "type": "@xstate.action", }, diff --git a/packages/core/test/transition.test.ts b/packages/core/test/transition.test.ts index 55e32012f5..1790078b93 100644 --- a/packages/core/test/transition.test.ts +++ b/packages/core/test/transition.test.ts @@ -17,6 +17,8 @@ import { } from '../src'; import { createDoneActorEvent } from '../src/eventUtils'; import { initialTransition } from '../src/transition'; +import assert from 'node:assert'; +import { resolveReferencedActor } from '../src/utils'; describe('transition function', () => { it('should capture actions', () => { @@ -67,7 +69,7 @@ describe('transition function', () => { expect(stringAction).not.toHaveBeenCalled(); // Execute actions - actions0.forEach((a) => machine.executeAction(a, {} as any)); + actions0.forEach((a) => machine.executeAction(a)); expect(actionWithParams).toHaveBeenCalledWith(expect.anything(), { a: 1 }); expect(stringAction).toHaveBeenCalled(); @@ -88,7 +90,7 @@ describe('transition function', () => { expect(actionWithDynamicParams).not.toHaveBeenCalled(); // Execute actions - actions1.forEach((a) => machine.executeAction(a, {} as any)); + actions1.forEach((a) => machine.executeAction(a)); expect(actionWithDynamicParams).toHaveBeenCalledWith({ msg: 'hello' @@ -415,11 +417,15 @@ describe('transition function', () => { async function execute(action: ExecutableActionsFrom) { switch (action.type) { - case 'xstate.spawn': { + case 'xstate.spawnChild': { const spawnAction = action as ExecutableSpawnAction; - const logic = machine.implementations.actors[spawnAction.params.src]; + const logic = + typeof spawnAction.params.src === 'string' + ? resolveReferencedActor(machine, spawnAction.params.src) + : spawnAction.params.src; + assert('transition' in logic); const output = await toPromise( - createActor(logic as any, spawnAction.params).start() + createActor(logic, spawnAction.params).start() ); postEvent(createDoneActorEvent(spawnAction.params.id, output)); } diff --git a/packages/core/test/utils.ts b/packages/core/test/utils.ts index 63bc001402..a97ce6cccd 100644 --- a/packages/core/test/utils.ts +++ b/packages/core/test/utils.ts @@ -81,21 +81,15 @@ export function trackEntries(machine: AnyStateMachine) { let logs: string[] = []; - machine.implementations.actions['__log'] = function __logAction(_, params) { - logs.push((params as any).msg); - }; - function addTrackingActions( state: StateNode, stateDescription: string ) { - state.entry.unshift({ - type: '__log', - params: { msg: `enter: ${stateDescription}` } + state.entry.unshift(function __testEntryTracker() { + logs.push(`enter: ${stateDescription}`); }); - state.exit.unshift({ - type: '__log', - params: { msg: `exit: ${stateDescription}` } + state.exit.unshift(function __testExitTracker() { + logs.push(`exit: ${stateDescription}`); }); }