Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 170 additions & 8 deletions ember-statechart-component/src/public-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,167 @@ interface Signature<
}

declare module 'xstate' {
/**
* NOTE: before you think about making StateValue renderable,
* remember that it can be an object (for nested states)
*/
function send(eventType: string): void;
function send<
TContext,
TEvent,
TChildren,
TActor,
TAction,
TGuard,
TDelay,
TStateValue,
TTag,
TInput,
TOutput,
TEmitted,
TMeta,
TConfig,
>(
event: EventFromLogic<
StateMachine<
TContext,
TEvent,
TChildren,
TActor,
TAction,
TGuard,
TDelay,
TStateValue,
TTag,
TInput,
TOutput,
TEmitted,
TMeta,
TConfig
>
>
): void;

interface Signature<
TContext extends MachineContext,
TEvent extends EventObject,
TChildren extends Record<string, AnyActorRef | undefined>,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string,
TStateValue extends StateValue,
TTag extends string,
TInput,
TOutput,
TEmitted extends EventObject,
TMeta extends MetaObject,
TConfig extends StateSchema,
> {
Args: {
config?: TConfig;
context?: TContext;
input?: TInput;
snapshot?: TStateValue;
};
Blocks: {
default: [
machine: {
/**
* Pass a function to this to be called when the machine transitions state
*/
onTransition: (
...params: Parameters<
Actor<
StateMachine<
TContext,
TEvent,
TChildren,
TActor,
TAction,
TGuard,
TDelay,
TStateValue,
TTag,
TInput,
TOutput,
TEmitted,
TMeta,
TConfig
>
>['subscribe']
>
) => void;
/**
* The current snapshot's value.
* Will be a string, or object, depending on state complexity.
*/
value: StateValue;
/**
* The snapshot value (state), as a dot-separated string
*/
statePath: string;
/**
* Send an event to the machine.
* For simple events, passing only a string is allowed as an alias
* for { type: "EVENT_NAME" }
*/
send: typeof send;

/**
* Alias for snapshot.matches,
* returns true of the passed state path is active.
*/
matches: (statePath: string) => boolean;

/**
* The Machine's Snapshot
*/
snapshot: SnapshotFrom<
StateMachine<
TContext,
TEvent,
TChildren,
TActor,
TAction,
TGuard,
TDelay,
TStateValue,
TTag,
TInput,
TOutput,
TEmitted,
TMeta,
TConfig
>
>;
/**
* If specific behavior is needed, or for more type-accurate
* usage in templates, the full actor is exposed here.
*/
actor: Actor<
StateMachine<
TContext,
TEvent,
TChildren,
TActor,
TAction,
TGuard,
TDelay,
TStateValue,
TTag,
TInput,
TOutput,
TEmitted,
TMeta,
TConfig
>
>;
},
];
};
}

// Mixing in ComponentLike to the StateMachine type
interface StateMachine<
TContext extends MachineContext,
Expand All @@ -231,14 +392,15 @@ declare module 'xstate' {
TEmitted extends EventObject,
TMeta extends MetaObject,
TConfig extends StateSchema,
> extends ComponentLike<Signature>,
ActorLogic<
MachineSnapshot<TContext, TEvent, TChildren, TStateValue, TTag, TOutput, TMeta, TConfig>,
TEvent,
TInput,
ActorSystem<any>,
TEmitted
> {
> extends ComponentLike<Signature> {
// ,
// ActorLogic<
// MachineSnapshot<TContext, TEvent, TChildren, TStateValue, TTag, TOutput, TMeta, TConfig>,
// TEvent,
// TInput,
// ActorSystem<any>,
// TEmitted
// >
// Indicator that we've messed with the types comeing from xstate.
// (Also useful for debugging)
__has_been_declaration_merged_from_ember_statechart_component__: string;
Expand Down
83 changes: 72 additions & 11 deletions ember-statechart-component/src/type-tests/component-like.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,82 @@
import 'ember-statechart-component';
import { expectTypeOf } from 'expect-type';
import { createMachine } from 'xstate';
import { createMachine, type MachineContext, type StateSchema, type StateValue } from 'xstate';

import type { ComponentLike } from '@glint/template';

expectTypeOf(
createMachine({
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } },
},
})
).toMatchTypeOf<
const Toggle = createMachine({
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } },
},
});

type SignatureFor<Component> =
Component extends ComponentLike<infer Signature>
? Signature & { extracted: 'signture' }
: { failed: 'failed to extract signature' };

/********************************
*
* Sanity checks so that we know
* testing will actually work
*
* *****************************/
expectTypeOf<typeof Toggle>().toHaveProperty(
'__has_been_declaration_merged_from_ember_statechart_component__'
);
expectTypeOf<SignatureFor<typeof Toggle>>().toHaveProperty('Args');
expectTypeOf<SignatureFor<typeof Toggle>>().toHaveProperty('Blocks');
expectTypeOf<SignatureFor<typeof Toggle>>().toHaveProperty('Element');

/********************************
*
* Did we merge the interface correctly?
*
* *****************************/
expectTypeOf<typeof Toggle>().not.toBeAny();
expectTypeOf<typeof Toggle>().not.toEqualTypeOf<
ComponentLike<{
Args: { invalid: number };
}>
>();

expectTypeOf(Toggle).toMatchTypeOf<
ComponentLike<{
Args: any;
Blocks: {
default: [any, never];
};
}>
>();

expectTypeOf(Toggle).toMatchTypeOf<
ComponentLike<{
Args: any;
Blocks: {
default: [any, any];
default: [{ send: (type: string) => void }];
};
}>
>();

/********************************
*
* Is the Component Signature correct?
*
* *****************************/
expectTypeOf<SignatureFor<typeof Toggle>>().toMatchTypeOf<{
Args: {
config?: StateSchema;
context?: MachineContext;
input?: any;
snapshot?: StateValue;
};
}>();

expectTypeOf<SignatureFor<typeof Toggle>>().toMatchTypeOf<{
Args: {
// @ts-expect-error
config: never;
};
}>();
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ expectTypeOf<ReactiveActorFrom<typeof Toggle>>().not.toBeAny();
expectTypeOf<ReactiveActorFrom<typeof Toggle>>().toHaveProperty('statePath');
expectTypeOf<ReactiveActorFrom<typeof Toggle>>().toHaveProperty('send');

expectTypeOf<ReactiveActorFrom<typeof Toggle>['send']>().not.toBeAny();
expectTypeOf<ReactiveActorFrom<typeof Toggle>['send']>().toMatchTypeOf<(type: string) => void>();
expectTypeOf<ReactiveActorFrom<typeof Toggle>['send']>().toMatchTypeOf<
(event: { type: string }) => void
>();
expectTypeOf<Parameters<ReactiveActorFrom<typeof Toggle>['send']>>().not.toBeAny();
7 changes: 5 additions & 2 deletions ember-statechart-component/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"glint": {
"environment": ["ember-loose", "ember-template-imports"]
},
"include": ["src"],
"include": ["src/**/*"],
"compilerOptions": {
"allowJs": true,
// Don't typecheck addon-shim
Expand Down Expand Up @@ -41,6 +41,9 @@
// Stylelistic
"noPropertyAccessFromIndexSignature": false,

"types": ["../test-app/node_modules/ember-source/types/stable"]
"types": [
"../test-app/node_modules/ember-source/types/stable",
"./src/public-types"
]
}
}
Loading