From b2f4da1f7ff3e36fabc18103ceb9da9e33bb93ff Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Mon, 29 Apr 2024 00:28:50 -0400 Subject: [PATCH 1/6] Use inline actions --- packages/core/src/actions/send.ts | 3 +- packages/core/src/stateUtils.ts | 54 +- packages/core/src/types.ts | 70 ++- packages/core/test/actions.test.ts | 890 ++++++++++++++++++++++++++++- packages/core/test/assign.test.ts | 447 ++++++++++++++- 5 files changed, 1436 insertions(+), 28 deletions(-) diff --git a/packages/core/src/actions/send.ts b/packages/core/src/actions/send.ts index 2f4f31c8d0..9804d6225d 100644 --- a/packages/core/src/actions/send.ts +++ b/packages/core/src/actions/send.ts @@ -234,7 +234,8 @@ export function sendTo< > { function sendTo( args: ActionArgs, - params: TParams + params: TParams, + _: any ) { if (isDevelopment) { throw new Error(`This isn't supposed to be called`); diff --git a/packages/core/src/stateUtils.ts b/packages/core/src/stateUtils.ts index bb1950d942..2dea121c28 100644 --- a/packages/core/src/stateUtils.ts +++ b/packages/core/src/stateUtils.ts @@ -1,7 +1,7 @@ import isDevelopment from '#is-development'; import { MachineSnapshot, cloneMachineSnapshot } from './State.ts'; import type { StateNode } from './StateNode.ts'; -import { raise } from './actions.ts'; +import { assign, raise, sendTo } from './actions.ts'; import { createAfterEvent, createDoneStateEvent } from './eventUtils.ts'; import { cancel } from './actions/cancel.ts'; import { spawnChild } from './actions/spawnChild.ts'; @@ -38,7 +38,9 @@ import { ActionFunction, AnyTransitionConfig, ProvidedActor, - AnyActorScope + AnyActorScope, + ActionFunctionEnqueuer, + AnyActorRef } from './types.ts'; import { resolveOutput, @@ -1563,12 +1565,58 @@ function resolveAndExecuteActionsWithContext( params: actionParams } }); - resolvedAction(actionArgs, actionParams); + const actions: UnknownAction[] = []; + const enq: ActionFunctionEnqueuer< + any, + any, + any, + any, + any, + any, + any, + any + > = { + assign: (...args) => { + actions.push(assign(...args) as UnknownAction); + }, + action: (actionFn) => { + actions.push(actionFn); + }, + raise: (...args) => { + actions.push(raise(...args) as UnknownAction); + }, + sendTo: (...args) => { + actions.push(sendTo(...args) as UnknownAction); + }, + check: (guard) => + evaluateGuard( + guard, + intermediateSnapshot.context, + event, + intermediateSnapshot + ) + }; + resolvedAction(actionArgs, actionParams, enq); + + if (actions.length) { + intermediateSnapshot = resolveAndExecuteActionsWithContext( + intermediateSnapshot, + event, + actorScope, + actions, + extra, + retries + ); + + return intermediateSnapshot; + } } if (!('resolve' in resolvedAction)) { if (actorScope.self._processingStatus === ProcessingStatus.Running) { executeAction(); + } else if (resolvedAction.length === 3) { + executeAction(); } else { actorScope.defer(() => { executeAction(); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5060412f9a..3c81e2cdeb 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,7 +1,7 @@ import type { MachineSnapshot } from './State.ts'; import type { StateMachine } from './StateMachine.ts'; import type { StateNode } from './StateNode.ts'; -import { AssignArgs } from './actions/assign.ts'; +import { AssignArgs, assign } from './actions/assign.ts'; import { PromiseActorLogic } from './actors/promise.ts'; import { Guard, GuardPredicate, UnknownGuard } from './guards.ts'; import type { Actor, ProcessingStatus } from './createActor.ts'; @@ -13,6 +13,8 @@ import { TypegenConstraint, TypegenDisabled } from './typegenTypes.ts'; +import { raise } from './actions/raise.ts'; +import { sendTo } from './actions/send.ts'; export type Identity = { [K in keyof T]: T[K] }; @@ -173,6 +175,57 @@ export type OutputFrom = T extends ActorLogic< ? (TSnapshot & { status: 'done' })['output'] : never; +export interface ActionFunctionEnqueuer< + TContext extends MachineContext, + TExpressionEvent extends EventObject, + TEvent extends EventObject, + TActor extends ProvidedActor, + TAction extends ParameterizedObject, + TGuard extends ParameterizedObject, + TDelay extends string, + TEmitted extends EventObject +> { + assign: ( + ...args: Parameters< + typeof assign + > + ) => void; + raise: ( + ...args: Parameters< + typeof raise< + TContext, + TExpressionEvent, + TEvent, + undefined, + TDelay, + TDelay + > + > + ) => void; + action: ( + actionFn: + | NoRequiredParams + | WithDynamicParams + | (() => void) + ) => void; + check: ( + guard: Guard + ) => boolean; + sendTo: ( + ...args: Parameters< + typeof sendTo< + TContext, + TExpressionEvent, + undefined, + TTargetActor, + TEvent, + TDelay, + TDelay + > + > + ) => void; +} + export type ActionFunction< TContext extends MachineContext, TExpressionEvent extends EventObject, @@ -184,7 +237,20 @@ export type ActionFunction< TDelay extends string, TEmitted extends EventObject > = { - (args: ActionArgs, params: TParams): void; + ( + args: ActionArgs, + params: TParams, + enqueue: ActionFunctionEnqueuer< + TContext, + TExpressionEvent, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TEmitted + > + ): void; _out_TEvent?: TEvent; // TODO: it feels like we should be able to remove this since now `TEvent` is "observable" by `self` _out_TActor?: TActor; _out_TAction?: TAction; diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index 79e1cd8f2e..b55436fa0a 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -1733,7 +1733,7 @@ describe('entry/exit actions', () => { interpreter.stop(); }); - it('should note execute referenced custom actions correctly when stopping an interpreter', () => { + it('should not execute referenced custom actions correctly when stopping an interpreter', () => { const spy = jest.fn(); const parent = createMachine( { @@ -2883,6 +2883,372 @@ describe('enqueueActions', () => { }); }); +describe('with inline actions', () => { + it('should execute a simple referenced action', () => { + const spy = jest.fn(); + + const machine = createMachine( + { + // entry: enqueueActions(({ enqueue }) => { + // enqueue('someAction'); + // }) + entry: (_, __, x) => { + x.action('someAction'); + } + }, + { + actions: { + someAction: spy + } + } + ); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should execute multiple different referenced actions', () => { + const spy1 = jest.fn(); + const spy2 = jest.fn(); + + const machine = createMachine( + { + // entry: enqueueActions(({ enqueue }) => { + // enqueue('someAction'); + // enqueue('otherAction'); + // }) + entry: (_, _params, x) => { + x.action('someAction'); + x.action('otherAction'); + } + }, + { + actions: { + someAction: spy1, + otherAction: spy2 + } + } + ); + + createActor(machine).start(); + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(1); + }); + + it('should execute multiple same referenced actions', () => { + const spy = jest.fn(); + + const machine = createMachine( + { + entry: (_, _params, x) => { + x.action('someAction'); + x.action('someAction'); + } + }, + { + actions: { + someAction: spy + } + } + ); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should execute a parameterized action', () => { + const spy = jest.fn(); + + const machine = createMachine( + { + // entry: enqueueActions(({ enqueue }) => { + // enqueue({ + // type: 'someAction', + // params: { answer: 42 } + // }); + // }), + entry: (_, _params, x) => { + x.action({ + type: 'someAction', + params: { answer: 42 } + }); + } + }, + { + actions: { + someAction: (_, params) => spy(params) + } + } + ); + + createActor(machine).start(); + + expect(spy).toMatchMockCallsInlineSnapshot(` + [ + [ + { + "answer": 42, + }, + ], + ] + `); + }); + + it('should execute a function', () => { + const spy = jest.fn(); + + const machine = createMachine({ + entry: (_, _params, x) => { + x.action(spy); + } + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should execute a builtin action using its own action creator', () => { + const spy = jest.fn(); + + const machine = createMachine({ + on: { + FOO: { + // actions: enqueueActions(({ enqueue }) => { + // enqueue( + // raise({ + // type: 'RAISED' + // }) + // ); + // }) + actions: (_, _params, x) => { + x.raise({ + type: 'RAISED' + }); + } + }, + RAISED: { + actions: spy + } + } + }); + + const actorRef = createActor(machine).start(); + + actorRef.send({ type: 'FOO' }); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should execute a builtin action using its bound action creator', () => { + const spy = jest.fn(); + + const machine = createMachine({ + on: { + FOO: { + // actions: enqueueActions(({ enqueue }) => { + // enqueue.raise({ + // type: 'RAISED' + // }); + // }) + actions: (_, _params, x) => { + x.raise({ + type: 'RAISED' + }); + } + }, + RAISED: { + actions: spy + } + } + }); + + const actorRef = createActor(machine).start(); + + actorRef.send({ type: 'FOO' }); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should execute assigns when resolving the initial snapshot', () => { + const machine = createMachine({ + context: { + count: 0 + }, + entry: (_, __, x) => { + x.assign({ + count: 42 + }); + } + }); + + const snapshot = createActor(machine).getSnapshot(); + + expect(snapshot.context).toEqual({ count: 42 }); + }); + + it('should be able to check a simple referenced guard', () => { + const spy = jest.fn().mockImplementation(() => true); + const machine = createMachine( + { + context: { + count: 0 + }, + // entry: enqueueActions(({ check }) => { + // check('alwaysTrue'); + // }) + entry: (_, __, x) => { + x.check('alwaysTrue'); + } + }, + { + guards: { + alwaysTrue: spy + } + } + ); + + createActor(machine); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should be able to check a parameterized guard', () => { + const spy = jest.fn(); + + const machine = createMachine( + { + context: { + count: 0 + }, + // entry: enqueueActions(({ check }) => { + // check({ + // type: 'alwaysTrue', + // params: { + // max: 100 + // } + // }); + // }) + entry: (_, __, x) => { + x.check({ + type: 'alwaysTrue', + params: { + max: 100 + } + }); + } + }, + { + guards: { + alwaysTrue: (_, params) => { + spy(params); + return true; + } + } + } + ); + + createActor(machine); + + expect(spy).toMatchMockCallsInlineSnapshot(` + [ + [ + { + "max": 100, + }, + ], + ] + `); + }); + + it('should provide self', () => { + expect.assertions(1); + const machine = createMachine({ + // entry: enqueueActions(({ self }) => { + // expect(self.send).toBeDefined(); + // }) + entry: ({ self }) => { + expect(self.send).toBeDefined(); + } + }); + + createActor(machine).start(); + }); + + it('should be able to communicate with the parent using params', () => { + type ParentEvent = { type: 'FOO' }; + + const childMachine = setup({ + types: {} as { + input: { + parent?: ActorRef, ParentEvent>; + }; + context: { + parent?: ActorRef, ParentEvent>; + }; + }, + actions: { + // mySendParent: enqueueActions( + // ({ context, enqueue }, event: ParentEvent) => { + // if (!context.parent) { + // // it's here just for illustration purposes + // console.log( + // 'WARN: an attempt to send an event to a non-existent parent' + // ); + // return; + // } + // enqueue.sendTo(context.parent, event); + // } + // ) + mySendParent: ({ context }, params: ParentEvent, x) => { + if (!context.parent) { + // it's here just for illustration purposes + console.log( + 'WARN: an attempt to send an event to a non-existent parent' + ); + return; + } + x.sendTo(context.parent, params); + } + } + }).createMachine({ + context: ({ input }) => ({ parent: input.parent }), + entry: { + type: 'mySendParent', + params: { + type: 'FOO' + } + } + }); + + const spy = jest.fn(); + + const parentMachine = setup({ + types: {} as { events: ParentEvent }, + actors: { + child: childMachine + } + }).createMachine({ + on: { + FOO: { + actions: spy + } + }, + invoke: { + src: 'child', + input: ({ self }) => ({ parent: self }) + } + }); + + createActor(parentMachine).start(); + + expect(spy).toHaveBeenCalledTimes(1); + }); +}); + describe('sendParent', () => { // https://github.com/statelyai/xstate/issues/711 it('TS: should compile for any event', () => { @@ -2897,18 +3263,240 @@ describe('sendParent', () => { id: 'child', initial: 'start', states: { - start: { - // This should not be a TypeScript error - entry: [sendParent({ type: 'PARENT' })] + start: { + // This should not be a TypeScript error + entry: [sendParent({ type: 'PARENT' })] + } + } + }); + + expect(child).toBeTruthy(); + }); +}); + +describe('sendTo', () => { + it('should be able to send an event to an actor', (done) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => done() + } + } + } + } + }); + + const parentMachine = createMachine({ + types: {} as { + context: { + child: ActorRefFrom; + }; + }, + context: ({ spawn }) => ({ + child: spawn(childMachine) + }), + entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + }); + + createActor(parentMachine).start(); + }); + + it('should be able to send an event from expression to an actor', (done) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT'; count: number }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => done() + } + } + } + } + }); + + const parentMachine = createMachine({ + types: {} as { + context: { + child: ActorRefFrom; + count: number; + }; + }, + context: ({ spawn }) => { + return { + child: spawn(childMachine, { id: 'child' }), + count: 42 + }; + }, + entry: sendTo( + ({ context }) => context.child, + ({ context }) => ({ type: 'EVENT', count: context.count }) + ) + }); + + createActor(parentMachine).start(); + }); + + it('should report a type error for an invalid event', () => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: {} + } + } + } + }); + + createMachine({ + types: {} as { + context: { + child: ActorRefFrom; + }; + }, + context: ({ spawn }) => ({ + child: spawn(childMachine) + }), + entry: sendTo(({ context }) => context.child, { + // @ts-expect-error + type: 'UNKNOWN' + }) + }); + }); + + it('should be able to send an event to a named actor', (done) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => done() + } + } + } + } + }); + + const parentMachine = createMachine({ + types: {} as { context: { child: ActorRefFrom } }, + context: ({ spawn }) => ({ + child: spawn(childMachine, { id: 'child' }) + }), + // No type-safety for the event yet + entry: sendTo('child', { type: 'EVENT' }) + }); + + createActor(parentMachine).start(); + }); + + it('should be able to send an event directly to an ActorRef', (done) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'EVENT' }; + }, + initial: 'waiting', + states: { + waiting: { + on: { + EVENT: { + actions: () => done() + } + } + } + } + }); + + const parentMachine = createMachine({ + types: {} as { context: { child: ActorRefFrom } }, + context: ({ spawn }) => ({ + child: spawn(childMachine) + }), + entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + }); + + createActor(parentMachine).start(); + }); + + it('should be able to read from event', () => { + expect.assertions(1); + const machine = createMachine({ + types: {} as { + context: Record>; + events: { type: 'EVENT'; value: string }; + }, + initial: 'a', + context: ({ spawn }) => ({ + foo: spawn( + fromCallback(({ receive }) => { + receive((event) => { + expect(event).toEqual({ type: 'EVENT' }); + }); + }) + ) + }), + states: { + a: { + on: { + EVENT: { + actions: sendTo(({ context, event }) => context[event.value], { + type: 'EVENT' + }) + } + } } } }); - expect(child).toBeTruthy(); + const service = createActor(machine).start(); + + service.send({ type: 'EVENT', value: 'foo' }); + }); + + it('should error if given a string', () => { + const machine = createMachine({ + invoke: { + id: 'child', + src: fromCallback(() => {}) + }, + entry: sendTo('child', 'a string') + }); + + const errorSpy = jest.fn(); + + const actorRef = createActor(machine); + actorRef.subscribe({ + error: errorSpy + }); + actorRef.start(); + + expect(errorSpy).toMatchMockCallsInlineSnapshot(` + [ + [ + [Error: Only event objects may be used with sendTo; use sendTo({ type: "a string" }) instead], + ], + ] + `); }); }); -describe('sendTo', () => { +describe('sendTo with inline actions', () => { it('should be able to send an event to an actor', (done) => { const childMachine = createMachine({ types: {} as { @@ -2935,7 +3523,10 @@ describe('sendTo', () => { context: ({ spawn }) => ({ child: spawn(childMachine) }), - entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + // entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + entry: (_, _params, x) => { + x.sendTo(({ context }) => context.child, { type: 'EVENT' }); + } }); createActor(parentMachine).start(); @@ -2971,10 +3562,16 @@ describe('sendTo', () => { count: 42 }; }, - entry: sendTo( - ({ context }) => context.child, - ({ context }) => ({ type: 'EVENT', count: context.count }) - ) + // entry: sendTo( + // ({ context }) => context.child, + // ({ context }) => ({ type: 'EVENT', count: context.count }) + // ) + entry: (_, _params, x) => { + x.sendTo( + ({ context }) => context.child, + ({ context }) => ({ type: 'EVENT', count: context.count }) + ); + } }); createActor(parentMachine).start(); @@ -3004,10 +3601,16 @@ describe('sendTo', () => { context: ({ spawn }) => ({ child: spawn(childMachine) }), - entry: sendTo(({ context }) => context.child, { - // @ts-expect-error - type: 'UNKNOWN' - }) + // entry: sendTo(({ context }) => context.child, { + // // @ts-expect-error + // type: 'UNKNOWN' + // }) + entry: (_, _params, x) => { + x.sendTo(({ context }) => context.child, { + // @ts-expect-error + type: 'UNKNOWN' + }); + } }); }); @@ -3034,7 +3637,9 @@ describe('sendTo', () => { child: spawn(childMachine, { id: 'child' }) }), // No type-safety for the event yet - entry: sendTo('child', { type: 'EVENT' }) + entry: (_, _params, x) => { + x.sendTo('child', { type: 'EVENT' }); + } }); createActor(parentMachine).start(); @@ -3062,7 +3667,9 @@ describe('sendTo', () => { context: ({ spawn }) => ({ child: spawn(childMachine) }), - entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) + entry: (_, _params, x) => { + x.sendTo(({ context }) => context.child, { type: 'EVENT' }); + } }); createActor(parentMachine).start(); @@ -3089,9 +3696,11 @@ describe('sendTo', () => { a: { on: { EVENT: { - actions: sendTo(({ context, event }) => context[event.value], { - type: 'EVENT' - }) + actions: (_, _params, x) => { + x.sendTo(({ context, event }) => context[event.value], { + type: 'EVENT' + }); + } } } } @@ -3109,7 +3718,9 @@ describe('sendTo', () => { id: 'child', src: fromCallback(() => {}) }, - entry: sendTo('child', 'a string') + entry: (_, _params, x) => { + x.sendTo('child', 'a string'); + } }); const errorSpy = jest.fn(); @@ -3336,6 +3947,243 @@ describe('raise', () => { }); }); +describe('raise with inline actions', () => { + it('should be able to send a delayed event to itself', (done) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + // entry: raise( + // { type: 'EVENT' }, + // { + // delay: 1 + // } + // ), + entry: (_, _params, x) => { + x.raise({ type: 'EVENT' }, { delay: 1 }); + }, + on: { + TO_B: 'b' + } + }, + b: { + on: { + EVENT: 'c' + } + }, + c: { + type: 'final' + } + } + }); + + const service = createActor(machine).start(); + + service.subscribe({ complete: () => done() }); + + // Ensures that the delayed self-event is sent when in the `b` state + service.send({ type: 'TO_B' }); + }); + + it('should be able to send a delayed event to itself with delay = 0', (done) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + // entry: raise( + // { type: 'EVENT' }, + // { + // delay: 0 + // } + // ), + entry: (_, _params, x) => { + x.raise({ type: 'EVENT' }, { delay: 0 }); + }, + on: { + EVENT: 'b' + } + }, + b: {} + } + }); + + const service = createActor(machine).start(); + + // The state should not be changed yet; `delay: 0` is equivalent to `setTimeout(..., 0)` + expect(service.getSnapshot().value).toEqual('a'); + + setTimeout(() => { + // The state should be changed now + expect(service.getSnapshot().value).toEqual('b'); + done(); + }); + }); + + it('should be able to raise an event and respond to it in the same state', () => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + // entry: raise({ type: 'TO_B' }), + entry: (_, _params, x) => { + x.raise({ type: 'TO_B' }); + }, + on: { + TO_B: 'b' + } + }, + b: { + type: 'final' + } + } + }); + + const service = createActor(machine).start(); + + expect(service.getSnapshot().value).toEqual('b'); + }); + + it('should be able to raise a delayed event and respond to it in the same state', (done) => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + // entry: raise( + // { type: 'TO_B' }, + // { + // delay: 100 + // } + // ), + entry: (_, _params, x) => { + x.raise( + { type: 'TO_B' }, + { + delay: 100 + } + ); + }, + on: { + TO_B: 'b' + } + }, + b: { + type: 'final' + } + } + }); + + const service = createActor(machine).start(); + + service.subscribe({ complete: () => done() }); + + setTimeout(() => { + // didn't transition yet + expect(service.getSnapshot().value).toEqual('a'); + }, 50); + }); + + it('should accept event expression', () => { + const machine = createMachine({ + initial: 'a', + states: { + a: { + on: { + NEXT: { + // actions: raise(() => ({ type: 'RAISED' })) + actions: (_, _params, x) => { + x.raise(() => ({ type: 'RAISED' })); + } + }, + RAISED: 'b' + } + }, + b: {} + } + }); + + const actor = createActor(machine).start(); + + actor.send({ type: 'NEXT' }); + + expect(actor.getSnapshot().value).toBe('b'); + }); + + it('should be possible to access context in the event expression', () => { + type MachineEvent = + | { + type: 'RAISED'; + } + | { + type: 'NEXT'; + }; + interface MachineContext { + eventType: MachineEvent['type']; + } + const machine = createMachine({ + types: {} as { context: MachineContext; events: MachineEvent }, + initial: 'a', + context: { + eventType: 'RAISED' + }, + states: { + a: { + on: { + NEXT: { + // actions: raise(({ context }) => ({ + // type: context.eventType + // })) + actions: (_, _params, x) => { + x.raise(({ context }) => ({ + type: context.eventType + })); + } + }, + RAISED: 'b' + } + }, + b: {} + } + }); + + const actor = createActor(machine).start(); + + actor.send({ type: 'NEXT' }); + + expect(actor.getSnapshot().value).toBe('b'); + }); + + it('should error if given a string', () => { + const machine = createMachine({ + // entry: raise( + // // @ts-ignore + // 'a string' + // ) + entry: (_, _params, x) => { + x.raise( + // @ts-expect-error + 'a string' + ); + } + }); + + const errorSpy = jest.fn(); + + const actorRef = createActor(machine); + actorRef.subscribe({ + error: errorSpy + }); + actorRef.start(); + + expect(errorSpy).toMatchMockCallsInlineSnapshot(` + [ + [ + [Error: Only event objects may be used with raise; use raise({ type: "a string" }) instead], + ], + ] + `); + }); +}); + describe('cancel', () => { it('should be possible to cancel a raised delayed event', () => { const machine = createMachine({ diff --git a/packages/core/test/assign.test.ts b/packages/core/test/assign.test.ts index b0e8055fb4..72d12f9291 100644 --- a/packages/core/test/assign.test.ts +++ b/packages/core/test/assign.test.ts @@ -2,7 +2,8 @@ import { assign, createActor, createMachine, - enqueueActions + enqueueActions, + setup } from '../src/index.ts'; interface CounterContext { @@ -285,6 +286,371 @@ describe('assign', () => { }); }); +describe('assign with inline actions', () => { + const counterMachine = createMachine({ + types: {} as { context: CounterContext }, + initial: 'counting', + context: { count: 0, foo: 'bar' }, + states: { + counting: { + on: { + INC: [ + { + target: 'counting', + actions: (_, _params, x) => { + x.assign(({ context }) => ({ + count: context.count + 1 + })); + } + } + ], + DEC: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: ({ context }) => context.count - 1 + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: ({ context }) => context.count - 1 + }); + } + } + ], + WIN_PROP: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: () => 100, + // foo: () => 'win' + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: () => 100, + foo: () => 'win' + }); + } + } + ], + WIN_STATIC: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: 100, + // foo: 'win' + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: 100, + foo: 'win' + }); + } + } + ], + WIN_MIX: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: () => 100, + // foo: 'win' + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: () => 100, + foo: 'win' + }); + } + } + ], + WIN: [ + { + target: 'counting', + // actions: [ + // assign(() => ({ + // count: 100, + // foo: 'win' + // })) + // ] + actions: (_, _params, x) => { + x.assign(() => ({ + count: 100, + foo: 'win' + })); + } + } + ], + SET_MAYBE: [ + { + // actions: [ + // assign({ + // maybe: 'defined' + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + maybe: 'defined' + }); + } + } + ] + } + } + } + }); + + it('applies the assignment to the external state (property assignment)', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'DEC' + }); + const oneState = actorRef.getSnapshot(); + + expect(oneState.value).toEqual('counting'); + expect(oneState.context).toEqual({ count: -1, foo: 'bar' }); + + actorRef.send({ type: 'DEC' }); + const twoState = actorRef.getSnapshot(); + + expect(twoState.value).toEqual('counting'); + expect(twoState.context).toEqual({ count: -2, foo: 'bar' }); + }); + + it('applies the assignment to the external state', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'INC' + }); + const oneState = actorRef.getSnapshot(); + + expect(oneState.value).toEqual('counting'); + expect(oneState.context).toEqual({ count: 1, foo: 'bar' }); + + actorRef.send({ type: 'INC' }); + const twoState = actorRef.getSnapshot(); + + expect(twoState.value).toEqual('counting'); + expect(twoState.context).toEqual({ count: 2, foo: 'bar' }); + }); + + it('applies the assignment to multiple properties (property assignment)', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'WIN_PROP' + }); + + expect(actorRef.getSnapshot().context).toEqual({ count: 100, foo: 'win' }); + }); + + it('applies the assignment to multiple properties (static)', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'WIN_STATIC' + }); + + expect(actorRef.getSnapshot().context).toEqual({ count: 100, foo: 'win' }); + }); + + it('applies the assignment to multiple properties (static + prop assignment)', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'WIN_MIX' + }); + + expect(actorRef.getSnapshot().context).toEqual({ count: 100, foo: 'win' }); + }); + + it('applies the assignment to multiple properties', () => { + const actorRef = createActor(counterMachine).start(); + actorRef.send({ + type: 'WIN' + }); + + expect(actorRef.getSnapshot().context).toEqual({ count: 100, foo: 'win' }); + }); + + it('applies the assignment to the explicit external state (property assignment)', () => { + // const machine = createCounterMachine({ count: 50, foo: 'bar' }); + const machine = createMachine({ + types: {} as { context: CounterContext }, + initial: 'counting', + context: { count: 50, foo: 'bar' }, + states: { + counting: { + on: { + DEC: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: ({ context }) => context.count - 1 + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: ({ context }) => context.count - 1 + }); + } + } + ] + } + } + } + }); + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'DEC' }); + const oneState = actorRef.getSnapshot(); + + expect(oneState.value).toEqual('counting'); + expect(oneState.context).toEqual({ count: 49, foo: 'bar' }); + + actorRef.send({ type: 'DEC' }); + const twoState = actorRef.getSnapshot(); + + expect(twoState.value).toEqual('counting'); + expect(twoState.context).toEqual({ count: 48, foo: 'bar' }); + + const machine2 = createCounterMachine({ count: 100, foo: 'bar' }); + + const actorRef2 = createActor(machine2).start(); + actorRef2.send({ type: 'DEC' }); + const threeState = actorRef2.getSnapshot(); + + expect(threeState.value).toEqual('counting'); + expect(threeState.context).toEqual({ count: 99, foo: 'bar' }); + }); + + it('applies the assignment to the explicit external state', () => { + const machine = createMachine({ + types: {} as { context: CounterContext }, + initial: 'counting', + context: { count: 50, foo: 'bar' }, + states: { + counting: { + on: { + INC: [ + { + target: 'counting', + actions: (_, _params, x) => { + x.assign(({ context }) => ({ + count: context.count + 1 + })); + } + } + ], + DEC: [ + { + target: 'counting', + // actions: [ + // assign({ + // count: ({ context }) => context.count - 1 + // }) + // ] + actions: (_, _params, x) => { + x.assign({ + count: ({ context }) => context.count - 1 + }); + } + } + ] + } + } + } + }); + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'INC' }); + const oneState = actorRef.getSnapshot(); + + expect(oneState.value).toEqual('counting'); + expect(oneState.context).toEqual({ count: 51, foo: 'bar' }); + + actorRef.send({ type: 'INC' }); + const twoState = actorRef.getSnapshot(); + + expect(twoState.value).toEqual('counting'); + expect(twoState.context).toEqual({ count: 52, foo: 'bar' }); + + const machine2 = createCounterMachine({ count: 102, foo: 'bar' }); + + const actorRef2 = createActor(machine2).start(); + actorRef2.send({ type: 'INC' }); + const threeState = actorRef2.getSnapshot(); + + expect(threeState.value).toEqual('counting'); + expect(threeState.context).toEqual({ count: 103, foo: 'bar' }); + }); + + it('should maintain state after unhandled event', () => { + const actorRef = createActor(counterMachine).start(); + + actorRef.send({ + type: 'FAKE_EVENT' + }); + const nextState = actorRef.getSnapshot(); + + expect(nextState.context).toBeDefined(); + expect(nextState.context).toEqual({ count: 0, foo: 'bar' }); + }); + + it('sets undefined properties', () => { + const actorRef = createActor(counterMachine).start(); + + actorRef.send({ + type: 'SET_MAYBE' + }); + + const nextState = actorRef.getSnapshot(); + + expect(nextState.context.maybe).toBeDefined(); + expect(nextState.context).toEqual({ + count: 0, + foo: 'bar', + maybe: 'defined' + }); + }); + + it('can assign from event', () => { + const machine = createMachine({ + types: {} as { + context: { count: number }; + events: { type: 'INC'; value: number }; + }, + initial: 'active', + context: { + count: 0 + }, + states: { + active: { + on: { + INC: { + // actions: assign({ + // count: ({ event }) => event.value + // }) + actions: (_, _params, x) => { + x.assign({ + count: ({ event }) => event.value + }); + } + } + } + } + } + }); + + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'INC', value: 30 }); + + expect(actorRef.getSnapshot().context.count).toEqual(30); + }); +}); + describe('assign meta', () => { it('should provide the parametrized action to the assigner', () => { const machine = createMachine( @@ -366,3 +732,82 @@ describe('assign meta', () => { service.send({ type: 'EVENT' }); }); }); + +describe('assign meta with inline actions', () => { + it('should provide the parametrized action to the assigner', () => { + const machine = setup({ + types: { + context: {} as { count: number } + }, + actions: { + inc: (_, params: { by: number }, x) => { + x.assign(({ context }) => ({ + count: context.count + params.by + })); + } + } + }).createMachine({ + context: { count: 1 }, + entry: { + type: 'inc', + params: { by: 10 } + } + }); + + const actor = createActor(machine).start(); + + expect(actor.getSnapshot().context.count).toEqual(11); + }); + + it('should provide the action parameters to the partial assigner', () => { + const machine = setup({ + types: { + context: {} as { count: number } + }, + actions: { + inc: (_, params: { by: number }, x) => { + x.assign({ + count: ({ context }) => context.count + params.by + }); + } + } + }).createMachine({ + context: { count: 1 }, + entry: { + type: 'inc', + params: { by: 10 } + } + }); + + const actor = createActor(machine).start(); + + expect(actor.getSnapshot().context.count).toEqual(11); + }); + + it('a parameterized action that resolves to assign() should be provided the params', (done) => { + const machine = setup({ + actions: { + inc: (_, params: { value: number }, x) => { + x.assign(({ context }) => { + expect(params).toEqual({ value: 5 }); + done(); + return context; + }); + } + } + }).createMachine({ + on: { + EVENT: { + actions: { + type: 'inc', + params: { value: 5 } + } + } + } + }); + + const service = createActor(machine).start(); + + service.send({ type: 'EVENT' }); + }); +}); From 25a0ec19fb06a3d53fd9bfb482e21529157229c2 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Mon, 29 Apr 2024 08:43:06 -0400 Subject: [PATCH 2/6] More tests --- packages/core/src/types.ts | 2 +- packages/core/test/actions.test.ts | 471 +++++++++++++++++++++++++++++ 2 files changed, 472 insertions(+), 1 deletion(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 3c81e2cdeb..8a1f7e3d2c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -206,7 +206,7 @@ export interface ActionFunctionEnqueuer< actionFn: | NoRequiredParams | WithDynamicParams - | (() => void) + | ((args: ActionArgs) => void) ) => void; check: ( guard: Guard diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index b55436fa0a..8831aa7be4 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -4397,6 +4397,94 @@ describe('assign action order', () => { }); }); +describe('assign action order with inline actions', () => { + it('should preserve action order', () => { + const captured: number[] = []; + + const machine = createMachine({ + types: {} as { + context: { count: number }; + }, + context: { count: 0 }, + entry: (_, _params, x) => { + x.action(({ context }) => captured.push(context.count)); // 0 + x.assign({ count: ({ context }) => context.count + 1 }); + x.action(({ context }) => captured.push(context.count)); // 1 + x.assign({ count: ({ context }) => context.count + 1 }); + x.action(({ context }) => captured.push(context.count)); // 2 + } + }); + + createActor(machine).start(); + + expect(captured).toEqual([0, 1, 2]); + }); + + it('should deeply preserve action order', () => { + const captured: number[] = []; + + interface CountCtx { + count: number; + } + + const machine = setup({ + types: { + context: {} as CountCtx + }, + actions: { + capture: ({ context }) => captured.push(context.count) + } + }).createMachine({ + context: { count: 0 }, + entry: [ + (_, _params, x) => { + x.action(({ context }) => captured.push(context.count)); // 0 + x.assign({ count: ({ context }) => context.count + 1 }); + x.action({ type: 'capture' }); + x.assign({ count: ({ context }) => context.count + 1 }); + x.action(({ context }) => captured.push(context.count)); // 2 + } + ] + }); + + createActor(machine).start(); + + expect(captured).toEqual([0, 1, 2]); + }); + + it('should capture correct context values on subsequent transitions', () => { + let captured: number[] = []; + + const machine = createMachine({ + types: {} as { + context: { counter: number }; + }, + context: { + counter: 0 + }, + on: { + EV: { + // actions: [ + // assign({ counter: ({ context }) => context.counter + 1 }), + // ({ context }) => captured.push(context.counter) + // ] + actions: (_, _params, x) => { + x.assign({ counter: ({ context }) => context.counter + 1 }); + x.action(({ context }) => captured.push(context.counter)); + } + } + } + }); + + const service = createActor(machine).start(); + + service.send({ type: 'EV' }); + service.send({ type: 'EV' }); + + expect(captured).toEqual([1, 2]); + }); +}); + describe('types', () => { it('assign actions should be inferred correctly', () => { createMachine({ @@ -4446,6 +4534,60 @@ describe('types', () => { }); }); +describe('types with inline actions', () => { + it('assign actions should be inferred correctly', () => { + createMachine({ + types: {} as { + context: { count: number; text: string }; + events: { type: 'inc'; value: number } | { type: 'say'; value: string }; + }, + context: { + count: 0, + text: 'hello' + }, + entry: (_, _params, x) => { + x.assign({ count: 31 }), + // @ts-expect-error + x.assign({ count: 'string' }), + x.assign({ count: () => 31 }), + // @ts-expect-error + x.assign({ count: () => 'string' }), + x.assign({ count: ({ context }) => context.count + 31 }), + // @ts-expect-error + x.assign({ count: ({ context }) => context.text + 31 }), + x.assign(() => ({ count: 31 })), + // @ts-expect-error + x.assign(() => ({ count: 'string' })), + x.assign(({ context }) => ({ count: context.count + 31 })), + // @ts-expect-error + x.assign(({ context }) => ({ count: context.text + 31 })); + }, + on: { + say: { + // actions: [ + // assign({ text: ({ event }) => event.value }), + // // @ts-expect-error + // assign({ count: ({ event }) => event.value }), + + // assign(({ event }) => ({ text: event.value })), + // // @ts-expect-error + // assign(({ event }) => ({ count: event.value })) + // ] + actions: (_, _params, x) => { + x.assign({ text: ({ event }) => event.value }); + // @ts-expect-error + x.assign({ count: ({ event }) => event.value }); + + x.assign(({ event }) => ({ text: event.value })); + // @ts-expect-error + x.assign(({ event }) => ({ count: event.value })); + } + } + } + }); + }); +}); + describe('action meta', () => { it.todo( 'base action objects should have meta.action as the same base action object' @@ -4784,3 +4926,332 @@ describe('actions', () => { }); }); }); + +describe('actions with inline actions', () => { + it('should call transition actions in document order for same-level parallel regions', () => { + const actual: string[] = []; + + const machine = createMachine({ + type: 'parallel', + states: { + a: { + on: { + FOO: { + actions: (_, _params, x) => { + x.action(() => actual.push('a')); + } + } + } + }, + b: { + on: { + FOO: { + actions: (_, _params, x) => { + x.action(() => actual.push('b')); + } + } + } + } + } + }); + const service = createActor(machine).start(); + service.send({ type: 'FOO' }); + + expect(actual).toEqual(['a', 'b']); + }); + + it('should call transition actions in document order for states at different levels of parallel regions', () => { + const actual: string[] = []; + + const machine = createMachine({ + type: 'parallel', + states: { + a: { + initial: 'a1', + states: { + a1: { + on: { + FOO: { + actions: (_, _params, x) => { + x.action(() => actual.push('a1')); + } + } + } + } + } + }, + b: { + on: { + FOO: { + actions: (_, _params, x) => { + x.action(() => actual.push('b')); + } + } + } + } + } + }); + const service = createActor(machine).start(); + service.send({ type: 'FOO' }); + + expect(actual).toEqual(['a1', 'b']); + }); + + it('should call an inline action responding to an initial raise with the raised event', () => { + const spy = jest.fn(); + + const machine = createMachine({ + entry: (_, _params, x) => { + x.raise({ type: 'HELLO' }); + }, + on: { + HELLO: { + actions: ({ event }, _params, x) => { + x.action(() => spy(event)); + } + } + } + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledWith({ type: 'HELLO' }); + }); + + it('should call a referenced action responding to an initial raise with the raised event', () => { + const spy = jest.fn(); + + const machine = setup({ + actions: { + foo: ({ event }) => { + spy(event); + } + } + }).createMachine({ + entry: (_, _params, x) => { + x.raise({ type: 'HELLO' }); + }, + on: { + HELLO: { + actions: 'foo' + } + } + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledWith({ type: 'HELLO' }); + }); + + it('should call an inline action responding to an initial raise with updated (non-initial) context', () => { + const spy = jest.fn(); + + const machine = createMachine({ + context: { count: 0 }, + // entry: [assign({ count: 42 }), raise({ type: 'HELLO' })], + entry: (_, _params, x) => { + x.assign({ count: 42 }); + x.raise({ type: 'HELLO' }); + }, + on: { + HELLO: { + actions: ({ context }, _params, x) => { + x.action(() => spy(context)); + } + } + } + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledWith({ count: 42 }); + }); + + it('should call a referenced action responding to an initial raise with updated (non-initial) context', () => { + const spy = jest.fn(); + + const machine = setup({ + actions: { + foo: ({ context }) => { + spy(context); + } + } + }).createMachine({ + context: { count: 0 }, + // entry: [assign({ count: 42 }), raise({ type: 'HELLO' })], + entry: (_, _params, x) => { + x.assign({ count: 42 }); + x.raise({ type: 'HELLO' }); + }, + on: { + HELLO: { + actions: 'foo' + } + } + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledWith({ count: 42 }); + }); + + it('should call inline entry custom action with undefined parametrized action object', () => { + const spy = jest.fn(); + createActor( + createMachine({ + entry: (_, params) => { + spy(params); + } + }) + ).start(); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call inline entry builtin action with undefined parametrized action object', () => { + const spy = jest.fn(); + createActor( + createMachine({ + entry: (_, params, x) => { + x.assign(() => { + spy(params); + return {}; + }); + } + }) + ).start(); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call inline transition custom action with undefined parametrized action object', () => { + const spy = jest.fn(); + + const actorRef = createActor( + createMachine({ + on: { + FOO: { + actions: (_, params) => { + spy(params); + } + } + } + }) + ).start(); + actorRef.send({ type: 'FOO' }); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call inline transition builtin action with undefined parameters', () => { + const spy = jest.fn(); + + const actorRef = createActor( + createMachine({ + on: { + FOO: { + actions: (_, params, x) => { + x.assign(() => { + spy(params); + return {}; + }); + } + } + } + }) + ).start(); + actorRef.send({ type: 'FOO' }); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call a referenced custom action with undefined params when it has no params and it is referenced using a string', () => { + const spy = jest.fn(); + + createActor( + setup({ + actions: { + myAction: (_, params) => { + spy(params); + } + } + }).createMachine({ + entry: 'myAction' + }) + ).start(); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call a referenced builtin action with undefined params when it has no params and it is referenced using a string', () => { + const spy = jest.fn(); + + createActor( + setup({ + actions: { + myAction: assign((_, params) => { + spy(params); + return {}; + }) + } + }).createMachine({ + entry: 'myAction' + }) + ).start(); + + expect(spy).toHaveBeenCalledWith(undefined); + }); + + it('should call a referenced custom action with the provided parametrized action object', () => { + const spy = jest.fn(); + + createActor( + setup({ + actions: { + myAction: (_, params) => { + spy(params); + } + } + }).createMachine({ + entry: { + type: 'myAction', + params: { + foo: 'bar' + } + } + }) + ).start(); + + expect(spy).toHaveBeenCalledWith({ + foo: 'bar' + }); + }); + + it('should call a referenced builtin action with the provided parametrized action object', () => { + const spy = jest.fn(); + + createActor( + setup({ + actions: { + myAction: (_, params, x) => { + x.assign(() => { + spy(params); + return {}; + }); + } + } + }).createMachine({ + entry: { + type: 'myAction', + params: { + foo: 'bar' + } + } + }) + ).start(); + + expect(spy).toHaveBeenCalledWith({ + foo: 'bar' + }); + }); +}); From fe02e0fbfbf8237a040e25216eb639fe57f97060 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Fri, 17 May 2024 09:27:03 -0400 Subject: [PATCH 3/6] Add tests --- packages/core/test/types.test.ts | 241 +++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 225c7dd274..5a8040c7b5 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -3466,6 +3466,247 @@ describe('enqueueActions', () => { }); }); +describe('inline actions (prev. enqueueActions)', () => { + it('should be able to enqueue a defined parameterized action with required params', () => { + setup({ + actions: { + greet: (_, params: { name: string }) => { + console.log(`Hello ${params.name}!`); + }, + poke: () => {} + } + }).createMachine({ + entry: (_, _params, x) => { + x.action({ + type: 'greet', + params: { + name: 'Anders' + } + }); + } + }); + }); + + it('should not allow to enqueue a defined parameterized action without all of its required params', () => { + setup({ + actions: { + greet: (_, params: { name: string }) => { + console.log(`Hello ${params.name}!`); + }, + poke: () => {} + } + }).createMachine({ + entry: (_, _params, x) => { + x.action({ + type: 'greet', + // @ts-expect-error + params: {} + }); + } + }); + }); + + it('should not be possible to enqueue a parameterized action outside of the defined ones', () => { + setup({ + actions: { + greet: (_, params: { name: string }) => { + console.log(`Hello ${params.name}!`); + }, + poke: () => {} + } + }).createMachine({ + entry: (_, _params, x) => { + x.action( + // @ts-expect-error + { + type: 'other' + } + ); + } + }); + }); + + it('should be possible to enqueue a parameterized action with no required params using a string', () => { + setup({ + actions: { + greet: (_, params: { name: string }) => { + console.log(`Hello ${params.name}!`); + }, + poke: () => {} + } + }).createMachine({ + entry: (_, _params, x) => { + x.action('poke'); + } + }); + }); + + it('should be possible to enqueue a parameterized action with no required params using an object', () => { + setup({ + actions: { + greet: (_, params: { name: string }) => { + console.log(`Hello ${params.name}!`); + }, + poke: () => {} + } + }).createMachine({ + entry: (_, _params, x) => { + x.action({ type: 'poke' }); + } + }); + }); + + it('should be able to enqueue an inline custom action', () => { + setup({ + actions: (_, _params: any, x) => { + x.action(() => {}); + } + }); + }); + + it('should allow a defined simple guard to be checked', () => { + createMachine( + { + types: { + guards: {} as + | { + type: 'isGreaterThan'; + params: { + count: number; + }; + } + | { type: 'plainGuard' } + } + }, + { + actions: { + foo: enqueueActions(({ check }) => { + check('plainGuard'); + }) + } + } + ); + + setup({ + guards: { + plainGuard: () => true, + isGreaterThan: (_, { count }: { count: number }) => count > 10 + }, + actions: { + foo: (_, _params, x) => { + x.check('plainGuard'); + } + } + }); + }); + + it('should allow a defined parameterized guard to be checked', () => { + setup({ + guards: { + isGreaterThan: (_, { count }: { count: number }) => count > 10, + plainGuard: () => true + }, + actions: { + foo: (_, _params, x) => { + x.check({ + type: 'isGreaterThan', + params: { + count: 10 + } + }); + } + } + }); + }); + + it('should allow a defined parameterized guard to be checked', () => { + setup({ + guards: { + isGreaterThan: (_, { count }: { count: number }) => count > 10, + plainGuard: () => true + }, + actions: { + foo: (_, _params, x) => { + x.check( + // @ts-expect-error + { + type: 'other' + } + ); + } + } + }); + }); + + it('should type guard params as undefined in inline custom guard when enqueueActions is used in the config', () => { + setup({ + guards: { + isGreaterThan: (_, { count }: { count: number }) => count > 10, + plainGuard: () => true + }, + actions: { + foo: (_, _params, x) => { + x.check((_, params) => { + params satisfies undefined; + undefined satisfies typeof params; + // @ts-expect-error + params satisfies 'not any'; + + return true; + }); + } + } + }); + }); + + it('should be able to enqueue `raise` using its bound action creator in a transition with one of the other accepted event types', () => { + setup({ + types: { + events: {} as + | { + type: 'SOMETHING'; + } + | { + type: 'SOMETHING_ELSE'; + } + } + }).createMachine({ + on: { + SOMETHING: { + actions: (_, _params, x) => { + x.raise({ type: 'SOMETHING_ELSE' }); + } + } + } + }); + }); + + it('should not be able to enqueue `raise` using its bound action creator in a transition with an event type that is not defined', () => { + setup({ + types: { + events: {} as + | { + type: 'SOMETHING'; + } + | { + type: 'SOMETHING_ELSE'; + } + } + }).createMachine({ + on: { + SOMETHING: { + actions: (_, _params, x) => { + x.raise({ + // @ts-expect-error + type: 'OTHER' + }); + } + } + } + }); + }); +}); + describe('input', () => { it('should provide the input type to the context factory', () => { createMachine({ From d70e050c4c4b4175e1dc7e35bceeab7d40acf6f1 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Tue, 21 May 2024 15:17:19 +0200 Subject: [PATCH 4/6] Rename for clarity --- packages/core/src/setup.ts | 20 ++++++++++---------- packages/core/test/types.test.ts | 8 +++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/core/src/setup.ts b/packages/core/src/setup.ts index 0ab6144013..34010a1e87 100644 --- a/packages/core/src/setup.ts +++ b/packages/core/src/setup.ts @@ -113,7 +113,7 @@ export function setup< TEvent extends AnyEventObject, // TODO: consider using a stricter `EventObject` here TActors extends Record = {}, TChildrenMap extends Record = {}, - TActions extends Record< + TActionParams extends Record< string, ParameterizedObject['params'] | undefined > = {}, @@ -153,13 +153,13 @@ export function setup< : never; }; actions?: { - [K in keyof TActions]: ActionFunction< + [K in keyof TActionParams]: ActionFunction< TContext, + TEvent, // Expression event TEvent, - TEvent, - TActions[K], + TActionParams[K], ToProvidedActor, - ToParameterizedObject, + ToParameterizedObject, ToParameterizedObject, TDelay, TEmitted @@ -177,7 +177,7 @@ export function setup< [K in TDelay]: DelayConfig< TContext, TEvent, - ToParameterizedObject['params'], + ToParameterizedObject['params'], TEvent >; }; @@ -189,7 +189,7 @@ export function setup< TContext, TEvent, ToProvidedActor, - ToParameterizedObject, + ToParameterizedObject, ToParameterizedObject, TDelay, TTag, @@ -201,7 +201,7 @@ export function setup< TypegenDisabled, TEvent, ToProvidedActor, - ToParameterizedObject, + ToParameterizedObject, ToParameterizedObject, TDelay, TTag, @@ -218,7 +218,7 @@ export function setup< Record >, ToProvidedActor, - ToParameterizedObject, + ToParameterizedObject, ToParameterizedObject, TDelay, ToStateValue, @@ -231,7 +231,7 @@ export function setup< TypegenDisabled, TEvent, ToProvidedActor, - ToParameterizedObject, + ToParameterizedObject, ToParameterizedObject, TDelay, TTag, diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 5a8040c7b5..0549ae737f 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -3556,10 +3556,12 @@ describe('inline actions (prev. enqueueActions)', () => { }); }); - it('should be able to enqueue an inline custom action', () => { + it('should be able to execute an inline custom action', () => { setup({ - actions: (_, _params: any, x) => { - x.action(() => {}); + actions: { + foo: (_, _params: any, x) => { + x.action(() => {}); + } } }); }); From 310b6be54a68fdec76e10fa88bfeaf929ed9f1c6 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Sat, 3 Aug 2024 11:57:13 -0400 Subject: [PATCH 5/6] x -> enq --- packages/core/test/actions.test.ts | 272 ++++++++++++++--------------- packages/core/test/assign.test.ts | 56 +++--- packages/core/test/types.test.ts | 48 ++--- 3 files changed, 188 insertions(+), 188 deletions(-) diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index a6107539f4..57d5257f64 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -2755,8 +2755,8 @@ describe('enqueueActions', () => { context: { count: 0 }, - entry: (_, _params, x) => { - x.assign({ + entry: (_, _params, enq) => { + enq.assign({ count: 42 }); } @@ -2956,8 +2956,8 @@ describe('with inline actions', () => { // entry: enqueueActions(({ enqueue }) => { // enqueue('someAction'); // }) - entry: (_, __, x) => { - x.action('someAction'); + entry: (_, __, enq) => { + enq.action('someAction'); } }, { @@ -2982,9 +2982,9 @@ describe('with inline actions', () => { // enqueue('someAction'); // enqueue('otherAction'); // }) - entry: (_, _params, x) => { - x.action('someAction'); - x.action('otherAction'); + entry: (_, _params, enq) => { + enq.action('someAction'); + enq.action('otherAction'); } }, { @@ -3006,9 +3006,9 @@ describe('with inline actions', () => { const machine = createMachine( { - entry: (_, _params, x) => { - x.action('someAction'); - x.action('someAction'); + entry: (_, _params, enq) => { + enq.action('someAction'); + enq.action('someAction'); } }, { @@ -3034,8 +3034,8 @@ describe('with inline actions', () => { // params: { answer: 42 } // }); // }), - entry: (_, _params, x) => { - x.action({ + entry: (_, _params, enq) => { + enq.action({ type: 'someAction', params: { answer: 42 } }); @@ -3065,8 +3065,8 @@ describe('with inline actions', () => { const spy = jest.fn(); const machine = createMachine({ - entry: (_, _params, x) => { - x.action(spy); + entry: (_, _params, enq) => { + enq.action(spy); } }); @@ -3088,8 +3088,8 @@ describe('with inline actions', () => { // }) // ); // }) - actions: (_, _params, x) => { - x.raise({ + actions: (_, _params, enq) => { + enq.raise({ type: 'RAISED' }); } @@ -3118,8 +3118,8 @@ describe('with inline actions', () => { // type: 'RAISED' // }); // }) - actions: (_, _params, x) => { - x.raise({ + actions: (_, _params, enq) => { + enq.raise({ type: 'RAISED' }); } @@ -3142,8 +3142,8 @@ describe('with inline actions', () => { context: { count: 0 }, - entry: (_, __, x) => { - x.assign({ + entry: (_, __, enq) => { + enq.assign({ count: 42 }); } @@ -3164,8 +3164,8 @@ describe('with inline actions', () => { // entry: enqueueActions(({ check }) => { // check('alwaysTrue'); // }) - entry: (_, __, x) => { - x.check('alwaysTrue'); + entry: (_, __, enq) => { + enq.check('alwaysTrue'); } }, { @@ -3196,8 +3196,8 @@ describe('with inline actions', () => { // } // }); // }) - entry: (_, __, x) => { - x.check({ + entry: (_, __, enq) => { + enq.check({ type: 'alwaysTrue', params: { max: 100 @@ -3267,7 +3267,7 @@ describe('with inline actions', () => { // enqueue.sendTo(context.parent, event); // } // ) - mySendParent: ({ context }, params: ParentEvent, x) => { + mySendParent: ({ context }, params: ParentEvent, enq) => { if (!context.parent) { // it's here just for illustration purposes console.log( @@ -3275,7 +3275,7 @@ describe('with inline actions', () => { ); return; } - x.sendTo(context.parent, params); + enq.sendTo(context.parent, params); } } }).createMachine({ @@ -3588,8 +3588,8 @@ describe('sendTo with inline actions', () => { child: spawn(childMachine) }), // entry: sendTo(({ context }) => context.child, { type: 'EVENT' }) - entry: (_, _params, x) => { - x.sendTo(({ context }) => context.child, { type: 'EVENT' }); + entry: (_, _params, enq) => { + enq.sendTo(({ context }) => context.child, { type: 'EVENT' }); } }); @@ -3630,8 +3630,8 @@ describe('sendTo with inline actions', () => { // ({ context }) => context.child, // ({ context }) => ({ type: 'EVENT', count: context.count }) // ) - entry: (_, _params, x) => { - x.sendTo( + entry: (_, _params, enq) => { + enq.sendTo( ({ context }) => context.child, ({ context }) => ({ type: 'EVENT', count: context.count }) ); @@ -3669,8 +3669,8 @@ describe('sendTo with inline actions', () => { // // @ts-expect-error // type: 'UNKNOWN' // }) - entry: (_, _params, x) => { - x.sendTo(({ context }) => context.child, { + entry: (_, _params, enq) => { + enq.sendTo(({ context }) => context.child, { // @ts-expect-error type: 'UNKNOWN' }); @@ -3701,8 +3701,8 @@ describe('sendTo with inline actions', () => { child: spawn(childMachine, { id: 'child' }) }), // No type-safety for the event yet - entry: (_, _params, x) => { - x.sendTo('child', { type: 'EVENT' }); + entry: (_, _params, enq) => { + enq.sendTo('child', { type: 'EVENT' }); } }); @@ -3731,8 +3731,8 @@ describe('sendTo with inline actions', () => { context: ({ spawn }) => ({ child: spawn(childMachine) }), - entry: (_, _params, x) => { - x.sendTo(({ context }) => context.child, { type: 'EVENT' }); + entry: (_, _params, enq) => { + enq.sendTo(({ context }) => context.child, { type: 'EVENT' }); } }); @@ -3760,8 +3760,8 @@ describe('sendTo with inline actions', () => { a: { on: { EVENT: { - actions: (_, _params, x) => { - x.sendTo(({ context, event }) => context[event.value], { + actions: (_, _params, enq) => { + enq.sendTo(({ context, event }) => context[event.value], { type: 'EVENT' }); } @@ -3782,8 +3782,8 @@ describe('sendTo with inline actions', () => { id: 'child', src: fromCallback(() => {}) }, - entry: (_, _params, x) => { - x.sendTo('child', 'a string'); + entry: (_, _params, enq) => { + enq.sendTo('child', 'a string'); } }); @@ -4023,8 +4023,8 @@ describe('raise with inline actions', () => { // delay: 1 // } // ), - entry: (_, _params, x) => { - x.raise({ type: 'EVENT' }, { delay: 1 }); + entry: (_, _params, enq) => { + enq.raise({ type: 'EVENT' }, { delay: 1 }); }, on: { TO_B: 'b' @@ -4060,8 +4060,8 @@ describe('raise with inline actions', () => { // delay: 0 // } // ), - entry: (_, _params, x) => { - x.raise({ type: 'EVENT' }, { delay: 0 }); + entry: (_, _params, enq) => { + enq.raise({ type: 'EVENT' }, { delay: 0 }); }, on: { EVENT: 'b' @@ -4089,8 +4089,8 @@ describe('raise with inline actions', () => { states: { a: { // entry: raise({ type: 'TO_B' }), - entry: (_, _params, x) => { - x.raise({ type: 'TO_B' }); + entry: (_, _params, enq) => { + enq.raise({ type: 'TO_B' }); }, on: { TO_B: 'b' @@ -4118,8 +4118,8 @@ describe('raise with inline actions', () => { // delay: 100 // } // ), - entry: (_, _params, x) => { - x.raise( + entry: (_, _params, enq) => { + enq.raise( { type: 'TO_B' }, { delay: 100 @@ -4154,8 +4154,8 @@ describe('raise with inline actions', () => { on: { NEXT: { // actions: raise(() => ({ type: 'RAISED' })) - actions: (_, _params, x) => { - x.raise(() => ({ type: 'RAISED' })); + actions: (_, _params, enq) => { + enq.raise(() => ({ type: 'RAISED' })); } }, RAISED: 'b' @@ -4196,8 +4196,8 @@ describe('raise with inline actions', () => { // actions: raise(({ context }) => ({ // type: context.eventType // })) - actions: (_, _params, x) => { - x.raise(({ context }) => ({ + actions: (_, _params, enq) => { + enq.raise(({ context }) => ({ type: context.eventType })); } @@ -4222,8 +4222,8 @@ describe('raise with inline actions', () => { // // @ts-ignore // 'a string' // ) - entry: (_, _params, x) => { - x.raise( + entry: (_, _params, enq) => { + enq.raise( // @ts-expect-error 'a string' ); @@ -4479,10 +4479,10 @@ describe('assign action order', () => { context: { count: 0 }, entry: [ ({ context }) => captured.push(context.count), // 0 - (_, _params, x) => { - x.assign({ count: ({ context }) => context.count + 1 }); - x.action({ type: 'capture' }); - x.assign({ count: ({ context }) => context.count + 1 }); + (_, _params, enq) => { + enq.assign({ count: ({ context }) => context.count + 1 }); + enq.action({ type: 'capture' }); + enq.assign({ count: ({ context }) => context.count + 1 }); }, ({ context }) => captured.push(context.count) // 2 ] @@ -4539,11 +4539,11 @@ describe('assign action order', () => { }, on: { EV: { - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ counter: ({ context }) => context.counter + 1 }); - x.action(({ context }) => captured.push(context.counter)); + enq.action(({ context }) => captured.push(context.counter)); } } } @@ -4567,12 +4567,12 @@ describe('assign action order with inline actions', () => { context: { count: number }; }, context: { count: 0 }, - entry: (_, _params, x) => { - x.action(({ context }) => captured.push(context.count)); // 0 - x.assign({ count: ({ context }) => context.count + 1 }); - x.action(({ context }) => captured.push(context.count)); // 1 - x.assign({ count: ({ context }) => context.count + 1 }); - x.action(({ context }) => captured.push(context.count)); // 2 + entry: (_, _params, enq) => { + enq.action(({ context }) => captured.push(context.count)); // 0 + enq.assign({ count: ({ context }) => context.count + 1 }); + enq.action(({ context }) => captured.push(context.count)); // 1 + enq.assign({ count: ({ context }) => context.count + 1 }); + enq.action(({ context }) => captured.push(context.count)); // 2 } }); @@ -4598,12 +4598,12 @@ describe('assign action order with inline actions', () => { }).createMachine({ context: { count: 0 }, entry: [ - (_, _params, x) => { - x.action(({ context }) => captured.push(context.count)); // 0 - x.assign({ count: ({ context }) => context.count + 1 }); - x.action({ type: 'capture' }); - x.assign({ count: ({ context }) => context.count + 1 }); - x.action(({ context }) => captured.push(context.count)); // 2 + (_, _params, enq) => { + enq.action(({ context }) => captured.push(context.count)); // 0 + enq.assign({ count: ({ context }) => context.count + 1 }); + enq.action({ type: 'capture' }); + enq.assign({ count: ({ context }) => context.count + 1 }); + enq.action(({ context }) => captured.push(context.count)); // 2 } ] }); @@ -4629,9 +4629,9 @@ describe('assign action order with inline actions', () => { // assign({ counter: ({ context }) => context.counter + 1 }), // ({ context }) => captured.push(context.counter) // ] - actions: (_, _params, x) => { - x.assign({ counter: ({ context }) => context.counter + 1 }); - x.action(({ context }) => captured.push(context.counter)); + actions: (_, _params, enq) => { + enq.assign({ counter: ({ context }) => context.counter + 1 }); + enq.action(({ context }) => captured.push(context.counter)); } } } @@ -4704,37 +4704,37 @@ describe('types', () => { count: 0, text: 'hello' }, - entry: (_, _params, x) => { - x.assign({ count: 31 }); + entry: (_, _params, enq) => { + enq.assign({ count: 31 }); // @ts-expect-error - x.assign({ count: 'string' }); + enq.assign({ count: 'string' }); - x.assign({ count: () => 31 }); + enq.assign({ count: () => 31 }); // @ts-expect-error - x.assign({ count: () => 'string' }); + enq.assign({ count: () => 'string' }); - x.assign({ count: ({ context }) => context.count + 31 }); + enq.assign({ count: ({ context }) => context.count + 31 }); // @ts-expect-error - x.assign({ count: ({ context }) => context.text + 31 }); + enq.assign({ count: ({ context }) => context.text + 31 }); - x.assign(() => ({ count: 31 })); + enq.assign(() => ({ count: 31 })); // @ts-expect-error - x.assign(() => ({ count: 'string' })); + enq.assign(() => ({ count: 'string' })); - x.assign(({ context }) => ({ count: context.count + 31 })); + enq.assign(({ context }) => ({ count: context.count + 31 })); // @ts-expect-error - x.assign(({ context }) => ({ count: context.text + 31 })); + enq.assign(({ context }) => ({ count: context.text + 31 })); }, on: { say: { - actions: (_, _params, x) => { - x.assign({ text: ({ event }) => event.value }); + actions: (_, _params, enq) => { + enq.assign({ text: ({ event }) => event.value }); // @ts-expect-error - x.assign({ count: ({ event }) => event.value }); + enq.assign({ count: ({ event }) => event.value }); - x.assign(({ event }) => ({ text: event.value })); + enq.assign(({ event }) => ({ text: event.value })); // @ts-expect-error - x.assign(({ event }) => ({ count: event.value })); + enq.assign(({ event }) => ({ count: event.value })); } } } @@ -4753,22 +4753,22 @@ describe('types with inline actions', () => { count: 0, text: 'hello' }, - entry: (_, _params, x) => { - x.assign({ count: 31 }), + entry: (_, _params, enq) => { + enq.assign({ count: 31 }), // @ts-expect-error - x.assign({ count: 'string' }), - x.assign({ count: () => 31 }), + enq.assign({ count: 'string' }), + enq.assign({ count: () => 31 }), // @ts-expect-error - x.assign({ count: () => 'string' }), - x.assign({ count: ({ context }) => context.count + 31 }), + enq.assign({ count: () => 'string' }), + enq.assign({ count: ({ context }) => context.count + 31 }), // @ts-expect-error - x.assign({ count: ({ context }) => context.text + 31 }), - x.assign(() => ({ count: 31 })), + enq.assign({ count: ({ context }) => context.text + 31 }), + enq.assign(() => ({ count: 31 })), // @ts-expect-error - x.assign(() => ({ count: 'string' })), - x.assign(({ context }) => ({ count: context.count + 31 })), + enq.assign(() => ({ count: 'string' })), + enq.assign(({ context }) => ({ count: context.count + 31 })), // @ts-expect-error - x.assign(({ context }) => ({ count: context.text + 31 })); + enq.assign(({ context }) => ({ count: context.text + 31 })); }, on: { say: { @@ -4781,14 +4781,14 @@ describe('types with inline actions', () => { // // @ts-expect-error // assign(({ event }) => ({ count: event.value })) // ] - actions: (_, _params, x) => { - x.assign({ text: ({ event }) => event.value }); + actions: (_, _params, enq) => { + enq.assign({ text: ({ event }) => event.value }); // @ts-expect-error - x.assign({ count: ({ event }) => event.value }); + enq.assign({ count: ({ event }) => event.value }); - x.assign(({ event }) => ({ text: event.value })); + enq.assign(({ event }) => ({ text: event.value })); // @ts-expect-error - x.assign(({ event }) => ({ count: event.value })); + enq.assign(({ event }) => ({ count: event.value })); } } } @@ -5175,8 +5175,8 @@ describe('actions with inline actions', () => { a: { on: { FOO: { - actions: (_, _params, x) => { - x.action(() => actual.push('a')); + actions: (_, _params, enq) => { + enq.action(() => actual.push('a')); } } } @@ -5184,8 +5184,8 @@ describe('actions with inline actions', () => { b: { on: { FOO: { - actions: (_, _params, x) => { - x.action(() => actual.push('b')); + actions: (_, _params, enq) => { + enq.action(() => actual.push('b')); } } } @@ -5210,8 +5210,8 @@ describe('actions with inline actions', () => { a1: { on: { FOO: { - actions: (_, _params, x) => { - x.action(() => actual.push('a1')); + actions: (_, _params, enq) => { + enq.action(() => actual.push('a1')); } } } @@ -5221,8 +5221,8 @@ describe('actions with inline actions', () => { b: { on: { FOO: { - actions: (_, _params, x) => { - x.action(() => actual.push('b')); + actions: (_, _params, enq) => { + enq.action(() => actual.push('b')); } } } @@ -5239,13 +5239,13 @@ describe('actions with inline actions', () => { const spy = jest.fn(); const machine = createMachine({ - entry: (_, _params, x) => { - x.raise({ type: 'HELLO' }); + entry: (_, _params, enq) => { + enq.raise({ type: 'HELLO' }); }, on: { HELLO: { - actions: ({ event }, _params, x) => { - x.action(() => spy(event)); + actions: ({ event }, _params, enq) => { + enq.action(() => spy(event)); } } } @@ -5266,8 +5266,8 @@ describe('actions with inline actions', () => { } } }).createMachine({ - entry: (_, _params, x) => { - x.raise({ type: 'HELLO' }); + entry: (_, _params, enq) => { + enq.raise({ type: 'HELLO' }); }, on: { HELLO: { @@ -5287,14 +5287,14 @@ describe('actions with inline actions', () => { const machine = createMachine({ context: { count: 0 }, // entry: [assign({ count: 42 }), raise({ type: 'HELLO' })], - entry: (_, _params, x) => { - x.assign({ count: 42 }); - x.raise({ type: 'HELLO' }); + entry: (_, _params, enq) => { + enq.assign({ count: 42 }); + enq.raise({ type: 'HELLO' }); }, on: { HELLO: { - actions: ({ context }, _params, x) => { - x.action(() => spy(context)); + actions: ({ context }, _params, enq) => { + enq.action(() => spy(context)); } } } @@ -5317,9 +5317,9 @@ describe('actions with inline actions', () => { }).createMachine({ context: { count: 0 }, // entry: [assign({ count: 42 }), raise({ type: 'HELLO' })], - entry: (_, _params, x) => { - x.assign({ count: 42 }); - x.raise({ type: 'HELLO' }); + entry: (_, _params, enq) => { + enq.assign({ count: 42 }); + enq.raise({ type: 'HELLO' }); }, on: { HELLO: { @@ -5350,8 +5350,8 @@ describe('actions with inline actions', () => { const spy = jest.fn(); createActor( createMachine({ - entry: (_, params, x) => { - x.assign(() => { + entry: (_, params, enq) => { + enq.assign(() => { spy(params); return {}; }); @@ -5388,8 +5388,8 @@ describe('actions with inline actions', () => { createMachine({ on: { FOO: { - actions: (_, params, x) => { - x.assign(() => { + actions: (_, params, enq) => { + enq.assign(() => { spy(params); return {}; }); @@ -5471,8 +5471,8 @@ describe('actions with inline actions', () => { createActor( setup({ actions: { - myAction: (_, params, x) => { - x.assign(() => { + myAction: (_, params, enq) => { + enq.assign(() => { spy(params); return {}; }); diff --git a/packages/core/test/assign.test.ts b/packages/core/test/assign.test.ts index 72d12f9291..63f90c6c24 100644 --- a/packages/core/test/assign.test.ts +++ b/packages/core/test/assign.test.ts @@ -297,8 +297,8 @@ describe('assign with inline actions', () => { INC: [ { target: 'counting', - actions: (_, _params, x) => { - x.assign(({ context }) => ({ + actions: (_, _params, enq) => { + enq.assign(({ context }) => ({ count: context.count + 1 })); } @@ -312,8 +312,8 @@ describe('assign with inline actions', () => { // count: ({ context }) => context.count - 1 // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: ({ context }) => context.count - 1 }); } @@ -328,8 +328,8 @@ describe('assign with inline actions', () => { // foo: () => 'win' // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: () => 100, foo: () => 'win' }); @@ -345,8 +345,8 @@ describe('assign with inline actions', () => { // foo: 'win' // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: 100, foo: 'win' }); @@ -362,8 +362,8 @@ describe('assign with inline actions', () => { // foo: 'win' // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: () => 100, foo: 'win' }); @@ -379,8 +379,8 @@ describe('assign with inline actions', () => { // foo: 'win' // })) // ] - actions: (_, _params, x) => { - x.assign(() => ({ + actions: (_, _params, enq) => { + enq.assign(() => ({ count: 100, foo: 'win' })); @@ -394,8 +394,8 @@ describe('assign with inline actions', () => { // maybe: 'defined' // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ maybe: 'defined' }); } @@ -493,8 +493,8 @@ describe('assign with inline actions', () => { // count: ({ context }) => context.count - 1 // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: ({ context }) => context.count - 1 }); } @@ -538,8 +538,8 @@ describe('assign with inline actions', () => { INC: [ { target: 'counting', - actions: (_, _params, x) => { - x.assign(({ context }) => ({ + actions: (_, _params, enq) => { + enq.assign(({ context }) => ({ count: context.count + 1 })); } @@ -553,8 +553,8 @@ describe('assign with inline actions', () => { // count: ({ context }) => context.count - 1 // }) // ] - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: ({ context }) => context.count - 1 }); } @@ -633,8 +633,8 @@ describe('assign with inline actions', () => { // actions: assign({ // count: ({ event }) => event.value // }) - actions: (_, _params, x) => { - x.assign({ + actions: (_, _params, enq) => { + enq.assign({ count: ({ event }) => event.value }); } @@ -740,8 +740,8 @@ describe('assign meta with inline actions', () => { context: {} as { count: number } }, actions: { - inc: (_, params: { by: number }, x) => { - x.assign(({ context }) => ({ + inc: (_, params: { by: number }, enq) => { + enq.assign(({ context }) => ({ count: context.count + params.by })); } @@ -765,8 +765,8 @@ describe('assign meta with inline actions', () => { context: {} as { count: number } }, actions: { - inc: (_, params: { by: number }, x) => { - x.assign({ + inc: (_, params: { by: number }, enq) => { + enq.assign({ count: ({ context }) => context.count + params.by }); } @@ -787,8 +787,8 @@ describe('assign meta with inline actions', () => { it('a parameterized action that resolves to assign() should be provided the params', (done) => { const machine = setup({ actions: { - inc: (_, params: { value: number }, x) => { - x.assign(({ context }) => { + inc: (_, params: { value: number }, enq) => { + enq.assign(({ context }) => { expect(params).toEqual({ value: 5 }); done(); return context; diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 9925a049f3..275d63d30b 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -3485,8 +3485,8 @@ describe('inline actions (prev. enqueueActions)', () => { poke: () => {} } }).createMachine({ - entry: (_, _params, x) => { - x.action({ + entry: (_, _params, enq) => { + enq.action({ type: 'greet', params: { name: 'Anders' @@ -3505,8 +3505,8 @@ describe('inline actions (prev. enqueueActions)', () => { poke: () => {} } }).createMachine({ - entry: (_, _params, x) => { - x.action({ + entry: (_, _params, enq) => { + enq.action({ type: 'greet', // @ts-expect-error params: {} @@ -3524,8 +3524,8 @@ describe('inline actions (prev. enqueueActions)', () => { poke: () => {} } }).createMachine({ - entry: (_, _params, x) => { - x.action( + entry: (_, _params, enq) => { + enq.action( // @ts-expect-error { type: 'other' @@ -3544,8 +3544,8 @@ describe('inline actions (prev. enqueueActions)', () => { poke: () => {} } }).createMachine({ - entry: (_, _params, x) => { - x.action('poke'); + entry: (_, _params, enq) => { + enq.action('poke'); } }); }); @@ -3559,8 +3559,8 @@ describe('inline actions (prev. enqueueActions)', () => { poke: () => {} } }).createMachine({ - entry: (_, _params, x) => { - x.action({ type: 'poke' }); + entry: (_, _params, enq) => { + enq.action({ type: 'poke' }); } }); }); @@ -3568,8 +3568,8 @@ describe('inline actions (prev. enqueueActions)', () => { it('should be able to execute an inline custom action', () => { setup({ actions: { - foo: (_, _params: any, x) => { - x.action(() => {}); + foo: (_, _params: any, enq) => { + enq.action(() => {}); } } }); @@ -3604,8 +3604,8 @@ describe('inline actions (prev. enqueueActions)', () => { isGreaterThan: (_, { count }: { count: number }) => count > 10 }, actions: { - foo: (_, _params, x) => { - x.check('plainGuard'); + foo: (_, _params, enq) => { + enq.check('plainGuard'); } } }); @@ -3618,8 +3618,8 @@ describe('inline actions (prev. enqueueActions)', () => { plainGuard: () => true }, actions: { - foo: (_, _params, x) => { - x.check({ + foo: (_, _params, enq) => { + enq.check({ type: 'isGreaterThan', params: { count: 10 @@ -3637,8 +3637,8 @@ describe('inline actions (prev. enqueueActions)', () => { plainGuard: () => true }, actions: { - foo: (_, _params, x) => { - x.check( + foo: (_, _params, enq) => { + enq.check( // @ts-expect-error { type: 'other' @@ -3656,8 +3656,8 @@ describe('inline actions (prev. enqueueActions)', () => { plainGuard: () => true }, actions: { - foo: (_, _params, x) => { - x.check((_, params) => { + foo: (_, _params, enq) => { + enq.check((_, params) => { params satisfies undefined; undefined satisfies typeof params; // @ts-expect-error @@ -3684,8 +3684,8 @@ describe('inline actions (prev. enqueueActions)', () => { }).createMachine({ on: { SOMETHING: { - actions: (_, _params, x) => { - x.raise({ type: 'SOMETHING_ELSE' }); + actions: (_, _params, enq) => { + enq.raise({ type: 'SOMETHING_ELSE' }); } } } @@ -3706,8 +3706,8 @@ describe('inline actions (prev. enqueueActions)', () => { }).createMachine({ on: { SOMETHING: { - actions: (_, _params, x) => { - x.raise({ + actions: (_, _params, enq) => { + enq.raise({ // @ts-expect-error type: 'OTHER' }); From d5994886b6c186aabcb2883929bb7b48fdf8eb3a Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Sat, 10 Aug 2024 09:37:29 -0400 Subject: [PATCH 6/6] Fix some types --- packages/core/test/actions.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index c599ef431e..05bc22074b 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -3584,7 +3584,7 @@ describe('sendTo with inline actions', () => { const parentMachine = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, context: ({ spawn }) => ({ @@ -3619,7 +3619,7 @@ describe('sendTo with inline actions', () => { const parentMachine = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; count: number; }; }, @@ -3662,7 +3662,7 @@ describe('sendTo with inline actions', () => { createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, context: ({ spawn }) => ({ @@ -3699,7 +3699,9 @@ describe('sendTo with inline actions', () => { }); const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, + types: {} as { + context: { child: ActorRefFromLogic }; + }, context: ({ spawn }) => ({ child: spawn(childMachine, { id: 'child' }) }), @@ -3730,7 +3732,9 @@ describe('sendTo with inline actions', () => { }); const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, + types: {} as { + context: { child: ActorRefFromLogic }; + }, context: ({ spawn }) => ({ child: spawn(childMachine) }),