-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add assertEvent * Cleanup * Support multiple events * TSDocs * Changeset * Update .changeset/cuddly-taxis-walk.md Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> * Update packages/core/src/assert.ts Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> * Update .changeset/cuddly-taxis-walk.md Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> * Update packages/core/src/assert.ts Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com> * Export * Cleanup * Fixes --------- Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
- Loading branch information
1 parent
dd668c6
commit 8e8d2ba
Showing
7 changed files
with
176 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
'xstate': minor | ||
--- | ||
|
||
Add `assertEvent(...)` to help provide strong typings for events that can't be easily inferred, such as events in `entry` and `exit` actions, or in `invoke.input`. | ||
|
||
The `assertEvent(event, 'someType')` function will _throw_ if the event is not the expected type. This ensures that the `event` is guaranteed to have that type, and assumes that the event object has the expected payload (naturally enforced by TypeScript). | ||
|
||
```ts | ||
// ... | ||
entry: ({ event }) => { | ||
assertEvent(event, 'greet'); | ||
// event is { type: 'greet'; message: string } | ||
|
||
assertEvent(event, ['greet', 'notify']); | ||
// event is { type: 'greet'; message: string } | ||
// or { type: 'notify'; message: string; level: 'info' | 'error' } | ||
}, | ||
exit: ({ event }) => { | ||
assertEvent(event, 'doNothing'); | ||
// event is { type: 'doNothing' } | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { EventObject } from './types.ts'; | ||
import { toArray } from './utils.ts'; | ||
|
||
/** | ||
* Asserts that the given event object is of the specified type or types. | ||
* Throws an error if the event object is not of the specified types. | ||
@example | ||
```ts | ||
// ... | ||
entry: ({ event }) => { | ||
assertEvent(event, 'doNothing'); | ||
// event is { type: 'doNothing' } | ||
}, | ||
// ... | ||
exit: ({ event }) => { | ||
assertEvent(event, 'greet'); | ||
// event is { type: 'greet'; message: string } | ||
assertEvent(event, ['greet', 'notify']); | ||
// event is { type: 'greet'; message: string } | ||
// or { type: 'notify'; message: string; level: 'info' | 'error' } | ||
}, | ||
``` | ||
*/ | ||
export function assertEvent< | ||
TEvent extends EventObject, | ||
TAssertedType extends TEvent['type'] | ||
>( | ||
event: TEvent, | ||
type: TAssertedType | TAssertedType[] | ||
): asserts event is TEvent & { type: TAssertedType } { | ||
const types = toArray(type); | ||
if (!types.includes(event.type as any)) { | ||
const typesText = | ||
types.length === 1 | ||
? `type "${types[0]}"` | ||
: `one of types "${types.join('", "')}"`; | ||
throw new Error( | ||
`Expected event ${JSON.stringify(event)} to have ${typesText}` | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { createActor, createMachine, assertEvent } from '../src'; | ||
|
||
describe('assertion helpers', () => { | ||
it('assertEvent asserts the correct event type', (done) => { | ||
const machine = createMachine( | ||
{ | ||
types: { | ||
events: {} as | ||
| { type: 'greet'; message: string } | ||
| { type: 'count'; value: number } | ||
}, | ||
on: { | ||
greet: { actions: 'greet' }, | ||
count: { actions: 'greet' } | ||
} | ||
}, | ||
{ | ||
actions: { | ||
greet: ({ event }) => { | ||
// @ts-expect-error | ||
event.message; | ||
|
||
assertEvent(event, 'greet'); | ||
event.message satisfies string; | ||
|
||
// @ts-expect-error | ||
event.count; | ||
} | ||
} | ||
} | ||
); | ||
|
||
const actor = createActor(machine); | ||
|
||
actor.subscribe({ | ||
error(err) { | ||
expect(err).toMatchInlineSnapshot( | ||
`[Error: Expected event {"type":"count","value":42} to have type "greet"]` | ||
); | ||
done(); | ||
} | ||
}); | ||
|
||
actor.start(); | ||
|
||
actor.send({ type: 'count', value: 42 }); | ||
}); | ||
|
||
it('assertEvent asserts multiple event types', (done) => { | ||
const machine = createMachine( | ||
{ | ||
types: { | ||
events: {} as | ||
| { type: 'greet'; message: string } | ||
| { type: 'notify'; message: string; level: 'info' | 'error' } | ||
| { type: 'count'; value: number } | ||
}, | ||
on: { | ||
greet: { actions: 'greet' }, | ||
count: { actions: 'greet' } | ||
} | ||
}, | ||
{ | ||
actions: { | ||
greet: ({ event }) => { | ||
// @ts-expect-error | ||
event.message; | ||
|
||
assertEvent(event, ['greet', 'notify']); | ||
event.message satisfies string; | ||
|
||
// @ts-expect-error | ||
event.level; | ||
|
||
assertEvent(event, ['notify']); | ||
event.level satisfies 'info' | 'error'; | ||
|
||
// @ts-expect-error | ||
event.count; | ||
} | ||
} | ||
} | ||
); | ||
|
||
const actor = createActor(machine); | ||
|
||
actor.subscribe({ | ||
error(err) { | ||
expect(err).toMatchInlineSnapshot( | ||
`[Error: Expected event {"type":"count","value":42} to have one of types "greet", "notify"]` | ||
); | ||
done(); | ||
} | ||
}); | ||
|
||
actor.start(); | ||
|
||
actor.send({ type: 'count', value: 42 }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters