Skip to content

Commit

Permalink
fix(builders): _xstateTree property typed incorrectly
Browse files Browse the repository at this point in the history
Passes through the selector/action/slots types correctly when building a machine with createXStateTreeMachine so that accessing properties on the _xstateTree property has the correct types instead of the defaults
  • Loading branch information
UberMouse committed Mar 4, 2024
1 parent c900ff6 commit 6f3373c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 14 deletions.
49 changes: 48 additions & 1 deletion src/builders.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,60 @@
import { act, render, waitFor } from "@testing-library/react";
import { createMemoryHistory } from "history";
import React from "react";
import { setup } from "xstate";

import { buildRoutingMachine, viewToMachine } from "./builders";
import {
buildRoutingMachine,
createXStateTreeMachine,
viewToMachine,
} from "./builders";
import { buildCreateRoute } from "./routing";
import { XstateTreeHistory } from "./types";
import { buildRootComponent } from "./xstateTree";

describe("xstate-tree builders", () => {
describe("createXStateTreeMachine", () => {
it("passes through the selectors/actions/slots/view types correctly through to the _xstateTree property", () => {
const machine = setup({
types: {
context: {} as { foo: string; bar: string },
},
}).createMachine({
context: {
foo: "foo",
bar: "bar",
},
initial: "idle",
states: {
idle: {},
},
});

const xstateTreeMachine = createXStateTreeMachine(machine, {
selectors({ ctx }) {
return {
foobar: ctx.foo + ctx.bar,
};
},
View({ selectors }) {
return <div>{selectors.foobar}</div>;
},
});

const View = xstateTreeMachine._xstateTree.View;

<View
actions={{}}
slots={{}}
selectors={{
foobar: "foobar",
// @ts-expect-error - should not be able to access context properties
foo: "expect-error",
}}
/>;
});
});

describe("viewToMachine", () => {
it("takes a React view and wraps it in an xstate-tree machine that renders that view", async () => {
const ViewMachine = viewToMachine(() => <div>hello world</div>);
Expand Down
11 changes: 8 additions & 3 deletions src/builders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from "react";
import {
setup,
type AnyStateMachine,
type ContextFrom,
AnyStateNodeConfig,
createMachine,
type ContextFrom,
} from "xstate";

import { AnyRoute } from "./routing";
Expand Down Expand Up @@ -37,11 +37,16 @@ export function createXStateTreeMachine<
>(
machine: TMachine,
options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>
): XstateTreeMachine<TMachine> {
): XstateTreeMachine<TMachine, TSelectorsOutput, TActionsOutput, TSlots> {
const selectors = options.selectors ?? (({ ctx }) => ctx);
const actions = options.actions ?? (() => ({}));

const machineWithMeta = machine as unknown as XstateTreeMachine<TMachine>;
const machineWithMeta = machine as unknown as XstateTreeMachine<
TMachine,
TSelectorsOutput,
TActionsOutput,
TSlots
>;
machineWithMeta._xstateTree = {
selectors: selectors as any,
actions: actions as any,
Expand Down
28 changes: 24 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,35 @@ export type MatchesFrom<T extends AnyStateMachine> = (
/**
* @internal
*/
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine> = {
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine>;
export type XstateTreeMachineInjection<
TMachine extends AnyStateMachine,
TSelectorsOutput = ContextFrom<TMachine>,
TActionsOutput = Record<never, string>,
TSlots extends readonly Slot[] = Slot[]
> = {
_xstateTree: XstateTreeMachineStateSchemaV2<
TMachine,
TSelectorsOutput,
TActionsOutput,
TSlots
>;
};

/**
* @public
*/
export type XstateTreeMachine<TMachine extends AnyStateMachine> = TMachine &
XstateTreeMachineInjection<TMachine>;
export type XstateTreeMachine<
TMachine extends AnyStateMachine,
TSelectorsOutput = ContextFrom<TMachine>,
TActionsOutput = Record<never, string>,
TSlots extends readonly Slot[] = Slot[]
> = TMachine &
XstateTreeMachineInjection<
TMachine,
TSelectorsOutput,
TActionsOutput,
TSlots
>;

/**
* @public
Expand Down
12 changes: 6 additions & 6 deletions xstate-tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function buildRoutingMachine<TRoutes extends AnyRoute[]>(_routes: TRoutes
export type CanHandleEvent<TMachine extends AnyStateMachine> = (e: EventFrom<TMachine>) => boolean;

// @public
export function createXStateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>): XstateTreeMachine<TMachine>;
export function createXStateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta<TMachine, TSelectorsOutput, TActionsOutput, TSlots>): XstateTreeMachine<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;

// @public
export const genericSlotsTestingDummy: any;
Expand Down Expand Up @@ -383,13 +383,13 @@ export type XstateTreeHistory<T = unknown> = History_2<{
// Warning: (ae-incompatible-release-tags) The symbol "XstateTreeMachine" is marked as @public, but its signature references "XstateTreeMachineInjection" which is marked as @internal
//
// @public (undocumented)
export type XstateTreeMachine<TMachine extends AnyStateMachine> = TMachine & XstateTreeMachineInjection<TMachine>;
export type XstateTreeMachine<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = Slot[]> = TMachine & XstateTreeMachineInjection<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;

// Warning: (ae-internal-missing-underscore) The name "XstateTreeMachineInjection" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine> = {
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine>;
export type XstateTreeMachineInjection<TMachine extends AnyStateMachine, TSelectorsOutput = ContextFrom<TMachine>, TActionsOutput = Record<never, string>, TSlots extends readonly Slot[] = Slot[]> = {
_xstateTree: XstateTreeMachineStateSchemaV2<TMachine, TSelectorsOutput, TActionsOutput, TSlots>;
};

// @public (undocumented)
Expand All @@ -400,8 +400,8 @@ export type XstateTreeMachineStateSchemaV2<TMachine extends AnyStateMachine, TSe
// src/routing/createRoute/createRoute.ts:285:19 - (ae-forgotten-export) The symbol "MergeRouteTypes" needs to be exported by the entry point index.d.ts
// src/routing/createRoute/createRoute.ts:285:19 - (ae-forgotten-export) The symbol "ResolveZodType" needs to be exported by the entry point index.d.ts
// src/routing/createRoute/createRoute.ts:322:9 - (ae-forgotten-export) The symbol "RouteRedirect" needs to be exported by the entry point index.d.ts
// src/types.ts:117:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal
// src/types.ts:118:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal
// src/types.ts:137:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal
// src/types.ts:138:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal

// (No @packageDocumentation comment for this package)

Expand Down

0 comments on commit 6f3373c

Please sign in to comment.