From 0eec9bec43edf96a7d958b7ef96dff6b0e09fc4d Mon Sep 17 00:00:00 2001 From: xeho91 Date: Sat, 15 Jun 2024 15:34:47 +0800 Subject: [PATCH] relax some types, add more tests & bring closer to final goal --- src/index.test.ts | 43 +++++++++++++++------ src/index.ts | 31 ++++++++------- src/runtime/Story.svelte | 20 +++++----- src/runtime/contexts/extractor.svelte.ts | 2 +- src/runtime/contexts/renderer.svelte.ts | 4 +- src/types.test.ts | 17 +++++--- src/types.ts | 49 +++++++++--------------- 7 files changed, 92 insertions(+), 74 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index d145934..8c84d5e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -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>(); + expectTypeOf(Story).toMatchTypeOf>(); + expectTypeOf(meta).toMatchTypeOf>>(); }); it('works with provided "component" entry', () => { - const { Story, meta } = defineMeta>({ + const { Story, meta } = defineMeta({ component: Button, args: { // FIXME: allow mapping snippets to primitives @@ -30,10 +29,32 @@ describe(defineMeta.name, () => { }); expectTypeOf(Button).toMatchTypeOf>>(); - expectTypeOf(Story).toMatchTypeOf>>(); + expectTypeOf(Story).toMatchTypeOf>>(); + }); +}); + +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>().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>().toMatchTypeOf< + BaseStoryContext<(typeof meta)['args']> + >(); +}); diff --git a/src/index.ts b/src/index.ts index 71af6a7..83b84c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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(meta: TMeta) { return { - Story: Story as StoryCmp, - meta, + Story: Story as StoryCmp, + meta: meta as TMeta extends { component?: infer TCmp } + ? TCmp extends Component | SvelteComponent | __sveltets_2_IsomorphicComponent + ? Meta + : never + : TMeta extends { args?: infer TArgs } + ? TArgs extends BaseArgs + ? Meta> + : never + : never, }; } -export type Args['Story']> = - Component extends typeof Story +export type Args> = + Component extends StoryCmp ? TMeta['args'] : never; -export type StoryContext['Story']> = - Component extends typeof Story - ? BaseStoryContext +export type StoryContext> = + Component extends StoryCmp + ? BaseStoryContext : never; diff --git a/src/runtime/Story.svelte b/src/runtime/Story.svelte index 03b0243..a48dc12 100644 --- a/src/runtime/Story.svelte +++ b/src/runtime/Story.svelte @@ -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 + ? SvelteComponent + : TMeta['args'] extends Args + ? SvelteComponent + : SvelteComponent >; type Annotations = StoryAnnotations< @@ -38,7 +40,7 @@ : Args >; - type Props = Annotations & { + type Props = Partial & { /** * The content to render in the story, either as: * 1. A snippet taking args and storyContext as parameters @@ -83,16 +85,16 @@ const { children, name, exportName: exportNameProp, play, ...restProps }: Props = $props(); const exportName = exportNameProp ?? storyNameToExportName(name!); - const extractor = useStoriesExtractor(); - const renderer = useStoryRenderer(); - const template = useStoriesTemplate(); + 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( diff --git a/src/runtime/contexts/extractor.svelte.ts b/src/runtime/contexts/extractor.svelte.ts index 0202747..f76e68c 100644 --- a/src/runtime/contexts/extractor.svelte.ts +++ b/src/runtime/contexts/extractor.svelte.ts @@ -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'; diff --git a/src/runtime/contexts/renderer.svelte.ts b/src/runtime/contexts/renderer.svelte.ts index ed77d56..897dd40 100644 --- a/src/runtime/contexts/renderer.svelte.ts +++ b/src/runtime/contexts/renderer.svelte.ts @@ -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 { currentStoryExportName: string | undefined; - args: Story['args']; + args: Meta['args']; storyContext: StoryContext; } diff --git a/src/types.test.ts b/src/types.test.ts index d40b111..6120203 100644 --- a/src/types.test.ts +++ b/src/types.test.ts @@ -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'; @@ -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