Skip to content

Instantly share code, notes, and snippets.

@highgrove
Created May 28, 2021 06:22
Show Gist options
  • Save highgrove/2d06703c8c2f2e5e38d8b310b7da1e2e to your computer and use it in GitHub Desktop.
Save highgrove/2d06703c8c2f2e5e38d8b310b7da1e2e to your computer and use it in GitHub Desktop.
Simple xstate test from machine definition
import {createMachine, EventObject, StateNodeConfig, StateSchema} from "xstate";
import {createModel as xCreateModel} from "@xstate/test";
export type TestDefinition<TTestContext, TContext, TSchema extends StateSchema<TContext>> = {
test?: (context: TTestContext) => Promise<any> | void;
} & (TSchema['states'] extends Record<string, StateSchema<TContext>> ? {
states?: {
[key in keyof TSchema['states']]?: TestDefinition<TTestContext, TContext, TSchema['states'][key]>
}
} : Record<string, never>);
function createModel<TTestContext, TContext, TSchema extends StateSchema<TContext>, TEvent extends EventObject>(definition: StateNodeConfig<TContext, TSchema, TEvent>, {
test,
states
}: TestDefinition<TTestContext, TContext, TSchema>) {
const mappedStates = states ? Object.entries(definition.states).map(([key, def]) => [key, createModel(def, states[key] ?? {})]).reduce((agg, [k, d]) => ({
...agg,
[k]: d
}), {}) : definition.states;
return {
...definition,
meta: test ? {test} : {},
states: mappedStates,
}
}
type StateContextFrom<TStateNodeConfig> = TStateNodeConfig extends StateNodeConfig<infer TContext, infer TSchema, infer TEvent> ? TContext : never;
type StateSchemaFrom<TStateNodeConfig> = TStateNodeConfig extends StateNodeConfig<infer TContext, infer TSchema, infer TEvent> ? TSchema : never;
type StateEventFrom<TStateNodeConfig> = TStateNodeConfig extends StateNodeConfig<infer TContext, infer TSchema, infer TEvent> ? TEvent : never;
type StateNodeConfigFrom<TStateNodeConfig> = TStateNodeConfig extends StateNodeConfig<infer TContext, infer TSchema, infer TEvent> ? StateNodeConfig<TContext, TSchema, TEvent> : never;
export declare type EventExecutor<TTestContext, TEvent extends EventObject> = (
testContext: TTestContext,
event: TEvent) => Promise<any> | void;
export interface TestEventConfig<TTestContext, TEvent extends EventObject> {
exec?: EventExecutor<TTestContext, TEvent>;
cases?: Omit<TEvent, 'type'>[];
}
type Platform<T extends string> = `error.platform.${T}` | `done.invoke.${T}`
type PlatformEvents<TTestContext, T extends string> = { [key in Platform<T>]?: TestEventConfig<TTestContext, any> };
/*
* Simple xstate model test definition
*
* */
export function defineModel<TStateNodeConfig = any, TTestContext = any, TServices extends string = never>(definition: StateNodeConfigFrom<TStateNodeConfig>, {
events = {},
...model
}: TestDefinition<TTestContext, StateContextFrom<TStateNodeConfig>, StateSchemaFrom<TStateNodeConfig>> & {
events?: {
[key in StateEventFrom<TStateNodeConfig>['type']]?: TestEventConfig<TTestContext, Extract<StateEventFrom<TStateNodeConfig>, { type: key }>>;
} & PlatformEvents<TTestContext, TServices>
}) {
return xCreateModel<TTestContext, StateContextFrom<TStateNodeConfig>>(createMachine(createModel(definition, model)), {events});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment