Skip to content

Commit

Permalink
relax some types, add more tests & bring closer to final goal
Browse files Browse the repository at this point in the history
  • Loading branch information
xeho91 committed Jun 15, 2024
1 parent cf95c43 commit 0eec9be
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 74 deletions.
43 changes: 32 additions & 11 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@ import type { Component, ComponentProps, Snippet } from 'svelte';
import type { EmptyObject } from 'type-fest';
import { describe, expectTypeOf, it } from 'vitest';

import { defineMeta } from '#index';

import type StoryCmp from './runtime/Story.svelte';
import { defineMeta, type Args, type StoryContext } from '#index';
import type { Meta, StoryCmp, StoryContext as BaseStoryContext } from '#types';

import Button from '../examples/components/Button.svelte';
import type { Meta } from '#types';

describe(defineMeta.name, () => {
it('works when no "component" entry is provided', () => {
it('works when no meta entry "component" is provided', () => {
const { Story, meta } = defineMeta({
args: {
children: 'Click me',
sample: 0,
},
});

expectTypeOf(Story).toMatchTypeOf<typeof StoryCmp<EmptyObject, typeof meta>>();
expectTypeOf(Story).toMatchTypeOf<StoryCmp<EmptyObject, typeof meta>>();
expectTypeOf(meta).toMatchTypeOf<Meta<Component<{ sample: 0 }>>>();
});

it('works with provided "component" entry', () => {
const { Story, meta } = defineMeta<EmptyObject, Meta<Button>>({
const { Story, meta } = defineMeta({
component: Button,
args: {
// FIXME: allow mapping snippets to primitives
Expand All @@ -30,10 +29,32 @@ describe(defineMeta.name, () => {
});

expectTypeOf(Button).toMatchTypeOf<Component<ComponentProps<Button>>>();
expectTypeOf(Story).toMatchTypeOf<typeof StoryCmp<EmptyObject, Meta<Button>>>();
expectTypeOf(Story).toMatchTypeOf<StoryCmp<EmptyObject, Meta<Button>>>();
});
});

describe("type helper for snippets 'Args'", () => {
const { Story, meta } = defineMeta({
component: Button,
args: {
// FIXME: allow mapping snippets to primitives
children: 'Click me' as unknown as Snippet,
},
});

expectTypeOf<Args<typeof Story>>().toMatchTypeOf<(typeof meta)['args']>();
});

describe.todo('type helper Args');
describe("type helper for snippets 'StoryContext'", () => {
const { Story, meta } = defineMeta({
component: Button,
args: {
// FIXME: allow mapping snippets to primitives
children: 'Click me' as unknown as Snippet,
},
});

describe.todo('type helper StoryContext');
expectTypeOf<StoryContext<typeof Story>>().toMatchTypeOf<
BaseStoryContext<(typeof meta)['args']>
>();
});
31 changes: 18 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import type { Args as BaseArgs, StoryContext as BaseStoryContext } from '@storybook/types';
import type { Args as BaseArgs } from '@storybook/types';
import type { EmptyObject } from 'type-fest';

import type { Meta, StoryCmp, SvelteRenderer } from '#types';
import type { Meta, StoryCmp, StoryContext as BaseStoryContext } from '#types';

import Story from './runtime/Story.svelte';
import type { Component, SvelteComponent } from 'svelte';

export { setTemplate } from './runtime/contexts/template.svelte';

export function defineMeta<
const TOverrideArgs extends BaseArgs = EmptyObject,
const TMeta extends Meta = Meta,
>(meta: TMeta) {
export function defineMeta<const TMeta extends Meta = Meta>(meta: TMeta) {
return {
Story: Story as StoryCmp<TOverrideArgs, TMeta>,
meta,
Story: Story as StoryCmp<EmptyObject, TMeta>,
meta: meta as TMeta extends { component?: infer TCmp }
? TCmp extends Component | SvelteComponent | __sveltets_2_IsomorphicComponent
? Meta<TCmp>
: never
: TMeta extends { args?: infer TArgs }
? TArgs extends BaseArgs
? Meta<Component<TArgs>>
: never
: never,
};
}

export type Args<Component extends ReturnType<typeof defineMeta>['Story']> =
Component extends typeof Story<infer _TOverrideArgs extends BaseArgs, infer TMeta extends Meta>
export type Args<Component extends StoryCmp<any, any>> =
Component extends StoryCmp<infer _TOverrideArgs extends BaseArgs, infer TMeta extends Meta>
? TMeta['args']
: never;

export type StoryContext<Component extends ReturnType<typeof defineMeta>['Story']> =
Component extends typeof Story<infer _TOverrideArgs extends BaseArgs, infer TMeta extends Meta>
? BaseStoryContext<SvelteRenderer, TMeta['args']>
export type StoryContext<Component extends StoryCmp<any, any>> =
Component extends StoryCmp<infer _TOverrideArgs extends BaseArgs, infer TMeta extends Meta>
? BaseStoryContext<TMeta['args']>
: never;
20 changes: 11 additions & 9 deletions src/runtime/Story.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
import { useStoriesTemplate } from '#runtime/contexts/template.svelte';
import { storyNameToExportName } from '#utils/identifier-utils';
import type { Meta, SvelteRenderer } from '#types';
import type { Meta, StoryCmpProps, SvelteRenderer } from '#types';
type Renderer = SvelteRenderer<
TMeta['component'] extends SvelteComponent
? TMeta['component']
: TMeta['args'] extends Args
? TMeta['args']
: Args
: TMeta['component'] extends Component<infer Props>
? SvelteComponent<Props>
: TMeta['args'] extends Args
? SvelteComponent<TMeta['args']>
: SvelteComponent<Args>
>;
type Annotations = StoryAnnotations<
Expand All @@ -38,7 +40,7 @@
: Args
>;
type Props = Annotations & {
type Props = Partial<Annotations> & {
/**
* The content to render in the story, either as:
* 1. A snippet taking args and storyContext as parameters
Expand Down Expand Up @@ -83,16 +85,16 @@
const { children, name, exportName: exportNameProp, play, ...restProps }: Props = $props();
const exportName = exportNameProp ?? storyNameToExportName(name!);
const extractor = useStoriesExtractor<Props>();
const renderer = useStoryRenderer<TMeta>();
const template = useStoriesTemplate<TMeta>();
const extractor = useStoriesExtractor();
const renderer = useStoryRenderer();
const template = useStoriesTemplate();
const isCurrentlyViewed = $derived(
!extractor.isExtracting && renderer.currentStoryExportName === exportName
);
if (extractor.isExtracting) {
extractor.register({ ...restProps, exportName, play, children } as unknown as Props);
extractor.register({ ...restProps, exportName, play, children } as unknown as StoryCmpProps);
}
function injectIntoPlayFunction(
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/contexts/extractor.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getContext, hasContext, setContext, type ComponentProps } from 'svelte';
import { getContext, hasContext, setContext } from 'svelte';

import { storyNameToExportName } from '#utils/identifier-utils';
import type { StoryCmpProps } from '#types';
Expand Down
4 changes: 1 addition & 3 deletions src/runtime/contexts/renderer.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import { getContext, hasContext, setContext } from 'svelte';

import type { Meta, StoryContext } from '#types';

import type Story from '../Story.svelte';

const CONTEXT_KEY = 'storybook-story-renderer-context';

interface ContextProps<TMeta extends Meta = Meta> {
currentStoryExportName: string | undefined;
args: Story['args'];
args: Meta['args'];
storyContext: StoryContext<TMeta['args']>;
}

Expand Down
17 changes: 11 additions & 6 deletions src/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentAnnotations } from '@storybook/types';
import type { Component, ComponentProps, Snippet } from 'svelte';
import type { ComponentAnnotations, PlayFunctionContext } from '@storybook/types';
import type { ComponentProps, Snippet, SvelteComponent } from 'svelte';
import { describe, expectTypeOf, it } from 'vitest';

import Button from '../examples/components/Button.svelte';
Expand Down Expand Up @@ -33,15 +33,15 @@ describe('Meta', () => {
component: Button,
// FIXME: allow mapping snippets to primitives
args: { children: 'good' as unknown as Snippet, disabled: false },
} satisfies Meta<{ disabled: boolean; children: Snippet }>;
} satisfies Meta<Button>;

expectTypeOf(meta).toMatchTypeOf<Meta<{ disabled: boolean; children: Snippet }>>();
expectTypeOf(meta).toMatchTypeOf<Meta<Button>>();
expectTypeOf(meta).toMatchTypeOf<
ComponentAnnotations<
// Renderer
SvelteRenderer<Component<{ disabled: boolean; children: Snippet }>>,
SvelteRenderer<Button>,
// Args
{ disabled: boolean; children: Snippet }
{ disabled: false; children: Snippet }
>
>();
});
Expand All @@ -57,6 +57,11 @@ describe('Meta', () => {
>();
},
},
play: (context) => {
expectTypeOf(context).toMatchTypeOf<
PlayFunctionContext<SvelteRenderer<SvelteComponent<ComponentProps<Button>>>>
>();
},
} satisfies Meta<Button>;

expectTypeOf(meta).toMatchTypeOf<Meta<Button>>();
Expand Down
49 changes: 18 additions & 31 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,26 @@ type MapSnippetsToAcceptPrimitives<Props extends ComponentProps<Component>> = {
*
* @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export)
*/
export type Meta<CmpOrArgs extends Component | SvelteComponent | Args = Args> =
CmpOrArgs extends Component<infer Props>
? ComponentAnnotations<SvelteRenderer<CmpOrArgs>, Props>
: CmpOrArgs extends SvelteComponent<infer Props>
? ComponentAnnotations<SvelteRenderer<CmpOrArgs>, Props>
: ComponentAnnotations<SvelteRenderer<CmpOrArgs>, CmpOrArgs>;

export interface SvelteRenderer<CmpOrArgs extends Component | SvelteComponent | Args = Args>
extends WebRenderer {
component: CmpOrArgs extends SvelteComponent<infer Props>
? Component<Props>
: CmpOrArgs extends Component<infer Props>
? Component<Props>
: Component<CmpOrArgs>;
storyResult: SvelteStoryResult<CmpOrArgs>;
export type Meta<TCmp = any> = TCmp extends
| Component
| SvelteComponent
| __sveltets_2_IsomorphicComponent
? ComponentAnnotations<SvelteRenderer<TCmp>, ComponentProps<TCmp>>
: never;

export interface SvelteRenderer<TCmp = any> extends WebRenderer {
component: TCmp | Component | SvelteComponent | __sveltets_2_IsomorphicComponent;
storyResult: SvelteStoryResult<TCmp>;
}

export interface SvelteStoryResult<CmpOrArgs extends Component | SvelteComponent | Args = Args> {
Component: CmpOrArgs extends Component<infer Props>
? Component<Props>
: CmpOrArgs extends SvelteComponent<infer Props>
? Component<Props>
: Component<CmpOrArgs>;
props: CmpOrArgs extends Component<infer Props>
? Props
: CmpOrArgs extends SvelteComponent<infer Props>
? Props
: CmpOrArgs;
decorator?: CmpOrArgs extends Component<infer Props>
? Component<Props>
: CmpOrArgs extends SvelteComponent<infer Props>
? Component<Props>
: Component<CmpOrArgs>;
export interface SvelteStoryResult<TCmp = any> {
Component: TCmp;
props: TCmp extends Component<infer TProps>
? TProps
: TCmp extends SvelteComponent<infer TProps>
? TProps
: never;
decorator?: TCmp;
}

export type StoryContext<TArgs = StrictArgs> = GenericStoryContext<SvelteRenderer, TArgs>;
Expand Down

0 comments on commit 0eec9be

Please sign in to comment.