diff --git a/package.json b/package.json index 9fbbf97..1b61998 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cassiozen/usestatemachine", - "version": "1.0.1", + "version": "2.0.0", "license": "MIT", "author": "Cassio Zen", "main": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 7ab82e3..f9189a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import { useEffect, useReducer } from "react"; -import { UseStateMachine, Machine, $$t } from "./types"; +import { UseStateMachine, Machine, $$t, O } from "./types"; import { assertNever, R, useConstant } from "./extras"; + const useStateMachineImpl = (definition: Machine.Definition.Impl) => { - const [state, dispatch] = useReducer(createReducer(definition), createInitialState(definition)); + const [machineInstant, dispatch] = useReducer(createReducer(definition), createInitialState(definition)); const send = useConstant(() => (sendable: Machine.Sendable.Impl) => dispatch({ type: "SEND", sendable })); @@ -13,30 +14,33 @@ const useStateMachineImpl = (definition: Machine.Definition.Impl) => { }; useEffect(() => { - const entry = R.get(definition.states, state.value)!.effect; + const entry = R.get(definition.states, machineInstant.state)!.effect; let exit = entry?.({ send, setContext, - event: state.event, - context: state.context, + event: machineInstant.event, + context: machineInstant.context, }); return typeof exit === "function" - ? () => exit?.({ send, setContext, event: state.event, context: state.context }) + ? () => exit?.({ send, setContext, event: machineInstant.event, context: machineInstant.context }) : undefined; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.value, state.event]); + }, [machineInstant.state, machineInstant.event]); - return [state, send]; + return { ...machineInstant, send }; }; -const createInitialState = (definition: Machine.Definition.Impl): Machine.State.Impl => { +type MachineInstant = + O.OmitKey + +const createInitialState = (definition: Machine.Definition.Impl): MachineInstant => { let nextEvents = R.keys(R.concat( R.fromMaybe(R.get(definition.states, definition.initial)!.on), R.fromMaybe(definition.on) )) return { - value: definition.initial, + state: definition.initial, context: definition.context as Machine.Context.Impl, event: { type: "$$initial" } as Machine.Event.Impl, nextEvents: nextEvents, @@ -46,56 +50,56 @@ const createInitialState = (definition: Machine.Definition.Impl): Machine.State. const createReducer = (definition: Machine.Definition.Impl) => { let log = createLogger(definition); - return (machineState: Machine.State.Impl, internalEvent: InternalEvent): Machine.State.Impl => { + return (machineInstant: MachineInstant, internalEvent: InternalEvent): MachineInstant => { if (internalEvent.type === "SET_CONTEXT") { - let nextContext = internalEvent.updater(machineState.context); - log("Context update", ["Previous Context", machineState.context], ["Next Context", nextContext]); + let nextContext = internalEvent.updater(machineInstant.context); + log("Context update", ["Previous Context", machineInstant.context], ["Next Context", nextContext]); - return { ...machineState, context: nextContext }; + return { ...machineInstant, context: nextContext }; } if (internalEvent.type === "SEND") { let sendable = internalEvent.sendable; let event = typeof sendable === "string" ? { type: sendable } : sendable; - let context = machineState.context; - let stateNode = R.get(definition.states, machineState.value)!; + let context = machineInstant.context; + let stateNode = R.get(definition.states, machineInstant.state)!; let resolvedTransition = R.get(R.fromMaybe(stateNode.on), event.type) ?? R.get(R.fromMaybe(definition.on), event.type); if (!resolvedTransition) { log( `Current state doesn't listen to event type "${event.type}".`, - ["Current State", machineState], + ["Current State", machineInstant], ["Event", event] ); - return machineState; + return machineInstant; } - let [nextStateValue, didGuardDeny = false] = (() => { + let [nextState, didGuardDeny = false] = (() => { if (typeof resolvedTransition === "string") return [resolvedTransition]; if (resolvedTransition.guard === undefined) return [resolvedTransition.target]; if (resolvedTransition.guard({ context, event })) return [resolvedTransition.target]; return [resolvedTransition.target, true] - })() as [Machine.StateValue.Impl, true?] + })() as [Machine.State.Impl, true?] if (didGuardDeny) { log( - `Transition from "${machineState.value}" to "${nextStateValue}" denied by guard`, + `Transition from "${machineInstant.state}" to "${nextState}" denied by guard`, ["Event", event], ["Context", context] ); - return machineState; + return machineInstant; } - log(`Transition from "${machineState.value}" to "${nextStateValue}"`, ["Event", event]); + log(`Transition from "${machineInstant.state}" to "${nextState}"`, ["Event", event]); - let resolvedStateNode = R.get(definition.states, nextStateValue)!; + let resolvedStateNode = R.get(definition.states, nextState)!; let nextEvents = R.keys(R.concat( R.fromMaybe(resolvedStateNode.on), R.fromMaybe(definition.on) )); return { - value: nextStateValue, + state: nextState, context, event, nextEvents, @@ -144,4 +148,4 @@ const createLogger = (definition: Machine.Definition.Impl) => (groupLabel: strin const useStateMachine = useStateMachineImpl as unknown as UseStateMachine; export default useStateMachine; -export const t = () => ({ [$$t]: undefined as T }) +export const t = () => ({ [$$t]: undefined as unknown as T }) diff --git a/src/types.ts b/src/types.ts index 5fb5090..f2a2d37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,16 +3,46 @@ import { R } from "./extras" export type UseStateMachine = >(definition: A.InferNarrowestObject) => - [ state: Machine.State> - , send: Machine.Send> - ] + Machine> export const $$t = Symbol("$$t"); type $$t = typeof $$t; -export type CreateType = - () => { [$$t]: T } +export type CreateType = () => { [$$t]: T } + +export type Machine, + NextEvents = + ( State extends any + ? A.Get, "type"> + : never + )[] + > = + & A.Instantiated< + { nextEvents: NextEvents + , send: Machine.Send + }> + & ( State extends any + ? A.Instantiated< + { state: State + , context: Machine.Context + , event: Machine.EntryEventForState + , nextEventsT: A.Get, "type">[] + }> + : never + ) + +interface MachineImpl + { state: Machine.State.Impl + , context: Machine.Context.Impl + , event: Machine.Event.Impl + , nextEvents: Machine.Event.Impl["type"][] + , nextEventsT: Machine.Event.Impl["type"][] + , send: Machine.Send.Impl + } export namespace Machine { + export type Impl = MachineImpl + export type Definition< Self, States = A.Get, @@ -53,8 +83,8 @@ export namespace Machine { ) interface DefinitionImp - { initial: StateValue.Impl - , states: R.Of + { initial: State.Impl + , states: R.Of , on?: Definition.On.Impl , schema?: { context?: null, events?: R.Of } , verbose?: boolean @@ -65,7 +95,7 @@ export namespace Machine { export namespace Definition { export type Impl = DefinitionImp - export type FromTypeParamter = + export type FromTypeParameter = "$$internalIsConstraint" extends keyof D ? D extends infer X ? X extends Definition ? X : never : never : D @@ -121,26 +151,27 @@ export namespace Machine { } export type Transition, + TargetString = Machine.State, Event = { type: L.Pop

} > = | TargetString | { target: TargetString , guard?: ( parameter: - { context: Machine.Context - , event: U.Extract, Event> - } + A.Instantiated< + { context: A.Uninstantiated> + , event: A.Uninstantiated, Event>> + }> ) => boolean } type TransitionImpl = - | State.Impl["value"] - | { target: State.Impl["value"] + | Machine.Impl["state"] + | { target: Machine.Impl["state"] , guard?: ( parameter: - { context: State.Impl["context"] - , event: State.Impl["event"] + { context: Machine.Impl["context"] + , event: Machine.Impl["event"] } ) => boolean } @@ -149,10 +180,10 @@ export namespace Machine { } - export type Effect>> = - (parameter: EffectParameterForStateValue) => + export type Effect>> = + (parameter: A.Instantiated>) => | void - | ((parameter: EffectCleanupParameterForStateValue) => void) + | ((parameter: A.Instantiated>) => void) type EffectImpl = (parameter: EffectParameter.Impl) => @@ -218,15 +249,15 @@ export namespace Machine { export type InitialEventType = "$$initial"; } - export type StateValue = + export type State = keyof A.Get - export type InitialStateValue = + export type InitialState = A.Get - type StateValueImpl = string & A.Tag<"Machine.StateValue"> - export namespace StateValue { - export type Impl = StateValueImpl; + type StateImpl = string & A.Tag<"Machine.State"> + export namespace State { + export type Impl = StateImpl; } export type Context = @@ -239,8 +270,8 @@ export namespace Machine { export type Event> = | O.Value<{ [T in U.Exclude]: - A.Get extends infer E - ? E extends any ? O.ShallowClean<{ type: T } & E> : never + A.Get extends infer P + ? P extends any ? O.ShallowClean<{ type: T } & P> : never : never }> | ( A.Get extends true ? never : @@ -271,26 +302,20 @@ export namespace Machine { } export namespace EffectParameter { - export interface EffectParameterForStateValue - extends Base - { event: Machine.EntryEventForStateValue + export interface EffectParameterForState + extends BaseEffectParameter + { event: Machine.EntryEventForState } export namespace Cleanup { - export interface ForStateValue - extends Base - { event: Machine.ExitEventForStateValue + export interface ForState + extends BaseEffectParameter + { event: Machine.ExitEventForState } export type Impl = EffectParameter.Impl } - export interface Base - { send: Machine.Send - , context: Machine.Context - , setContext: Machine.SetContext - } - export type Impl = EffectParameterImpl; } export interface EffectParameterImpl @@ -300,24 +325,24 @@ export namespace Machine { , setContext: SetContext.Impl } - export interface EffectParameterForStateValue + export interface EffectParameterForState extends BaseEffectParameter - { event: Machine.EntryEventForStateValue + { event: A.Uninstantiated> } - export interface EffectCleanupParameterForStateValue + export interface EffectCleanupParameterForState extends BaseEffectParameter - { event: Machine.ExitEventForStateValue + { event: A.Uninstantiated> } export interface BaseEffectParameter { send: Machine.Send - , context: Machine.Context + , context: A.Uninstantiated> , setContext: Machine.SetContext } - export type EntryEventForStateValue = - | ( StateValue extends InitialStateValue + export type EntryEventForState = + | ( State extends InitialState ? { type: Definition.InitialEventType } : never ) @@ -327,7 +352,7 @@ export namespace Machine { | O.Value<{ [S in keyof A.Get]: O.Value<{ [E in keyof A.Get]: A.Get extends infer T - ? (T extends A.String ? T : A.Get) extends StateValue + ? (T extends A.String ? T : A.Get) extends State ? E : never : never @@ -335,7 +360,7 @@ export namespace Machine { }> | O.Value<{ [E in keyof A.Get]: A.Get extends infer T - ? (T extends A.String ? T : A.Get) extends StateValue + ? (T extends A.String ? T : A.Get) extends State ? E : never : never @@ -343,11 +368,11 @@ export namespace Machine { } > - export type ExitEventForStateValue = + export type ExitEventForState = U.Extract< Event, { type: - | keyof A.Get + | keyof A.Get | keyof A.Get } > @@ -368,8 +393,8 @@ export namespace Machine { } export type Send = - { (sendable: U.Exclude, A.String>): void - , (sendable: U.Extract, A.String>): void + { (sendable: A.Uninstantiated, A.String>>): void + , (sendable: A.Uninstantiated, A.String>>): void } type SendImpl = (send: Sendable.Impl) => void @@ -386,40 +411,14 @@ export namespace Machine { export type Impl = SetContextImpl; } - export type ContextUpdater = (context: Context) => Context + export type ContextUpdater = + (context: A.Uninstantiated>) => + A.Uninstantiated> type ContextUpdaterImpl = (context: Context.Impl) => Context.Impl export namespace ContextUpdater { export type Impl = ContextUpdaterImpl; } - - export type State, - NextEvents = - ( Value extends any - ? A.Get, "type"> - : never - )[] - > = - Value extends any - ? { value: Value - , context: Context - , event: EntryEventForStateValue - , nextEventsT: A.Get, "type">[] - , nextEvents: NextEvents - } - : never - - interface StateImpl - { value: StateValue.Impl - , context: Context.Impl - , event: Event.Impl - , nextEvents: Event.Impl["type"][] - , nextEventsT: Event.Impl["type"][] - } - export namespace State { - export type Impl = StateImpl - } } export namespace L { @@ -446,11 +445,6 @@ export namespace S { : false; } -export namespace F { - export type Call = F extends (...args: any[]) => infer R ? R : never; - export type Parameters = F extends (...args: infer A) => any ? A : never; -} - export namespace U { export type Extract = T extends U ? T : never; export type Exclude = T extends U ? never : T; @@ -459,11 +453,11 @@ export namespace U { export namespace O { export type Value = T[keyof T]; export type ShallowClean = { [K in keyof T]: T[K] } + export type OmitKey = { [P in U.Exclude]: T[P] } } export namespace A { export type Cast = T extends U ? T : U; - export type Fallback = T extends U ? T : U; export type Tuple = T[] | [T]; export type Object = object; export type String = string; @@ -508,10 +502,6 @@ export namespace A { P extends [infer K1, ...infer Kr] ? K1 extends keyof T ? _Get : - K1 extends Get.Returned$$ ? - _Get infer R ? R : undefined, Kr, F> : - K1 extends Get.Parameters$$ ? - _Get any ? A : undefined, Kr, F> : F : never @@ -520,14 +510,6 @@ export namespace A { ? A.Cast : never - export namespace Get { - const Returned$$ = Symbol("Returned$$"); - export type Returned$$ = typeof Returned$$; - - const Parameters$$ = Symbol("Parameters$$"); - export type Parameters$$ = typeof Parameters$$; - } - export type CustomError = Place extends (S.IsLiteral extends true ? Error : A.String) ? Place extends `${S.Assert} ` @@ -535,6 +517,33 @@ export namespace A { : `${S.Assert} ` : Error + export type Instantiated = + T extends Uninstantiated ? U : + T extends Builtin ? T : + T extends any + ? T extends A.Function + ? T extends { (...a: infer A1): infer R1, (...a: infer A2): infer R2 } + ? { (...a: Instantiated): Instantiated + , (...a: Instantiated): Instantiated + } : + T extends (...a: infer A1) => infer R1 + ? (...a1: Instantiated) => Instantiated : + never : + T extends A.Object + ? { [K in keyof T]: Instantiated } : + T + : never + + type Builtin = + | { [Symbol.toStringTag]: string } + | Error + | Date + | RegExp + | Generator + + export type Uninstantiated = T & { [$$uninstantiated]: true } + declare const $$uninstantiated: unique symbol; + export type Tag = { [_ in N]: void } diff --git a/test/index.test.ts b/test/index.test.ts index 570dbcf..62d3d2f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -6,14 +6,14 @@ const logger: Console["log"] = (...xs) => log += xs.reduce( (a, x) => a + (typeof x === "string" ? x : JSON.stringify(x)), "" - ) + ) + "\n" const clearLog = () => log = ""; const useStateMachine = ((d: any) => _useStateMachine({ ...d, console: { log: logger } }) - ) as typeof _useStateMachine + ) as any as typeof _useStateMachine describe("useStateMachine", () => { describe("States & Transitions", () => { @@ -32,12 +32,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["ACTIVATE"], nextEventsT: ["ACTIVATE"], + send: expect.any(Function) }); }); @@ -57,17 +58,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("ACTIVATE"); + result.current.send("ACTIVATE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE"], nextEventsT: ["DEACTIVATE"], + send: expect.any(Function) }); }); @@ -90,17 +92,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("FORCE_ACTIVATE"); + result.current.send("FORCE_ACTIVATE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "FORCE_ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE", "FORCE_ACTIVATE"], nextEventsT: ["DEACTIVATE", "FORCE_ACTIVATE"], + send: expect.any(Function), }); }); @@ -120,17 +123,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]({ type: "ACTIVATE" }); + result.current.send({ type: "ACTIVATE" }); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE"], nextEventsT: ["DEACTIVATE"], + send: expect.any(Function), }); }); @@ -152,15 +156,16 @@ describe("useStateMachine", () => { act(() => { // TypeScript won"t allow me to type "ON" because it knows it"s not a valid event // @ts-expect-error - result.current[1]("ON"); + result.current.send("ON"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -188,17 +193,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); it("should invoke effect callbacks", () => { @@ -227,7 +233,7 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(entry.mock.calls.length).toBe(2); @@ -260,14 +266,15 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -295,7 +302,7 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]({ type: "ACTIVATE", number: 10 }); + result.current.send({ type: "ACTIVATE", number: 10 }); }); expect(effect.mock.calls[0][0]["event"]).toStrictEqual({ type: "ACTIVATE", number: 10 }); }); @@ -352,16 +359,17 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(guard).toHaveBeenCalled(); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -388,18 +396,19 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(guard).toHaveBeenCalled(); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); }); @@ -420,12 +429,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ - value: "inactive", + expect(result.current).toStrictEqual({ + state: "inactive", context: { foo: "bar" }, event: { type: "$$initial" }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -453,12 +463,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ - value: "inactive", + expect(result.current).toStrictEqual({ + state: "inactive", context: { foo: "bar" }, event: { type: "$$initial" }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -482,17 +493,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ - value: "active", + expect(result.current).toStrictEqual({ + state: "active", context: { toggleCount: 1 }, event: { type: "TOGGLE", }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); it("should update context on exit", () => { @@ -515,17 +527,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ - value: "active", + expect(result.current).toStrictEqual({ + state: "active", context: { toggleCount: 1 }, event: { type: "TOGGLE", }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); }); @@ -592,7 +605,7 @@ describe("useStateMachine", () => { if (result.all[0] instanceof Error) throw result.all[0]; else if (result.all[1] instanceof Error) throw result.all[1]; - else expect(result.all[0][1]).toBe(result.all[1][1]); + else expect(result.all[0].send).toBe(result.all[1].send); }); }); }); diff --git a/test/types.twoslash-test.ts b/test/types.twoslash-test.ts index f45be5e..512985a 100644 --- a/test/types.twoslash-test.ts +++ b/test/types.twoslash-test.ts @@ -2,7 +2,7 @@ import { A, LS, UseStateMachine, CreateType } from "../src/types"; const useStateMachine = (() => []) as any as UseStateMachine; -const t = (() => {}) as CreateType +const t = (() => undefined) as unknown as CreateType const query = () => ((global as any).twoSlashQueries.shift()) as { completions: string[], text: string } @@ -432,13 +432,13 @@ describe("Machine.Definition", () => { }) it("doesn't infer narrowest", () => { - let [state] = useStateMachine({ + let machine = useStateMachine({ schema: {}, context: { foo: "hello" }, initial: "a", states: { a: {} } }) - A.test(A.areEqual()) + A.test(A.areEqual()) }) }) @@ -1123,8 +1123,8 @@ describe("Machine.Definition", () => { }) }) -describe("UseStateMachine", () => { - let [state, send] = useStateMachine({ +describe("Machine", () => { + let machine = useStateMachine({ schema: { events: { X: t<{ foo: number }>(), @@ -1152,46 +1152,40 @@ describe("UseStateMachine", () => { } }) - describe("Machine.State", () => { - A.test(A.areEqual< - typeof state, - | { value: "a" + A.test(A.areEqual< + typeof machine, + & { nextEvents: ("X" | "Y" | "Z")[] + , send: + { ( sendable: + | { type: "X", foo: number } + | { type: "Y", bar?: number } + | { type: "Z" } + ): void + , ( sendable: + | "Y" + | "Z" + ): void + } + } + & ( { state: "a" , context: { foo?: number } , event: | { type: "$$initial" } | { type: "Y", bar?: number } | { type: "Z" } , nextEventsT: ("X" | "Z")[] - , nextEvents: ("X" | "Y" | "Z")[] } - | { value: "b" + | { state: "b" , context: { foo?: number } , event: { type: "X", foo: number } , nextEventsT: ("Y" | "Z")[] - , nextEvents: ("X" | "Y" | "Z")[] } - >()) - }) - - describe("Machine.Send", () => { - A.test(A.areEqual< - typeof send, - { ( sendable: - | { type: "X", foo: number } - | { type: "Y", bar?: number } - | { type: "Z" } - ): void - , ( sendable: - | "Y" - | "Z" - ): void - } - >()) - }) + ) + >()) }) describe("Machine.Definition.FromTypeParamter", () => { - let [state, send] = useStateMachine({ + let machine = useStateMachine({ context: { toggleCount: 0 }, initial: "inactive", states: { @@ -1208,33 +1202,31 @@ describe("Machine.Definition.FromTypeParamter", () => { }) A.test(A.areEqual< - typeof state, - | { value: "inactive" - , context: { toggleCount: number } - , event: - | { type: "$$initial" } - | { type: "TOGGLE" } - , nextEventsT: "TOGGLE"[] - , nextEvents: "TOGGLE"[] - } - | { value: "active" - , context: { toggleCount: number } - , event: { type: "TOGGLE" } - , nextEventsT: "TOGGLE"[] - , nextEvents: "TOGGLE"[] + typeof machine, + & { nextEvents: "TOGGLE"[] + , send: + { (sendable: { type: "TOGGLE" }): void + , (sendable: "TOGGLE"): void + } } - >()) - - A.test(A.areEqual< - typeof send, - { (sendable: { type: "TOGGLE" }): void - , (sendable: "TOGGLE"): void - } + & ( { state: "inactive" + , context: { toggleCount: number } + , event: + | { type: "$$initial" } + | { type: "TOGGLE" } + , nextEventsT: "TOGGLE"[] + } + | { state: "active" + , context: { toggleCount: number } + , event: { type: "TOGGLE" } + , nextEventsT: "TOGGLE"[] + } + ) >()) }) describe("fix(Machine.State['nextEvents']): only normalize don't widen", () => { - let [state] = useStateMachine({ + let machine = useStateMachine({ schema: { events: { Y: t<{}>() } }, @@ -1246,11 +1238,11 @@ describe("fix(Machine.State['nextEvents']): only normalize don't widen", () => { } }) - A.test(A.areEqual()) + A.test(A.areEqual()) }) describe("workaround for #65", () => { - let [_, send] = useStateMachine({ + let machine = useStateMachine({ schema: { events: { A: t<{ value: string }>() @@ -1267,9 +1259,46 @@ describe("workaround for #65", () => { }) A.test(A.areEqual< - typeof send, + typeof machine.send, { (sendable: { type: "A", value: string } | { type: "B" }): void , (sendable: "B"): void } >()) }) + +describe("A.Instantiated", () => { + it("does not instantiate builtin objects", () => { + let _x: A.Instantiated = new Date() + _x; +// ^? + expect(query().text).toContain("Date") + }) + + it("does not instantiate context", () => { + interface Something { foo: string } + let _machine = useStateMachine({ + // ^? + context: { foo: "" } as Something, + initial: "a", + states: { a: {} } + }) + _machine; + + expect(query().text).toContain("Something") + }) + + it("does not instantiate event payloads deeply", () => { + interface Something { foo: string } + let _machine = useStateMachine({ + // ^? + schema: { + events: { A: t<{ bar: Something }>() } + }, + initial: "a", + states: { a: { on: { A: "a" } } } + }) + _machine; + + expect(query().text).toContain("Something") + }) +})