From 384f0ffc712a36846d97b58195eeaa71edbc67f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 24 Nov 2023 10:29:31 +0100 Subject: [PATCH] Remove `getNextEvents` method from the `MachineSnapshot` (#4478) * Remove `getNextEvents` method from the `MachineSnapshot` * changeset --------- Co-authored-by: David Khourshid --- .changeset/dirty-roses-develop.md | 5 ++ packages/core/src/State.ts | 23 ------ packages/core/src/StateMachine.ts | 11 ++- packages/core/src/index.ts | 7 +- packages/core/src/utils.ts | 9 ++- packages/core/test/machine.test.ts | 9 --- packages/core/test/state.test.ts | 72 ------------------- packages/xstate-graph/src/graph.ts | 5 +- .../xstate-solid/test/useMachine.test.tsx | 62 ---------------- packages/xstate-test/src/machine.ts | 9 ++- 10 files changed, 34 insertions(+), 178 deletions(-) create mode 100644 .changeset/dirty-roses-develop.md diff --git a/.changeset/dirty-roses-develop.md b/.changeset/dirty-roses-develop.md new file mode 100644 index 0000000000..908024bb47 --- /dev/null +++ b/.changeset/dirty-roses-develop.md @@ -0,0 +1,5 @@ +--- +'xstate': major +--- + +Removed `MachineSnapshot['nextEvents']`. diff --git a/packages/core/src/State.ts b/packages/core/src/State.ts index 476927e692..d9b7f69afb 100644 --- a/packages/core/src/State.ts +++ b/packages/core/src/State.ts @@ -126,20 +126,6 @@ interface MachineSnapshotBase< event: TEvent ) => boolean; - /** - * The next events that will cause a transition from the current state. - */ - getNextEvents: ( - this: MachineSnapshot< - TContext, - TEvent, - TChildren, - TTag, - TOutput, - TResolvedTypesMeta - > - ) => ReadonlyArray>; - getMeta: ( this: MachineSnapshot< TContext, @@ -322,7 +308,6 @@ const machineSnapshotToJSON = function toJSON(this: AnyMachineSnapshot) { _nodes: nodes, tags, machine, - getNextEvents, getMeta, toJSON, can, @@ -333,12 +318,6 @@ const machineSnapshotToJSON = function toJSON(this: AnyMachineSnapshot) { return { ...jsonValues, tags: Array.from(tags) }; }; -const machineSnapshotGetNextEvents = function getNextEvents( - this: AnyMachineSnapshot -) { - return [...new Set(flatten([...this._nodes.map((sn) => sn.ownEvents)]))]; -}; - const machineSnapshotGetMeta = function getMeta(this: AnyMachineSnapshot) { return this._nodes.reduce( (acc, stateNode) => { @@ -383,7 +362,6 @@ export function createMachineSnapshot< matches: machineSnapshotMatches as any, hasTag: machineSnapshotHasTag, can: machineSnapshotCan, - getNextEvents: machineSnapshotGetNextEvents, getMeta: machineSnapshotGetMeta, toJSON: machineSnapshotToJSON }; @@ -426,7 +404,6 @@ export function getPersistedState< can, hasTag, matches, - getNextEvents, getMeta, toJSON, ...jsonValues diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index 48cf694ccd..1d7471c42b 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -52,7 +52,12 @@ import type { AnyActorLogic, HistoryValue } from './types.ts'; -import { isErrorActorEvent, resolveReferencedActor } from './utils.ts'; +import { + flatten, + getAllOwnEventDescriptors, + isErrorActorEvent, + resolveReferencedActor +} from './utils.ts'; import { $$ACTOR_TYPE, createActor } from './interpreter.ts'; import isDevelopment from '#is-development'; @@ -297,7 +302,9 @@ export class StateMachine< // TODO: handle error events in a better way if ( isErrorActorEvent(event) && - !state.getNextEvents().some((nextEvent) => nextEvent === event.type) + !getAllOwnEventDescriptors(state).some( + (nextEvent) => nextEvent === event.type + ) ) { return cloneMachineSnapshot(state, { status: 'error', diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f3174357f8..7a1cbc647b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -12,7 +12,12 @@ import { createMachine } from './createMachine.ts'; export { type MachineSnapshot, isMachineSnapshot } from './State.ts'; import { StateNode } from './StateNode.ts'; // TODO: decide from where those should be exported -export { matchesState, pathToStateValue, toObserver } from './utils.ts'; +export { + matchesState, + pathToStateValue, + toObserver, + getAllOwnEventDescriptors as __unsafe_getAllOwnEventDescriptors +} from './utils.ts'; export { Actor, createActor, diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 9f96780b9f..47423e92ec 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -18,9 +18,10 @@ import type { AnyTransitionConfig, NonReducibleUnknown, AnyStateMachine, - InvokeConfig + InvokeConfig, + AnyMachineSnapshot } from './types.ts'; -import { isMachineSnapshot } from './State.ts'; +import { MachineSnapshot, isMachineSnapshot } from './State.ts'; export function keys(value: T): Array { return Object.keys(value) as Array; @@ -402,3 +403,7 @@ export function resolveReferencedActor(machine: AnyStateMachine, src: string) { } return machine.implementations.actors[src]; } + +export function getAllOwnEventDescriptors(snapshot: AnyMachineSnapshot) { + return [...new Set(flatten([...snapshot._nodes.map((sn) => sn.ownEvents)]))]; +} diff --git a/packages/core/test/machine.test.ts b/packages/core/test/machine.test.ts index e94aba8c1e..b221512b13 100644 --- a/packages/core/test/machine.test.ts +++ b/packages/core/test/machine.test.ts @@ -289,15 +289,6 @@ describe('machine', () => { }); }); - it('should resolve the state nodes (implicit via events)', () => { - const resolvedState = resolveMachine.resolveState({ value: 'foo' }); - - expect([...resolvedState.getNextEvents()].sort()).toEqual([ - 'TO_BAR', - 'TO_TWO' - ]); - }); - it('should resolve `status: done`', () => { const machine = createMachine({ initial: 'foo', diff --git a/packages/core/test/state.test.ts b/packages/core/test/state.test.ts index f3e1034da8..914fbc25ce 100644 --- a/packages/core/test/state.test.ts +++ b/packages/core/test/state.test.ts @@ -106,78 +106,6 @@ const exampleMachine = createMachine({ }); describe('State', () => { - describe('.getNextEvents', () => { - it('returns the next possible events for the current state', () => { - const actorRef = createActor(exampleMachine); - - expect([...actorRef.getSnapshot().getNextEvents()].sort()).toEqual( - [ - 'EXTERNAL', - 'INTERNAL', - 'MACHINE_EVENT', - 'TO_FINAL', - 'TO_THREE', - 'TO_TWO', - 'TO_TWO_MAYBE' - ].sort() - ); - - actorRef.start(); - actorRef.send({ - type: 'TO_TWO', - foo: 'test' - }); - - expect([...actorRef.getSnapshot().getNextEvents()].sort()).toEqual([ - 'DEEP_EVENT', - 'FOO_EVENT', - 'MACHINE_EVENT' - ]); - - const actorRef2 = createActor(exampleMachine).start(); - actorRef2.send({ type: 'TO_THREE' }); - - expect([...actorRef2.getSnapshot().getNextEvents()].sort()).toEqual([ - 'MACHINE_EVENT', - 'P31', - 'P32', - 'THREE_EVENT' - ]); - }); - - it('returns events when transitioned from StateValue', () => { - const actorRef = createActor(exampleMachine).start(); - - actorRef.send({ - type: 'TO_THREE' - }); - actorRef.send({ type: 'TO_THREE' }); - - expect([...actorRef.getSnapshot().getNextEvents()].sort()).toEqual([ - 'MACHINE_EVENT', - 'P31', - 'P32', - 'THREE_EVENT' - ]); - }); - - it('returns no next events if there are none', () => { - const noEventsMachine = createMachine({ - id: 'no-events', - initial: 'idle', - states: { - idle: { - on: {} - } - } - }); - - expect( - createActor(noEventsMachine).getSnapshot().getNextEvents() - ).toEqual([]); - }); - }); - describe('status', () => { it('should show that a machine has not reached its final state', () => { expect(createActor(exampleMachine).getSnapshot().status).not.toBe('done'); diff --git a/packages/xstate-graph/src/graph.ts b/packages/xstate-graph/src/graph.ts index eeb3363839..43efc7f53f 100644 --- a/packages/xstate-graph/src/graph.ts +++ b/packages/xstate-graph/src/graph.ts @@ -8,7 +8,8 @@ import { AnyActorLogic, SnapshotFrom, EventFromLogic, - Snapshot + Snapshot, + __unsafe_getAllOwnEventDescriptors } from 'xstate'; import type { SerializedEvent, @@ -91,7 +92,7 @@ export function createDefaultMachineOptions( const events = typeof getEvents === 'function' ? getEvents(state) : getEvents ?? []; return flatten( - state.getNextEvents().map((type) => { + __unsafe_getAllOwnEventDescriptors(state).map((type) => { const matchingEvents = events.filter( (ev) => (ev as any).type === type ); diff --git a/packages/xstate-solid/test/useMachine.test.tsx b/packages/xstate-solid/test/useMachine.test.tsx index a531d1b007..1fd9e3cf77 100644 --- a/packages/xstate-solid/test/useMachine.test.tsx +++ b/packages/xstate-solid/test/useMachine.test.tsx @@ -654,68 +654,6 @@ describe('useMachine hook', () => { expect(count).toEqual(1); }); - it('getNextEvents should be defined and reactive', () => { - const machine = createMachine({ - initial: 'green', - states: { - green: { - on: { - TRANSITION: 'yellow' - } - }, - yellow: { - on: { - TRANSITION: 'red', - BACK_TRANSITION: 'green' - } - }, - red: { - on: { - TRANSITION: 'green' - } - } - } - }); - - const App = () => { - const [state, send] = useMachine(machine); - - return ( -
-
- ); - }; - - render(() => ); - const transitionBtn = screen.getByTestId('transition-button'); - - // Green - expect(screen.getByTestId('event-0')).toBeTruthy(); - expect(screen.queryByTestId('event-1')).not.toBeTruthy(); - transitionBtn.click(); - - // Yellow - expect(screen.getByTestId('event-0')).toBeTruthy(); - expect(screen.getByTestId('event-1')).toBeTruthy(); - transitionBtn.click(); - - // Red - expect(screen.getByTestId('event-0')).toBeTruthy(); - expect(screen.queryByTestId('event-1')).not.toBeTruthy(); - }); - it('should be reactive to toJSON method calls', () => { const machine = createMachine({ initial: 'green', diff --git a/packages/xstate-test/src/machine.ts b/packages/xstate-test/src/machine.ts index 382ffe4dd1..ad7660d758 100644 --- a/packages/xstate-test/src/machine.ts +++ b/packages/xstate-test/src/machine.ts @@ -12,6 +12,7 @@ import { StateValue, SnapshotFrom, MachineSnapshot, + __unsafe_getAllOwnEventDescriptors, AnyActorRef } from 'xstate'; import { TestModel } from './TestModel.ts'; @@ -167,11 +168,9 @@ export function createTestModel( typeof getEvents === 'function' ? getEvents(state) : getEvents ?? []; return flatten( - (state as any).getNextEvents().map((eventType: string) => { - // @ts-ignore - if (events.some((e) => e.type === eventType)) { - // @ts-ignore - return events.filter((e) => e.type === eventType); + __unsafe_getAllOwnEventDescriptors(state).map((eventType: string) => { + if (events.some((e) => (e as EventObject).type === eventType)) { + return events.filter((e) => (e as EventObject).type === eventType); } return [{ type: eventType } as any]; // TODO: fix types