Skip to content

Commit

Permalink
Merge pull request #28385 from storybookjs/kasper/mount-args
Browse files Browse the repository at this point in the history
Test: Add args to `mount` in react, svelte, and vue renderers
  • Loading branch information
kasperpeulen authored Jul 3, 2024
2 parents 137c344 + 61d0ddf commit 67d9c5b
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 5 deletions.
1 change: 1 addition & 0 deletions code/renderers/react/src/entry-preview.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const parameters: {} = { renderer: 'react' };
export { render } from './render';
export { renderToCanvas } from './renderToCanvas';
export { mount } from './mount';
11 changes: 11 additions & 0 deletions code/renderers/react/src/mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { type StoryContext, type ReactRenderer } from './public-types';
import type { BaseAnnotations } from '@storybook/types';

export const mount: BaseAnnotations<ReactRenderer>['mount'] =
(context: StoryContext) => async (ui) => {
if (ui != null) {
context.originalStoryFn = () => ui;
}
await context.renderToCanvas();
return context.canvas;
};
2 changes: 1 addition & 1 deletion code/renderers/react/src/public-types.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ it('Infer mock function given to args in meta.', () => {

const Basic: Story = {
play: async ({ args, mount }) => {
const canvas = await mount();
const canvas = await mount(<TestButton {...args} />);
expectTypeOf(canvas).toEqualTypeOf<Canvas>();
expectTypeOf(args.onClick).toEqualTypeOf<Mock<[], void>>();
expectTypeOf(args.onRender).toEqualTypeOf<() => JSX.Element>();
Expand Down
3 changes: 2 additions & 1 deletion code/renderers/react/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ComponentType } from 'react';
import type { WebRenderer } from 'storybook/internal/types';
import type { Canvas, WebRenderer } from 'storybook/internal/types';

export type { RenderContext, StoryContext } from 'storybook/internal/types';

export interface ReactRenderer extends WebRenderer {
component: ComponentType<this['T']>;
storyResult: StoryFnReactReturnType;
mount: (ui?: JSX.Element) => Promise<Canvas>;
}

export interface ShowErrorArgs {
Expand Down
20 changes: 20 additions & 0 deletions code/renderers/react/template/stories/mount-in-play.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { FC } from 'react';
import React from 'react';
import type { StoryObj } from '@storybook/react';

const Button: FC<{ label?: string; disabled?: boolean }> = (props) => {
return <button disabled={props.disabled}>{props.label}</button>;
};

export default {
component: Button,
};

export const Basic: StoryObj<typeof Button> = {
args: {
disabled: true,
},
async play({ mount, args }) {
await mount(<Button {...args} label={'set in play'} />);
},
};
1 change: 1 addition & 0 deletions code/renderers/svelte/src/entry-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export const parameters: {} = { renderer: 'svelte' };

export { render, renderToCanvas } from './render';
export { decorateStory as applyDecorators } from './decorators';
export { mount } from './mount';
15 changes: 15 additions & 0 deletions code/renderers/svelte/src/mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type StoryContext, type SvelteRenderer } from './public-types';
import { type BaseAnnotations } from '@storybook/types';

export const mount: BaseAnnotations<SvelteRenderer>['mount'] = (context: StoryContext) => {
return async (Component, options) => {
if (Component) {
context.originalStoryFn = () => ({
Component,
props: options && 'props' in options ? options?.props : options,
});
}
await context.renderToCanvas();
return context.canvas;
};
};
10 changes: 10 additions & 0 deletions code/renderers/svelte/src/public-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,13 @@ describe('Story args can be inferred', () => {
expectTypeOf(Basic).toEqualTypeOf<Expected>();
});
});

it('mount accepts a Component and props', () => {
const Basic: StoryObj<Button> = {
async play({ mount }) {
const canvas = await mount(Button, { label: 'label', disabled: true });
expectTypeOf(canvas).toEqualTypeOf<Canvas>();
},
};
expectTypeOf(Basic).toEqualTypeOf<StoryObj<Button>>();
});
12 changes: 11 additions & 1 deletion code/renderers/svelte/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { StoryContext as StoryContextBase, WebRenderer } from 'storybook/internal/types';
import type {
Canvas,
StoryContext as StoryContextBase,
WebRenderer,
} from 'storybook/internal/types';
import type { ComponentConstructorOptions, ComponentEvents, SvelteComponent } from 'svelte';

export type StoryContext = StoryContextBase<SvelteRenderer>;
Expand Down Expand Up @@ -37,6 +41,12 @@ export interface SvelteRenderer<C extends SvelteComponent = SvelteComponent> ext
storyResult: this['T'] extends Record<string, any>
? SvelteStoryResult<this['T'], ComponentEvents<C>>
: SvelteStoryResult;

mount: (
Component?: ComponentType,
// TODO add proper typesafety
options?: Record<string, any> & { props: Record<string, any> }
) => Promise<Canvas>;
}

export interface SvelteStoryResult<
Expand Down
44 changes: 44 additions & 0 deletions code/renderers/svelte/template/stories/Button.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script>
import { createEventDispatcher } from 'svelte';
/**
* Is this the principal call to action on the page?
*/
export let primary = false;
/**
* What background color to use
*/
export let backgroundColor = undefined;
/**
* How large should the button be?
*/
export let size = 'medium';
/**
* Button contents
*/
export let label = '';
$: mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
$: style = backgroundColor ? `background-color: ${backgroundColor}` : '';
const dispatch = createEventDispatcher();
/**
* Optional click handler
*/
export let onClick = (event) => {
dispatch('click', event);
};
</script>

<button
type="button"
class={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
{style}
on:click={onClick}
>
{label}
</button>
15 changes: 15 additions & 0 deletions code/renderers/svelte/template/stories/mount-in-play.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Button from './Button.svelte';
import type { StoryObj } from '@storybook/svelte';

export default {
component: Button,
};

export const Basic: StoryObj = {
args: {
disabled: true,
},
async play({ mount, args }) {
await mount(Button, { props: { ...args, label: 'set in play' } });
},
};
1 change: 1 addition & 0 deletions code/renderers/vue3/src/entry-preview.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const parameters: {} = { renderer: 'vue3' };
export { render, renderToCanvas } from './render';
export { decorateStory as applyDecorators } from './decorateStory';
export { mount } from './mount';
13 changes: 13 additions & 0 deletions code/renderers/vue3/src/mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type StoryContext, type VueRenderer } from './public-types';
import { h } from 'vue';
import { type BaseAnnotations } from '@storybook/types';

export const mount: BaseAnnotations<VueRenderer>['mount'] = (context: StoryContext) => {
return async (Component, options) => {
if (Component) {
context.originalStoryFn = () => () => h(Component, options?.props, options?.slots);
}
await context.renderToCanvas();
return context.canvas;
};
};
12 changes: 11 additions & 1 deletion code/renderers/vue3/src/public-types.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// this file tests Typescript types that's why there are no assertions
import { describe, it } from 'vitest';
import { satisfies } from 'storybook/internal/common';
import type { ComponentAnnotations, StoryAnnotations } from 'storybook/internal/types';
import type { Canvas, ComponentAnnotations, StoryAnnotations } from 'storybook/internal/types';
import { expectTypeOf } from 'expect-type';
import type { SetOptional } from 'type-fest';
import { h } from 'vue';
Expand Down Expand Up @@ -196,3 +196,13 @@ it('Infer type of slots', () => {
type Expected = StoryAnnotations<VueRenderer, Props, Props>;
expectTypeOf(Basic).toEqualTypeOf<Expected>();
});

it('mount accepts a Component', () => {
const Basic: StoryObj<typeof Button> = {
async play({ mount }) {
const canvas = await mount(Button, { label: 'label', disabled: true });
expectTypeOf(canvas).toEqualTypeOf<Canvas>();
},
};
expectTypeOf(Basic).toEqualTypeOf<StoryObj<typeof Button>>();
});
12 changes: 11 additions & 1 deletion code/renderers/vue3/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { type StoryContext as StoryContextBase, type WebRenderer } from 'storybook/internal/types';
import {
type Canvas,
type StoryContext as StoryContextBase,
type WebRenderer,
} from 'storybook/internal/types';
import type { App, ConcreteComponent } from 'vue';

export type { RenderContext } from 'storybook/internal/types';
Expand All @@ -21,4 +25,10 @@ export interface VueRenderer extends WebRenderer {
// Try not omitting, and check the type errros in the test file, if you want to learn more.
component: Omit<ConcreteComponent<this['T']>, 'props'>;
storyResult: StoryFnVueReturnType;

mount: (
Component?: StoryFnVueReturnType,
// TODO add proper typesafety
options?: { props?: Record<string, any>; slots?: Record<string, any> }
) => Promise<Canvas>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { StoryObj } from '@storybook/vue3';
import { defineComponent } from 'vue';

const Button = defineComponent({
template: '<button :disabled="disabled">{{label}}</button>',
props: ['disabled', 'label'],
});

export default {
component: Button,
};

export const Basic: StoryObj<typeof Button> = {
args: {
disabled: true,
},
async play({ mount, args }) {
await mount(Button, { props: { ...args, label: 'set in play' } });
},
};

0 comments on commit 67d9c5b

Please sign in to comment.