Skip to content

Commit

Permalink
Fixed issues with TEvent narrowing (#3252)
Browse files Browse the repository at this point in the history
* Fixed issues with `TEvent` narrowing

* Fixed `model.assign` inference by using old `ExtractEvent` implementation in it
  • Loading branch information
Andarist committed Apr 27, 2022
1 parent 326bae9 commit a94dfd4
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-pianos-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with `EventFrom` not being able to extract events that had a union of strings as their `type` (such as `{ type: 'INC' | 'DEC'; value: number; }`).
5 changes: 5 additions & 0 deletions .changeset/thin-tools-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed an issue with default `TEvent` (`{ type: string }`) not being correctly provided to inline transition actions.
13 changes: 9 additions & 4 deletions packages/core/src/model.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
BaseActionObject,
Compute,
EventObject,
ExtractEvent,
MachineConfig,
Prop,
PropertyAssigner,
Expand All @@ -19,6 +18,12 @@ import {
TypegenDisabled
} from './typegenTypes';

// real `ExtractEvent` breaks `model.assign` inference within transition actions
type SimplisticExtractEvent<
TEvent extends EventObject,
TEventType extends TEvent['type']
> = TEvent extends { type: TEventType } ? TEvent : never;

export interface Model<
TContext,
TEvent extends EventObject,
Expand All @@ -28,10 +33,10 @@ export interface Model<
initialContext: TContext;
assign: <TEventType extends TEvent['type'] = TEvent['type']>(
assigner:
| Assigner<TContext, ExtractEvent<TEvent, TEventType>>
| PropertyAssigner<TContext, ExtractEvent<TEvent, TEventType>>,
| Assigner<TContext, SimplisticExtractEvent<TEvent, TEventType>>
| PropertyAssigner<TContext, SimplisticExtractEvent<TEvent, TEventType>>,
eventType?: TEventType
) => AssignAction<TContext, ExtractEvent<TEvent, TEventType>>;
) => AssignAction<TContext, SimplisticExtractEvent<TEvent, TEventType>>;
events: Prop<TModelCreators, 'events'>;
actions: Prop<TModelCreators, 'actions'>;
reset: () => AssignAction<TContext, any>;
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,7 @@ export type TransitionConfigOrTarget<
export type TransitionsConfigMap<TContext, TEvent extends EventObject> = {
[K in TEvent['type'] | '' | '*']?: K extends '' | '*'
? TransitionConfigOrTarget<TContext, TEvent>
: TransitionConfigOrTarget<
TContext,
TEvent extends { type: K } ? TEvent : never
>;
: TransitionConfigOrTarget<TContext, ExtractEvent<TEvent, K>>;
};

type TransitionsConfigArray<TContext, TEvent extends EventObject> = Array<
Expand Down Expand Up @@ -1657,7 +1654,11 @@ export type Spawnable =
export type ExtractEvent<
TEvent extends EventObject,
TEventType extends TEvent['type']
> = TEvent extends { type: TEventType } ? TEvent : never;
> = TEvent extends any
? TEventType extends TEvent['type']
? TEvent
: never
: never;

export interface BaseActorRef<TEvent extends EventObject> {
send: (event: TEvent) => void;
Expand Down Expand Up @@ -1840,8 +1841,8 @@ type ResolveEventType<T> = ReturnTypeOrValue<T> extends infer R
export type EventFrom<
T,
K extends Prop<TEvent, 'type'> = never,
TEvent = ResolveEventType<T>
> = IsNever<K> extends true ? TEvent : Extract<TEvent, { type: K }>;
TEvent extends EventObject = ResolveEventType<T>
> = IsNever<K> extends true ? TEvent : ExtractEvent<TEvent, K>;

export type ContextFrom<T> = ReturnTypeOrValue<T> extends infer R
? R extends StateMachine<
Expand Down
14 changes: 14 additions & 0 deletions packages/core/test/typeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ describe('EventFrom', () => {
// @ts-expect-error
acceptUserModelEventSubset({ type: 'eventThatDoesNotExist' });
});

it('should correctly extract events from events having union of strings as their `type`', () => {
const machine = createMachine({
schema: {
events: {} as { type: 'INC' | 'DEC' }
}
});

type MachineEvent = EventFrom<typeof machine, 'INC'>;

const acceptEvent = (_event: MachineEvent) => {};

acceptEvent({ type: 'INC' });
});
});

describe('MachineOptionsFrom', () => {
Expand Down
17 changes: 17 additions & 0 deletions packages/core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,23 @@ describe('events', () => {
}
});
});

it('should provide the default TEvent to transition actions when there is no specific TEvent configured', () => {
createMachine({
schema: {
context: {} as {
count: number;
}
},
on: {
FOO: {
actions: (_context, event) => {
((_accept: string) => {})(event.type);
}
}
}
});
});
});

describe('interpreter', () => {
Expand Down

0 comments on commit a94dfd4

Please sign in to comment.