From 50c271dc1a1a05b035364f8247aa4d80d613864f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 24 Feb 2022 10:54:19 +0100 Subject: [PATCH] Fixed an issue with context type defined using `schema.context` being sometimes widened based on `config.context` (#3084) --- .changeset/red-frogs-yell.md | 5 +++++ packages/core/src/types.ts | 3 ++- packages/core/test/types.test.ts | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .changeset/red-frogs-yell.md diff --git a/.changeset/red-frogs-yell.md b/.changeset/red-frogs-yell.md new file mode 100644 index 0000000000..e265bebe01 --- /dev/null +++ b/.changeset/red-frogs-yell.md @@ -0,0 +1,5 @@ +--- +'xstate': patch +--- + +Fixed an issue with context type defined using `schema.context` being sometimes widened based on `config.context`. If both are given the `schema.context` should always take precedence and should represent the complete type of the context. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ac225daea3..b1b8d3ae09 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -31,6 +31,7 @@ export type Equals = (() => A extends A2 export type IsAny = Equals; export type Cast = A extends B ? A : B; export type NoInfer = [T][T extends any ? 0 : any]; +export type LowInfer = T & {}; export type EventType = string; export type ActionType = string; @@ -951,7 +952,7 @@ export interface MachineConfig< /** * The initial context (extended state) */ - context?: TContext | (() => TContext); + context?: LowInfer TContext)>; /** * The machine's own version. */ diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index e9d010cf44..67c755f359 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -395,6 +395,25 @@ describe('Typestates', () => { }); describe('context', () => { + it('should infer context type from `config.context` when there is no `schema.context`', () => { + createMachine( + { + context: { + foo: 'test' + } + }, + { + actions: { + someAction: (ctx) => { + ((_accept: string) => {})(ctx.foo); + // @ts-expect-error + ((_accept: number) => {})(ctx.foo); + } + } + } + ); + }); + it('should not use actions as possible inference sites', () => { createMachine( { @@ -426,6 +445,20 @@ describe('context', () => { createMachineWithExtras({ counter: 42 }); }); + + it('should not widen literal types defined in `schema.context` based on `config.context`', () => { + createMachine({ + schema: { + context: {} as { + literalTest: 'foo' | 'bar'; + } + }, + context: { + // @ts-expect-error + literalTest: 'anything' + } + }); + }); }); describe('interpreter', () => {