diff --git a/packages/nova-react-test-utils/src/shared/types.d-test.ts b/packages/nova-react-test-utils/src/shared/types.d-test.ts index 4e16aa8..c047ace 100644 --- a/packages/nova-react-test-utils/src/shared/types.d-test.ts +++ b/packages/nova-react-test-utils/src/shared/types.d-test.ts @@ -8,7 +8,12 @@ type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y : false; import type { StoryObj, Meta } from "@storybook/react"; -import type { StoryObjWithoutFragmentRefs } from "./types"; +import type { + ComponentTypeError, + ParametersError, + ReferenceEntriesError, + StoryObjWithoutFragmentRefs, +} from "./types"; import type { UnknownOperation, WithNovaEnvironment, @@ -30,6 +35,11 @@ type OptionalPropsOnly = { optionalProp?: string; }; +type MultiplePropsFromDecorator = { + propPassedThroughDecorator: unknown; + anotherPropPassedThroughDecorator: unknown; +}; + const Component: React.FC = (_: Props) => null; const ComponentWithOptionalProps: React.FC = ( @@ -40,6 +50,10 @@ const ComponentWithOptionalPropsOnly: React.FC = ( _: OptionalPropsOnly, ) => null; +const ComponentWithMultiplePropsFromDecorator: React.FC< + MultiplePropsFromDecorator +> = (_: MultiplePropsFromDecorator) => null; + const parameters = { novaEnvironment: { query: { @@ -51,6 +65,31 @@ const parameters = { }, } satisfies WithNovaEnvironment; +const parametersWithMultiplePropsCorrect = { + novaEnvironment: { + query: { + __brand: "GraphQLTaggedNode" as const, + }, + referenceEntries: { + propPassedThroughDecorator: (data) => data, + anotherPropPassedThroughDecorator: (data) => data, + }, + }, +} satisfies WithNovaEnvironment; + +const parametersWithMultiplePropsWithRefEntryTypo = { + novaEnvironment: { + query: { + __brand: "GraphQLTaggedNode" as const, + }, + referenceEntries: { + propPassedThroughDecorator: (data) => data, + anothPropPassedThroughDecorator: (data) => data, + someRandomProp: (data) => data, + }, + }, +} satisfies WithNovaEnvironment; + const meta = { component: Component, parameters, @@ -81,6 +120,20 @@ const metaForComponentWithOptionalPropsOnly = { parameters, } satisfies Meta; +const metaWithoutComponent = { + parameters, +} satisfies Meta; + +const metaForMultiplePropsFromDecorator = { + component: ComponentWithMultiplePropsFromDecorator, + parameters: parametersWithMultiplePropsCorrect, +} satisfies Meta; + +const metaWithIncorrectRefEntry = { + component: ComponentWithMultiplePropsFromDecorator, + parameters: parametersWithMultiplePropsWithRefEntryTypo, +} satisfies Meta; + type StoryObjForMeta = StoryObj; type StoryObjWithoutFragmentRefsForMeta = StoryObjWithoutFragmentRefs< typeof meta @@ -125,8 +178,29 @@ const StoryForComponentWithOptionalPropsOnly: StoryObjWithoutFragmentRefs< }, }; -type WithoutDecoratorParamsItShouldBeNever = Expect< - Equal> +const StoryForComponentWithMultiplePropsFromDecorator: StoryObjWithoutFragmentRefs< + typeof metaForMultiplePropsFromDecorator +> = {}; + +type WithoutDecoratorParamsItShouldBeParametersError = Expect< + Equal< + ParametersError, + StoryObjWithoutFragmentRefs + > +>; + +type WithoutComponentItShouldBeComponentError = Expect< + Equal< + ComponentTypeError, + StoryObjWithoutFragmentRefs + > +>; + +type WithTypoInRefEntryItShouldBeReferenceEntriesError = Expect< + Equal< + ReferenceEntriesError<"anothPropPassedThroughDecorator" | "someRandomProp">, + StoryObjWithoutFragmentRefs + > >; type VerifyTypeMatchesWithNoArgsOnMeta = Expect< diff --git a/packages/nova-react-test-utils/src/shared/types.ts b/packages/nova-react-test-utils/src/shared/types.ts index 33651a6..b95aef7 100644 --- a/packages/nova-react-test-utils/src/shared/types.ts +++ b/packages/nova-react-test-utils/src/shared/types.ts @@ -12,6 +12,7 @@ type OptionalArgs = T extends { args?: infer A } : never : never; +// Omits Z from the Storybook "args" field of T type OmitFromArgs = Omit & Simplify< (Record extends Omit, keyof Z> @@ -22,11 +23,31 @@ type OmitFromArgs = Omit & { deep: true } >; +// Check if keys of T are a subset of keys of P. Returns true if they are or union of keys that are not. +type AreKeysSubset = { + [K in keyof T]: K extends keyof P ? never : K; +}[keyof T] extends never + ? true + : { + [K in keyof T]: K extends keyof P ? never : K; + }[keyof T]; + +export type ParametersError = + "❌ Error: The type passed to StoryObjWithoutFragmentRefs needs to have a parameters field with novaEnvironment with referenceEntries field"; + +export type ComponentTypeError = + "❌ Error: The type passed to StoryObjWithoutFragmentRefs needs to have a component field that is a React component"; + +export type ReferenceEntriesError = `❌ Error: The reference entry '${K & + string}' is not a property on the component's props`; + export type StoryObjWithoutFragmentRefs = T extends { - component: infer C; - parameters: { novaEnvironment: { referenceEntries: infer D } }; + component?: infer C; + parameters: { novaEnvironment: { referenceEntries: infer D extends object } }; } - ? C extends React.ComponentType - ? OmitFromArgs, D> - : never - : never; + ? C extends React.ComponentType + ? AreKeysSubset extends true + ? OmitFromArgs, D> + : ReferenceEntriesError> + : ComponentTypeError + : ParametersError;