Skip to content

Commit

Permalink
Remove getNextEvents method from the MachineSnapshot (#4478)
Browse files Browse the repository at this point in the history
* Remove `getNextEvents` method from the `MachineSnapshot`

* changeset

---------

Co-authored-by: David Khourshid <[email protected]>
  • Loading branch information
Andarist and davidkpiano authored Nov 24, 2023
1 parent c0025c3 commit 384f0ff
Show file tree
Hide file tree
Showing 10 changed files with 34 additions and 178 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-roses-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': major
---

Removed `MachineSnapshot['nextEvents']`.
23 changes: 0 additions & 23 deletions packages/core/src/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventDescriptor<TEvent>>;

getMeta: (
this: MachineSnapshot<
TContext,
Expand Down Expand Up @@ -322,7 +308,6 @@ const machineSnapshotToJSON = function toJSON(this: AnyMachineSnapshot) {
_nodes: nodes,
tags,
machine,
getNextEvents,
getMeta,
toJSON,
can,
Expand All @@ -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) => {
Expand Down Expand Up @@ -383,7 +362,6 @@ export function createMachineSnapshot<
matches: machineSnapshotMatches as any,
hasTag: machineSnapshotHasTag,
can: machineSnapshotCan,
getNextEvents: machineSnapshotGetNextEvents,
getMeta: machineSnapshotGetMeta,
toJSON: machineSnapshotToJSON
};
Expand Down Expand Up @@ -426,7 +404,6 @@ export function getPersistedState<
can,
hasTag,
matches,
getNextEvents,
getMeta,
toJSON,
...jsonValues
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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',
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends object>(value: T): Array<keyof T & string> {
return Object.keys(value) as Array<keyof T & string>;
Expand Down Expand Up @@ -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)]))];
}
9 changes: 0 additions & 9 deletions packages/core/test/machine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
72 changes: 0 additions & 72 deletions packages/core/test/state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
5 changes: 3 additions & 2 deletions packages/xstate-graph/src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
AnyActorLogic,
SnapshotFrom,
EventFromLogic,
Snapshot
Snapshot,
__unsafe_getAllOwnEventDescriptors
} from 'xstate';
import type {
SerializedEvent,
Expand Down Expand Up @@ -91,7 +92,7 @@ export function createDefaultMachineOptions<TMachine extends AnyStateMachine>(
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
);
Expand Down
62 changes: 0 additions & 62 deletions packages/xstate-solid/test/useMachine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<button
data-testid="transition-button"
onclick={() => send({ type: 'TRANSITION' })}
/>
<ul>
<For
each={state.getNextEvents()}
fallback={<li>Empty / undefined</li>}
>
{(event, i) => <li data-testid={`event-${i()}`}>{event}</li>}
</For>
</ul>
</div>
);
};

render(() => <App />);
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',
Expand Down
9 changes: 4 additions & 5 deletions packages/xstate-test/src/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
StateValue,
SnapshotFrom,
MachineSnapshot,
__unsafe_getAllOwnEventDescriptors,
AnyActorRef
} from 'xstate';
import { TestModel } from './TestModel.ts';
Expand Down Expand Up @@ -167,11 +168,9 @@ export function createTestModel<TMachine extends AnyStateMachine>(
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
Expand Down

0 comments on commit 384f0ff

Please sign in to comment.