Skip to content

Commit

Permalink
make types for reference entries stronger and error message more frie…
Browse files Browse the repository at this point in the history
…ndly
  • Loading branch information
Stanislaw Wilczynski authored and Stanislaw Wilczynski committed Oct 24, 2024
1 parent 4b4cfca commit 8b2cf61
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 9 deletions.
80 changes: 77 additions & 3 deletions packages/nova-react-test-utils/src/shared/types.d-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => 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,
Expand All @@ -30,6 +35,11 @@ type OptionalPropsOnly = {
optionalProp?: string;
};

type MultiplePropsFromDecorator = {
propPassedThroughDecorator: unknown;
anotherPropPassedThroughDecorator: unknown;
};

const Component: React.FC<Props> = (_: Props) => null;

const ComponentWithOptionalProps: React.FC<OptionalProps> = (
Expand All @@ -40,6 +50,10 @@ const ComponentWithOptionalPropsOnly: React.FC<OptionalPropsOnly> = (
_: OptionalPropsOnly,
) => null;

const ComponentWithMultiplePropsFromDecorator: React.FC<
MultiplePropsFromDecorator
> = (_: MultiplePropsFromDecorator) => null;

const parameters = {
novaEnvironment: {
query: {
Expand All @@ -51,6 +65,31 @@ const parameters = {
},
} satisfies WithNovaEnvironment<UnknownOperation>;

const parametersWithMultiplePropsCorrect = {
novaEnvironment: {
query: {
__brand: "GraphQLTaggedNode" as const,
},
referenceEntries: {
propPassedThroughDecorator: (data) => data,
anotherPropPassedThroughDecorator: (data) => data,
},
},
} satisfies WithNovaEnvironment<UnknownOperation>;

const parametersWithMultiplePropsWithRefEntryTypo = {
novaEnvironment: {
query: {
__brand: "GraphQLTaggedNode" as const,
},
referenceEntries: {
propPassedThroughDecorator: (data) => data,
anothPropPassedThroughDecorator: (data) => data,
someRandomProp: (data) => data,
},
},
} satisfies WithNovaEnvironment<UnknownOperation>;

const meta = {
component: Component,
parameters,
Expand Down Expand Up @@ -81,6 +120,20 @@ const metaForComponentWithOptionalPropsOnly = {
parameters,
} satisfies Meta<typeof ComponentWithOptionalPropsOnly>;

const metaWithoutComponent = {
parameters,
} satisfies Meta<typeof ComponentWithOptionalPropsOnly>;

const metaForMultiplePropsFromDecorator = {
component: ComponentWithMultiplePropsFromDecorator,
parameters: parametersWithMultiplePropsCorrect,
} satisfies Meta<typeof ComponentWithMultiplePropsFromDecorator>;

const metaWithIncorrectRefEntry = {
component: ComponentWithMultiplePropsFromDecorator,
parameters: parametersWithMultiplePropsWithRefEntryTypo,
} satisfies Meta<typeof ComponentWithMultiplePropsFromDecorator>;

type StoryObjForMeta = StoryObj<typeof meta>;
type StoryObjWithoutFragmentRefsForMeta = StoryObjWithoutFragmentRefs<
typeof meta
Expand Down Expand Up @@ -125,8 +178,29 @@ const StoryForComponentWithOptionalPropsOnly: StoryObjWithoutFragmentRefs<
},
};

type WithoutDecoratorParamsItShouldBeNever = Expect<
Equal<never, StoryObjWithoutFragmentRefs<typeof metaWithoutDecoratorParams>>
const StoryForComponentWithMultiplePropsFromDecorator: StoryObjWithoutFragmentRefs<
typeof metaForMultiplePropsFromDecorator
> = {};

type WithoutDecoratorParamsItShouldBeParametersError = Expect<
Equal<
ParametersError,
StoryObjWithoutFragmentRefs<typeof metaWithoutDecoratorParams>
>
>;

type WithoutComponentItShouldBeComponentError = Expect<
Equal<
ComponentTypeError,
StoryObjWithoutFragmentRefs<typeof metaWithoutComponent>
>
>;

type WithTypoInRefEntryItShouldBeReferenceEntriesError = Expect<
Equal<
ReferenceEntriesError<"anothPropPassedThroughDecorator" | "someRandomProp">,
StoryObjWithoutFragmentRefs<typeof metaWithIncorrectRefEntry>
>
>;

type VerifyTypeMatchesWithNoArgsOnMeta = Expect<
Expand Down
33 changes: 27 additions & 6 deletions packages/nova-react-test-utils/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type OptionalArgs<T> = T extends { args?: infer A }
: never
: never;

// Omits Z from the Storybook "args" field of T
type OmitFromArgs<T, Z> = Omit<T, "args"> &
Simplify<
(Record<string, never> extends Omit<RequiredArgs<T>, keyof Z>
Expand All @@ -22,11 +23,31 @@ type OmitFromArgs<T, Z> = Omit<T, "args"> &
{ 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<T extends object, P extends object> = {
[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<K> = `❌ Error: The reference entry '${K &
string}' is not a property on the component's props`;

export type StoryObjWithoutFragmentRefs<T> = T extends {
component: infer C;
parameters: { novaEnvironment: { referenceEntries: infer D } };
component?: infer C;
parameters: { novaEnvironment: { referenceEntries: infer D extends object } };
}
? C extends React.ComponentType<any>
? OmitFromArgs<StoryObj<T>, D>
: never
: never;
? C extends React.ComponentType<infer P extends object>
? AreKeysSubset<D, P> extends true
? OmitFromArgs<StoryObj<T>, D>
: ReferenceEntriesError<AreKeysSubset<D, P>>
: ComponentTypeError
: ParametersError;

0 comments on commit 8b2cf61

Please sign in to comment.