diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts index 0b63428c24d4..c8038791e8d2 100644 --- a/code/core/src/csf/csf-factories.ts +++ b/code/core/src/csf/csf-factories.ts @@ -2,15 +2,22 @@ import type { Args, ComponentAnnotations, + ComposedStoryFn, NormalizedComponentAnnotations, NormalizedProjectAnnotations, NormalizedStoryAnnotations, + PlayFunction, ProjectAnnotations, Renderer, StoryAnnotations, + StoryContext, } from '@storybook/core/types'; -import { composeConfigs, normalizeProjectAnnotations } from '@storybook/core/preview-api'; +import { + composeConfigs, + composeStory, + normalizeProjectAnnotations, +} from '@storybook/core/preview-api'; export interface Preview { readonly _tag: 'Preview'; @@ -82,12 +89,23 @@ export interface Story { input: StoryAnnotations; composed: NormalizedStoryAnnotations; meta: Meta; + play: PlayFunction; + run: (context?: Partial>>) => Promise; } function defineStory( input: ComponentAnnotations, meta: Meta ): Story { + let composed: ComposedStoryFn; + + const compose = () => { + if (!composed) { + composed = composeStory(input, meta.input, meta.preview.composed); + } + return composed; + }; + return { _tag: 'Story', input, @@ -95,6 +113,12 @@ function defineStory( get composed(): never { throw new Error('Not implemented'); }, + get play() { + return input.play ?? meta.input?.play ?? (async () => {}); + }, + get run() { + return compose().run || (async () => {}); + }, }; } diff --git a/code/renderers/react/src/csf-factories.test.tsx b/code/renderers/react/src/csf-factories.test.tsx index ec564474772f..63a554a35c47 100644 --- a/code/renderers/react/src/csf-factories.test.tsx +++ b/code/renderers/react/src/csf-factories.test.tsx @@ -277,3 +277,53 @@ it('Infer mock function given to args in meta.', () => { }, }); }); + +describe('Composed getters', () => { + const TestButton = () => <>; + + const meta = preview.meta({ + component: TestButton, + args: { label: 'label', onClick: fn(), onRender: () => <>some jsx }, + }); + + it('Composes the play function', async () => { + const spy = fn(); + const Basic = meta.story({ + play: async ({ args }) => { + spy(args); + }, + }); + + await Basic.play(meta.input as any); + + expect(spy).toHaveBeenCalledWith({ + label: 'label', + onClick: expect.any(Function), + onRender: expect.any(Function), + }); + }); + + it('Composes the run function', async () => { + const playSpy = fn(); + const renderSpy = fn(); + const Basic = meta.story({ + play: async ({ args }) => { + playSpy(args); + }, + render: () => { + renderSpy(); + return <>; + }, + }); + + await Basic.run(); + + expect(playSpy).toHaveBeenCalledWith({ + label: 'label', + onClick: expect.any(Function), + onRender: expect.any(Function), + }); + + expect(renderSpy).toHaveBeenCalled(); + }); +});