mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:11:53 +08:00
fix: adjust Svelte typings to include Svelte 5 function components
Svelte 5 components that only use runes and none of the legacy stuff (events, slots) are typed as function components. This adjusts the types to include that shape. Fixes #29308
This commit is contained in:
parent
26e8829d09
commit
055410310a
15
code/renderers/svelte/src/__test__/ButtonV5.svelte
Normal file
15
code/renderers/svelte/src/__test__/ButtonV5.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
label: string;
|
||||
clicked?: (e: MouseEvent) => void
|
||||
}
|
||||
|
||||
// When a Svelte 5 component only uses runes, it is typed as a function component.
|
||||
// Others are typed as class components. Hence we need to have tests for both shapes.
|
||||
let { disabled, label, clicked}: Props = $props();
|
||||
</script>
|
||||
|
||||
<button onclick={clicked} {disabled}>
|
||||
{label}
|
||||
</button>
|
@ -5,19 +5,20 @@ import { satisfies } from 'storybook/internal/common';
|
||||
import type { Canvas, ComponentAnnotations, StoryAnnotations } from 'storybook/internal/types';
|
||||
|
||||
import { expectTypeOf } from 'expect-type';
|
||||
import type { ComponentProps, SvelteComponent } from 'svelte';
|
||||
import type { Component, ComponentProps, SvelteComponent } from 'svelte';
|
||||
|
||||
import Button from './__test__/Button.svelte';
|
||||
import ButtonV5 from './__test__/ButtonV5.svelte';
|
||||
import Decorator2 from './__test__/Decorator2.svelte';
|
||||
import Decorator1 from './__test__/Decorator.svelte';
|
||||
import type { Decorator, Meta, StoryObj } from './public-types';
|
||||
import type { SvelteRenderer } from './types';
|
||||
|
||||
type SvelteStory<Component extends SvelteComponent, Args, RequiredArgs> = StoryAnnotations<
|
||||
SvelteRenderer<Component>,
|
||||
type SvelteStory<
|
||||
Comp extends SvelteComponent | Component<any, any, any>,
|
||||
Args,
|
||||
RequiredArgs
|
||||
>;
|
||||
RequiredArgs,
|
||||
> = StoryAnnotations<SvelteRenderer<Comp>, Args, RequiredArgs>;
|
||||
|
||||
describe('Meta', () => {
|
||||
it('Generic parameter of Meta can be a component', () => {
|
||||
@ -34,6 +35,20 @@ describe('Meta', () => {
|
||||
>();
|
||||
});
|
||||
|
||||
it('Generic parameter of Meta can be a Svelte 5 component', () => {
|
||||
const meta: Meta<typeof ButtonV5> = {
|
||||
component: ButtonV5,
|
||||
args: {
|
||||
label: 'good',
|
||||
disabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
expectTypeOf(meta).toMatchTypeOf<
|
||||
ComponentAnnotations<SvelteRenderer<typeof ButtonV5>, { disabled: boolean; label: string }>
|
||||
>();
|
||||
});
|
||||
|
||||
it('Generic parameter of Meta can be the props of the component', () => {
|
||||
const meta: Meta<{ disabled: boolean; label: string }> = {
|
||||
component: Button,
|
||||
@ -99,6 +114,21 @@ describe('StoryObj', () => {
|
||||
expectTypeOf<Actual>().toMatchTypeOf<Expected>();
|
||||
});
|
||||
|
||||
it('✅ Required args may be provided partial in meta and the story (Svelte 5)', () => {
|
||||
const meta = satisfies<Meta<typeof ButtonV5>>()({
|
||||
component: ButtonV5,
|
||||
args: { label: 'good' },
|
||||
});
|
||||
|
||||
type Actual = StoryObj<typeof meta>;
|
||||
type Expected = SvelteStory<
|
||||
typeof ButtonV5,
|
||||
{ disabled: boolean; label: string },
|
||||
{ disabled: boolean; label?: string }
|
||||
>;
|
||||
expectTypeOf<Actual>().toMatchTypeOf<Expected>();
|
||||
});
|
||||
|
||||
it('❌ The combined shape of meta args and story args must match the required args.', () => {
|
||||
{
|
||||
const meta = satisfies<Meta<Button>>()({ component: Button });
|
||||
@ -150,6 +180,16 @@ describe('StoryObj', () => {
|
||||
>
|
||||
>();
|
||||
});
|
||||
|
||||
it('Svelte 5 Component can be used as generic parameter for StoryObj', () => {
|
||||
expectTypeOf<StoryObj<typeof ButtonV5>>().toMatchTypeOf<
|
||||
SvelteStory<
|
||||
typeof ButtonV5,
|
||||
{ disabled: boolean; label: string },
|
||||
{ disabled?: boolean; label?: string }
|
||||
>
|
||||
>();
|
||||
});
|
||||
});
|
||||
|
||||
type ThemeData = 'light' | 'dark';
|
||||
@ -243,3 +283,13 @@ it('mount accepts a Component and props', () => {
|
||||
};
|
||||
expectTypeOf(Basic).toMatchTypeOf<StoryObj<Button>>();
|
||||
});
|
||||
|
||||
it('mount accepts a Svelte 5 Component and props', () => {
|
||||
const Basic: StoryObj<typeof ButtonV5> = {
|
||||
async play({ mount }) {
|
||||
const canvas = await mount(ButtonV5, { props: { label: 'label', disabled: true } });
|
||||
expectTypeOf(canvas).toMatchTypeOf<Canvas>();
|
||||
},
|
||||
};
|
||||
expectTypeOf(Basic).toMatchTypeOf<StoryObj<Button>>();
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ import type {
|
||||
import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte';
|
||||
import type { SetOptional, Simplify } from 'type-fest';
|
||||
|
||||
import type { SvelteRenderer } from './types';
|
||||
import type { Svelte5ComponentType, SvelteRenderer } from './types';
|
||||
|
||||
export type { Args, ArgTypes, Parameters, StrictArgs } from 'storybook/internal/types';
|
||||
|
||||
@ -24,19 +24,22 @@ export type { Args, ArgTypes, Parameters, StrictArgs } from 'storybook/internal/
|
||||
*
|
||||
* @see [Default export](https://storybook.js.org/docs/api/csf#default-export)
|
||||
*/
|
||||
export type Meta<CmpOrArgs = Args> =
|
||||
CmpOrArgs extends SvelteComponent<infer Props>
|
||||
? ComponentAnnotations<SvelteRenderer<CmpOrArgs>, Props>
|
||||
: ComponentAnnotations<SvelteRenderer, CmpOrArgs>;
|
||||
export type Meta<CmpOrArgs = Args> = CmpOrArgs extends
|
||||
| SvelteComponent<infer Props>
|
||||
| Svelte5ComponentType<infer Props>
|
||||
? ComponentAnnotations<SvelteRenderer<CmpOrArgs>, Props>
|
||||
: ComponentAnnotations<SvelteRenderer, CmpOrArgs>;
|
||||
|
||||
/**
|
||||
* Story function that represents a CSFv2 component example.
|
||||
*
|
||||
* @see [Named Story exports](https://storybook.js.org/docs/api/csf#named-story-exports)
|
||||
*/
|
||||
export type StoryFn<TCmpOrArgs = Args> =
|
||||
TCmpOrArgs extends SvelteComponent<infer Props>
|
||||
? AnnotatedStoryFn<SvelteRenderer, Props>
|
||||
: AnnotatedStoryFn<SvelteRenderer, TCmpOrArgs>;
|
||||
export type StoryFn<TCmpOrArgs = Args> = TCmpOrArgs extends
|
||||
| SvelteComponent<infer Props>
|
||||
| Svelte5ComponentType<infer Props>
|
||||
? AnnotatedStoryFn<SvelteRenderer, Props>
|
||||
: AnnotatedStoryFn<SvelteRenderer, TCmpOrArgs>;
|
||||
|
||||
/**
|
||||
* Story object that represents a CSFv3 component example.
|
||||
@ -45,19 +48,32 @@ export type StoryFn<TCmpOrArgs = Args> =
|
||||
*/
|
||||
export type StoryObj<MetaOrCmpOrArgs = Args> = MetaOrCmpOrArgs extends {
|
||||
render?: ArgsStoryFn<SvelteRenderer, any>;
|
||||
component?: ComponentType<infer Component>;
|
||||
component?: infer Comp;
|
||||
args?: infer DefaultArgs;
|
||||
}
|
||||
? Simplify<
|
||||
ComponentProps<Component> & ArgsFromMeta<SvelteRenderer, MetaOrCmpOrArgs>
|
||||
ComponentProps<
|
||||
Comp extends ComponentType<infer Component>
|
||||
? Component
|
||||
: Comp extends Svelte5ComponentType
|
||||
? Comp
|
||||
: never
|
||||
> &
|
||||
ArgsFromMeta<SvelteRenderer, MetaOrCmpOrArgs>
|
||||
> extends infer TArgs
|
||||
? StoryAnnotations<
|
||||
SvelteRenderer<Component>,
|
||||
SvelteRenderer<
|
||||
Comp extends ComponentType<infer Component>
|
||||
? Component
|
||||
: Comp extends Svelte5ComponentType
|
||||
? Comp
|
||||
: never
|
||||
>,
|
||||
TArgs,
|
||||
SetOptional<TArgs, Extract<keyof TArgs, keyof DefaultArgs>>
|
||||
>
|
||||
: never
|
||||
: MetaOrCmpOrArgs extends SvelteComponent
|
||||
: MetaOrCmpOrArgs extends SvelteComponent | Svelte5ComponentType
|
||||
? StoryAnnotations<SvelteRenderer<MetaOrCmpOrArgs>, ComponentProps<MetaOrCmpOrArgs>>
|
||||
: StoryAnnotations<SvelteRenderer, MetaOrCmpOrArgs>;
|
||||
|
||||
|
@ -37,14 +37,19 @@ type ComponentType<
|
||||
>[P];
|
||||
};
|
||||
|
||||
export interface SvelteRenderer<C extends SvelteComponent = SvelteComponent> extends WebRenderer {
|
||||
component: ComponentType<this['T'] extends Record<string, any> ? this['T'] : any>;
|
||||
export type Svelte5ComponentType<Props extends Record<string, any> = any> = (typeof import('svelte') extends { mount: any }
|
||||
? // @ts-ignore svelte.Component doesn't exist in Svelte 4
|
||||
import('svelte').Component<Props, any, any>
|
||||
: never)
|
||||
|
||||
export interface SvelteRenderer<C extends SvelteComponent | Svelte5ComponentType = SvelteComponent> extends WebRenderer {
|
||||
component: ComponentType<this['T'] extends Record<string, any> ? this['T'] : any> | Svelte5ComponentType<this['T'] extends Record<string, any> ? this['T'] : any>;
|
||||
storyResult: this['T'] extends Record<string, any>
|
||||
? SvelteStoryResult<this['T'], ComponentEvents<C>>
|
||||
? SvelteStoryResult<this['T'], C extends SvelteComponent ? ComponentEvents<C> : {}>
|
||||
: SvelteStoryResult;
|
||||
|
||||
mount: (
|
||||
Component?: ComponentType,
|
||||
Component?: ComponentType | Svelte5ComponentType,
|
||||
// TODO add proper typesafety
|
||||
options?: Record<string, any> & { props: Record<string, any> }
|
||||
) => Promise<Canvas>;
|
||||
@ -54,10 +59,10 @@ export interface SvelteStoryResult<
|
||||
Props extends Record<string, any> = any,
|
||||
Events extends Record<string, any> = any,
|
||||
> {
|
||||
Component?: ComponentType<Props>;
|
||||
Component?: ComponentType<Props> | Svelte5ComponentType<Props>;
|
||||
on?: Record<string, any> extends Events
|
||||
? Record<string, (event: CustomEvent) => void>
|
||||
: { [K in keyof Events as string extends K ? never : K]?: (event: Events[K]) => void };
|
||||
props?: Props;
|
||||
decorator?: ComponentType<Props>;
|
||||
decorator?: ComponentType<Props> | Svelte5ComponentType<Props>;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user