diff --git a/CHANGELOG.md b/CHANGELOG.md index 444fe7ae0be..bdf5324fe23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 8.2.7 + +- CPC: Fix type usage in renderers - [#28745](https://github.com/storybookjs/storybook/pull/28745), thanks @ndelangen! +- Core: Introduce run over play in portable stories, and revert back play changes of 8.2 - [#28764](https://github.com/storybookjs/storybook/pull/28764), thanks @kasperpeulen! + ## 8.2.6 - CPC: Fix missing exports for addon-kit - [#28691](https://github.com/storybookjs/storybook/pull/28691), thanks @ndelangen! diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 37893aa4c03..6563cb23fe6 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -89,25 +89,8 @@ export function composeStory( - composeConfigs([ - { - ...defaultConfig, - renderToCanvas: fallback ? undefined : defaultConfig?.renderToCanvas, - }, - globalProjectAnnotations, - projectAnnotations ?? {}, - ]) + composeConfigs([defaultConfig ?? {}, globalProjectAnnotations, projectAnnotations ?? {}]) ); const story = prepareStory( @@ -130,7 +113,7 @@ export function composeStory story.runStep(label, play, context), - canvasElement: globalThis?.document?.body, + canvasElement: null!, canvas: {} as Canvas, ...story, context: null!, @@ -149,6 +132,7 @@ export function composeStory {}, showError: (error) => {}, showException: (error) => {}, forceRemount: true, @@ -171,28 +155,23 @@ export function composeStory | undefined; - // TODO: Remove in 9.0 - const backwardsCompatiblePlay = async ( - extraContext?: Partial>> - ) => { + const play = async (extraContext?: Partial>>) => { const context = initializeContext(); + context.canvasElement ??= globalThis?.document?.body; if (loadedContext) { context.loaded = loadedContext.loaded; } Object.assign(context, extraContext); return story.playFunction!(context); }; - const newPlay = (extraContext?: Partial>>) => { + + const run = (extraContext?: Partial>>) => { const context = initializeContext(); Object.assign(context, extraContext); - return playStory(story, context); + return runStory(story, context); }; - const playFunction = - !story.renderToCanvas && story.playFunction - ? backwardsCompatiblePlay - : !story.renderToCanvas && !story.playFunction - ? undefined - : newPlay; + + const playFunction = story.playFunction ? play : undefined; const composedStory: ComposedStoryFn> = Object.assign( function storyFn(extraArgs?: Partial) { @@ -226,6 +205,7 @@ export function composeStory, play: playFunction!, + run, tags: story.tags, } ); @@ -325,13 +305,24 @@ export function createPlaywrightTest( // TODO At some point this function should live in prepareStory and become the core of StoryRender.render as well. // Will make a follow up PR for that -async function playStory( +async function runStory( story: PreparedStory, context: StoryContext ) { for (const callback of [...cleanups].reverse()) await callback(); cleanups.length = 0; + if (!context.canvasElement) { + const container = document.createElement('div'); + globalThis?.document?.body?.appendChild(container); + context.canvasElement = container; + cleanups.push(() => { + if (globalThis?.document?.body?.contains(container)) { + globalThis?.document?.body?.removeChild(container); + } + }); + } + context.loaded = await story.applyLoaders(context); if (context.abortSignal.aborted) return; diff --git a/code/core/src/types/modules/composedStory.ts b/code/core/src/types/modules/composedStory.ts index c3c613ec6b1..ade2c302a99 100644 --- a/code/core/src/types/modules/composedStory.ts +++ b/code/core/src/types/modules/composedStory.ts @@ -44,7 +44,8 @@ export type ComposedStoryFn< > = PartialArgsStoryFn & { args: TArgs; id: StoryId; - play: (context?: Partial>>) => Promise; + play?: (context?: Partial>>) => Promise; + run: (context?: Partial>>) => Promise; load: () => Promise; storyName: string; parameters: Parameters; diff --git a/code/package.json b/code/package.json index 7a0bbc2eab2..fb86a12a1af 100644 --- a/code/package.json +++ b/code/package.json @@ -278,5 +278,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.2.7" } diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index 6a0f096f868..8e17288243d 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -53,7 +53,7 @@ describe('renders', () => { }); it('should render component mounted in play function', async () => { - await MountInPlayFunction.play(); + await MountInPlayFunction.run(); expect(screen.getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(screen.getByTestId('loaded-data').textContent).toEqual('loaded data'); @@ -65,7 +65,7 @@ describe('renders', () => { expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); // spy assertions happen in the play function and should work - await LoaderStory.play!(); + await LoaderStory.run!(); }); }); @@ -125,7 +125,7 @@ describe('CSF3', () => { it('renders with play function without canvas element', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - await CSF3InputFieldFilled.play(); + await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -138,7 +138,7 @@ describe('CSF3', () => { console.log(div.tagName); document.body.appendChild(div); - await CSF3InputFieldFilled.play({ canvasElement: div }); + await CSF3InputFieldFilled.run({ canvasElement: div }); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -185,6 +185,6 @@ const testCases = Object.values(composeStories(stories)).map( ); it.each(testCases)('Renders %s story', async (_storyName, Story) => { if (_storyName === 'CSF2StoryWithLocale') return; - await Story.play(); + await Story.run(); expect(document.body).toMatchSnapshot(); }); diff --git a/code/renderers/react/src/mount.ts b/code/renderers/react/src/mount.ts index 13db1f03a0d..d27e66809a5 100644 --- a/code/renderers/react/src/mount.ts +++ b/code/renderers/react/src/mount.ts @@ -1,5 +1,5 @@ import { type StoryContext, type ReactRenderer } from './public-types'; -import type { BaseAnnotations } from '@storybook/types'; +import type { BaseAnnotations } from 'storybook/internal/types'; export const mount: BaseAnnotations['mount'] = (context: StoryContext) => async (ui) => { diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx index b8058deb457..88d6e5382ae 100644 --- a/code/renderers/react/src/portable-stories.tsx +++ b/code/renderers/react/src/portable-stories.tsx @@ -45,11 +45,16 @@ export function setProjectAnnotations( // This will not be necessary once we have auto preset loading export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = { ...reactProjectAnnotations, - renderToCanvas: ({ - storyContext: { context, unboundStoryFn: Story, testingLibraryRender: render, canvasElement }, - }) => { - if (render == null) throw new TestingLibraryMustBeConfiguredError(); - const { unmount } = render(, { baseElement: context.canvasElement }); + renderToCanvas: (renderContext, canvasElement) => { + if (renderContext.storyContext.testingLibraryRender == null) { + throw new TestingLibraryMustBeConfiguredError(); + // Enable for 8.3 + // return reactProjectAnnotations.renderToCanvas(renderContext, canvasElement); + } + const { + storyContext: { context, unboundStoryFn: Story, testingLibraryRender: render }, + } = renderContext; + const { unmount } = render(, { container: context.canvasElement }); return unmount; }, }; diff --git a/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap b/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap index 6b75dc71eb3..4cef8820fbc 100644 --- a/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap +++ b/code/renderers/svelte/src/__test__/composeStories/__snapshots__/portable-stories.test.ts.snap @@ -2,82 +2,48 @@ exports[`Renders CSF2Secondary story 1`] = ` - - - - - -`; - -exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` - - - - -
+
- - +
- - - `; -exports[`Renders CSF3Button story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` - - - - - - - + + +
- - + + + `; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders CSF3Button story 1`] = ` - - - - - - -
-

- I am a custom render function -

- - + +
+ +`; + +exports[`Renders CSF3ButtonWithRender story 1`] = ` + +
+
+

+ I am a custom render function +

+ + + +
+
- `; exports[`Renders CSF3InputFieldFilled story 1`] = ` - - - - - - - - - - +
+ + +
`; exports[`Renders CSF3Primary story 1`] = ` - - - - - - - +
+ + +
`; exports[`Renders LoaderStory story 1`] = ` - - - - - - - - -
-
- loaded data -
- -
- mockFn return value +
+
+ loaded data +
+ +
+ mockFn return value +
+
- `; exports[`Renders NewStory story 1`] = ` - - - - -
- - + +
+
- - - `; diff --git a/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts b/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts index 8bff1514450..fa21d4967e3 100644 --- a/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts +++ b/code/renderers/svelte/src/__test__/composeStories/portable-stories.test.ts @@ -70,7 +70,7 @@ describe('renders', () => { expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); // spy assertions happen in the play function and should work - await LoaderStory.play!(); + await LoaderStory.run!(); }); }); @@ -120,7 +120,7 @@ describe('CSF3', () => { it('renders with play function without canvas element', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - await CSF3InputFieldFilled.play(); + await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -132,7 +132,7 @@ describe('CSF3', () => { const div = document.createElement('div'); document.body.appendChild(div); - await CSF3InputFieldFilled.play({ canvasElement: div }); + await CSF3InputFieldFilled.run({ canvasElement: div }); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -171,6 +171,6 @@ const testCases = Object.values(composeStories(stories)).map( ); it.each(testCases)('Renders %s story', async (_storyName, Story) => { if (_storyName === 'CSF2StoryWithLocale') return; - await Story.play(); + await Story.run(); expect(document.body).toMatchSnapshot(); }); diff --git a/code/renderers/svelte/src/mount.ts b/code/renderers/svelte/src/mount.ts index 5730f4cce40..50d17f81181 100644 --- a/code/renderers/svelte/src/mount.ts +++ b/code/renderers/svelte/src/mount.ts @@ -1,5 +1,5 @@ import { type StoryContext, type SvelteRenderer } from './public-types'; -import { type BaseAnnotations } from '@storybook/types'; +import { type BaseAnnotations } from 'storybook/internal/types'; export const mount: BaseAnnotations['mount'] = (context: StoryContext) => { return async (Component, options) => { diff --git a/code/renderers/svelte/src/portable-stories.ts b/code/renderers/svelte/src/portable-stories.ts index a3c2e54336e..937d490edf1 100644 --- a/code/renderers/svelte/src/portable-stories.ts +++ b/code/renderers/svelte/src/portable-stories.ts @@ -63,8 +63,16 @@ export function setProjectAnnotations( // This will not be necessary once we have auto preset loading export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = { ...svelteProjectAnnotations, - renderToCanvas: ({ storyFn, storyContext: { testingLibraryRender: render, canvasElement } }) => { - if (render == null) throw new TestingLibraryMustBeConfiguredError(); + renderToCanvas: (renderContext, canvasElement) => { + if (renderContext.storyContext.testingLibraryRender == null) { + throw new TestingLibraryMustBeConfiguredError(); + // Enable for 8.3 + // return svelteProjectAnnotations.renderToCanvas(renderContext, canvasElement); + } + const { + storyFn, + storyContext: { testingLibraryRender: render }, + } = renderContext; const { Component, props } = storyFn(); const { unmount } = render(Component, { props, target: canvasElement }); return unmount; diff --git a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts index f0d644ee8d0..359baf7300c 100644 --- a/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts +++ b/code/renderers/vue3/src/__tests__/composeStories/portable-stories.test.ts @@ -52,7 +52,7 @@ describe('renders', () => { expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); // spy assertions happen in the play function and should work - await LoaderStory.play!(); + await LoaderStory.run!(); }); }); @@ -103,7 +103,7 @@ describe('CSF3', () => { it('renders with play function', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - await CSF3InputFieldFilled.play(); + await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -145,7 +145,7 @@ describe('ComposeStories types', () => { const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); it.each(testCases)('Renders %s story', async (_storyName, Story) => { if (typeof Story === 'string' || _storyName === 'CSF2StoryWithLocale') return; - await Story.play(); + await Story.run(); await new Promise((resolve) => setTimeout(resolve, 0)); expect(document.body).toMatchSnapshot(); }); diff --git a/code/renderers/vue3/src/mount.ts b/code/renderers/vue3/src/mount.ts index 73bc7a3326a..8cd6190dab8 100644 --- a/code/renderers/vue3/src/mount.ts +++ b/code/renderers/vue3/src/mount.ts @@ -1,6 +1,6 @@ import { type StoryContext, type VueRenderer } from './public-types'; import { h } from 'vue'; -import { type BaseAnnotations } from '@storybook/types'; +import { type BaseAnnotations } from 'storybook/internal/types'; export const mount: BaseAnnotations['mount'] = (context: StoryContext) => { return async (Component, options) => { diff --git a/code/renderers/vue3/src/portable-stories.ts b/code/renderers/vue3/src/portable-stories.ts index 7e1e64c6f45..13a34728ad9 100644 --- a/code/renderers/vue3/src/portable-stories.ts +++ b/code/renderers/vue3/src/portable-stories.ts @@ -53,9 +53,17 @@ export function setProjectAnnotations( // This will not be necessary once we have auto preset loading export const vueProjectAnnotations: ProjectAnnotations = { ...defaultProjectAnnotations, - renderToCanvas: ({ storyFn, storyContext: { testingLibraryRender: render, canvasElement } }) => { - if (render == null) throw new TestingLibraryMustBeConfiguredError(); - const { unmount } = render(storyFn(), { baseElement: canvasElement }); + renderToCanvas: (renderContext, canvasElement) => { + if (renderContext.storyContext.testingLibraryRender == null) { + throw new TestingLibraryMustBeConfiguredError(); + // Enable for 8.3 + // return defaultProjectAnnotations.renderToCanvas(renderContext, canvasElement); + } + const { + storyFn, + storyContext: { testingLibraryRender: render }, + } = renderContext; + const { unmount } = render(storyFn(), { container: canvasElement }); return unmount; }, }; diff --git a/code/yarn.lock b/code/yarn.lock index 5294d21d2da..7790793e651 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -3016,7 +3016,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.15, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.10.2": version: 7.24.4 resolution: "@babel/runtime@npm:7.24.4" dependencies: @@ -3025,6 +3025,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.15, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.24.7 + resolution: "@babel/runtime@npm:7.24.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/b6fa3ec61a53402f3c1d75f4d808f48b35e0dfae0ec8e2bb5c6fc79fb95935da75766e0ca534d0f1c84871f6ae0d2ebdd950727cfadb745a2cdbef13faef5513 + languageName: node + linkType: hard + "@babel/runtime@npm:~7.5.4": version: 7.5.5 resolution: "@babel/runtime@npm:7.5.5" diff --git a/docs/_assets/api/story-pipeline.png b/docs/_assets/api/story-pipeline.png index 7e3932de10e..f709265f213 100644 Binary files a/docs/_assets/api/story-pipeline.png and b/docs/_assets/api/story-pipeline.png differ diff --git a/docs/_snippets/button-snapshot-test-portable-stories.md b/docs/_snippets/button-snapshot-test-portable-stories.md index 3afb7bdac9e..bdf487cfc1e 100644 --- a/docs/_snippets/button-snapshot-test-portable-stories.md +++ b/docs/_snippets/button-snapshot-test-portable-stories.md @@ -5,7 +5,7 @@ import * as stories from '../stories/Button.stories'; const { Primary } = composeStories(stories); test('Button snapshot', async () => { - await Primary.play(); + await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` @@ -21,7 +21,7 @@ import * as stories from '../stories/Button.stories'; const { Primary } = composeStories(stories); test('Button snapshot', async () => { - await Primary.play(); + await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` @@ -37,7 +37,7 @@ import * as stories from '../stories/Button.stories'; const { Primary } = composeStories(stories); test('Button snapshot', async () => { - await Primary.play(); + await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` @@ -53,7 +53,7 @@ import * as stories from '../stories/Button.stories'; const { Primary } = composeStories(stories); test('Button snapshot', async () => { - await Primary.play(); + await Primary.run(); expect(document.body.firstChild).toMatchSnapshot(); }); ``` diff --git a/docs/_snippets/component-test-with-testing-library.md b/docs/_snippets/component-test-with-testing-library.md index a8fd8558dde..0ac1e206d6b 100644 --- a/docs/_snippets/component-test-with-testing-library.md +++ b/docs/_snippets/component-test-with-testing-library.md @@ -47,7 +47,7 @@ const { InvalidForm } = composeStories(stories); test('Checks if the form is valid', async () => { // Renders the composed story - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -71,7 +71,7 @@ const { InvalidForm } = composeStories(stories); test('Checks if the form is valid', async () => { // Renders the composed story - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -95,7 +95,7 @@ const { InvalidForm } = composeStories(stories); it('Checks if the form is valid', async () => { // Renders the composed story - await InvalidForm.play(); + await InvalidForm.run(); await fireEvent.click(screen.getByText('Submit')); @@ -115,7 +115,7 @@ const { InvalidForm } = composeStories(stories); test('Checks if the form is valid', async () => { // Renders the composed story - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -139,7 +139,7 @@ const { InvalidForm } = composeStories(stories); test('Checks if the form is valid', async () => { // Renders the composed story - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', diff --git a/docs/_snippets/individual-snapshot-tests-portable-stories.md b/docs/_snippets/individual-snapshot-tests-portable-stories.md index 6a51eb12544..14490c429f1 100644 --- a/docs/_snippets/individual-snapshot-tests-portable-stories.md +++ b/docs/_snippets/individual-snapshot-tests-portable-stories.md @@ -51,7 +51,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); // Defines the custom snapshot path location and file name @@ -131,7 +131,7 @@ describe("Stories Snapshots", () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); // Defines the custom snapshot path location and file name @@ -191,7 +191,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); // Defines the custom snapshot path location and file name @@ -262,7 +262,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); // Defines the custom snapshot path location and file name diff --git a/docs/_snippets/multiple-stories-test.md b/docs/_snippets/multiple-stories-test.md index 19b6d38d2b6..5610d4c144c 100644 --- a/docs/_snippets/multiple-stories-test.md +++ b/docs/_snippets/multiple-stories-test.md @@ -8,7 +8,7 @@ import * as FormStories from './LoginForm.stories'; const { InvalidForm, ValidForm } = composeStories(FormStories); test('Tests invalid form state', async () => { - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -21,7 +21,7 @@ test('Tests invalid form state', async () => { }); test('Tests filled form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -44,7 +44,7 @@ import * as FormStories from './LoginForm.stories'; const { InvalidForm, ValidForm } = composeStories(FormStories); test('Tests invalid form state', async () => { - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -57,7 +57,7 @@ test('Tests invalid form state', async () => { }); test('Tests filled form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -80,7 +80,7 @@ import * as FormStories from './LoginForm.stories'; const { InvalidForm, ValidForm } = composeStories(FormStories); test('Tests invalid form state', async () => { - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -93,7 +93,7 @@ test('Tests invalid form state', async () => { }); test('Tests filled form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -116,7 +116,7 @@ import * as FormStories from './LoginForm.stories'; const { InvalidForm, ValidForm } = composeStories(FormStories); test('Tests invalid form state', async () => { - await InvalidForm.play(); + await InvalidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -129,7 +129,7 @@ test('Tests invalid form state', async () => { }); test('Tests filled form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', diff --git a/docs/_snippets/portable-stories-jest-compose-story.md b/docs/_snippets/portable-stories-jest-compose-story.md index ed50a5540ba..610283b5f33 100644 --- a/docs/_snippets/portable-stories-jest-compose-story.md +++ b/docs/_snippets/portable-stories-jest-compose-story.md @@ -11,7 +11,7 @@ test('onclick handler is called', () => { const Primary = composeStory(PrimaryStory, meta); const onClickSpy = jest.fn(); - await Primary.play({ args: { ...Primary.args, onClick: onClickSpy } }); + await Primary.run({ args: { ...Primary.args, onClick: onClickSpy } }); const buttonElement = screen.getByRole('button'); buttonElement.click(); @@ -31,7 +31,7 @@ test('onclick handler is called', () => { const Primary = composeStory(PrimaryStory, meta); const onClickSpy = jest.fn(); - await Primary.play({ args: { ...Primary.args, onClick: onClickSpy } }); + await Primary.run({ args: { ...Primary.args, onClick: onClickSpy } }); const buttonElement = screen.getByRole('button'); buttonElement.click(); diff --git a/docs/_snippets/portable-stories-jest-multi-snapshot-test.md b/docs/_snippets/portable-stories-jest-multi-snapshot-test.md index bf9fb2e2426..4845a1abd69 100644 --- a/docs/_snippets/portable-stories-jest-multi-snapshot-test.md +++ b/docs/_snippets/portable-stories-jest-multi-snapshot-test.md @@ -13,7 +13,7 @@ describe(options.suite, () => { stories.forEach(({ name, story }) => { // ...Previously existing code testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); diff --git a/docs/_snippets/portable-stories-jest-override-globals.md b/docs/_snippets/portable-stories-jest-override-globals.md index 1bf18cd793d..43da554fca8 100644 --- a/docs/_snippets/portable-stories-jest-override-globals.md +++ b/docs/_snippets/portable-stories-jest-override-globals.md @@ -12,13 +12,13 @@ test('renders in English', async () => { { globals: { locale: 'en' } } // 👈 Project annotations to override the locale ); - await Primary.play(); + await Primary.run(); }); test('renders in Spanish', async () => { const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } }); - await Primary.play(); + await Primary.run(); }); ``` @@ -36,12 +36,12 @@ test('renders in English', async () => { { globals: { locale: 'en' } } // 👈 Project annotations to override the locale ); - await Primary.play(); + await Primary.run(); }); test('renders in Spanish', async () => { const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } }); - await Primary.play(); + await Primary.run(); }); ``` diff --git a/docs/_snippets/portable-stories-jest-snapshot-test.md b/docs/_snippets/portable-stories-jest-snapshot-test.md index 3abe940b0c4..20a146ab48e 100644 --- a/docs/_snippets/portable-stories-jest-snapshot-test.md +++ b/docs/_snippets/portable-stories-jest-snapshot-test.md @@ -67,7 +67,7 @@ describe(options.suite, () => { const testFn = story.parameters.storyshots?.skip ? test.skip : test; testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); expect(document.body.firstChild).toMatchSnapshot(); @@ -155,7 +155,7 @@ describe(options.suite, () => { const testFn = story.parameters.storyshots?.skip ? test.skip : test; testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); expect(document.body.firstChild).toMatchSnapshot(); diff --git a/docs/_snippets/portable-stories-jest-with-play-function.md b/docs/_snippets/portable-stories-jest-with-play-function.md index 2742aae2272..a927b1366cf 100644 --- a/docs/_snippets/portable-stories-jest-with-play-function.md +++ b/docs/_snippets/portable-stories-jest-with-play-function.md @@ -9,7 +9,7 @@ const { Primary } = composeStories(stories); test('renders and executes the play function', async () => { // Mount story and run interactions - await Primary.play(); + await Primary.run(); }); ``` @@ -23,6 +23,6 @@ const { Primary } = composeStories(stories); test('renders and executes the play function', async () => { // Mount story and run interactions - await Primary.play(); + await Primary.run(); }); ``` diff --git a/docs/_snippets/portable-stories-vitest-compose-stories.md b/docs/_snippets/portable-stories-vitest-compose-stories.md index 3e4164a0429..8ee390c4824 100644 --- a/docs/_snippets/portable-stories-vitest-compose-stories.md +++ b/docs/_snippets/portable-stories-vitest-compose-stories.md @@ -11,14 +11,14 @@ import * as stories from './Button.stories'; const { Primary, Secondary } = composeStories(stories); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { // You can override props by passing them in the context argument of the play function - await Primary.play({ args: { ...Primary.args, children: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, children: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); }); @@ -37,14 +37,14 @@ import * as stories from './Button.stories'; const { Primary, Secondary } = composeStories(stories); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { // You can override props by passing them in the context argument of the play function - await Primary.play({ args: { ...Primary.args, children: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, children: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); }); @@ -63,14 +63,14 @@ import * as stories from './Button.stories'; const { Primary, Secondary } = composeStories(stories); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { // You can override props by passing them in the context argument of the play function - await Primary.play({ args: { ...Primary.args, children: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, children: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); }); diff --git a/docs/_snippets/portable-stories-vitest-compose-story.md b/docs/_snippets/portable-stories-vitest-compose-story.md index 3e38a8e7e01..bb59a3e2c4e 100644 --- a/docs/_snippets/portable-stories-vitest-compose-story.md +++ b/docs/_snippets/portable-stories-vitest-compose-story.md @@ -9,14 +9,14 @@ import meta, { Primary as PrimaryStory } from './Button.stories'; const Primary = composeStory(PrimaryStory, meta); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { - await Primary.play({ args: { ...Primary.args, label: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); @@ -34,14 +34,14 @@ import meta, { Primary as PrimaryStory } from './Button.stories'; const Primary = composeStory(PrimaryStory, meta); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { - await Primary.play({ args: { ...Primary.args, label: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); @@ -59,14 +59,14 @@ import meta, { Primary as PrimaryStory } from './Button.stories'; const Primary = composeStory(PrimaryStory, meta); test('renders primary button with default args', async () => { - await Primary.play(); + await Primary.run(); const buttonElement = screen.getByText('Text coming from args in stories file!'); expect(buttonElement).not.toBeNull(); }); test('renders primary button with overridden props', async () => { - await Primary.play({ args: { ...Primary.args, label: 'Hello world' } }); + await Primary.run({ args: { ...Primary.args, label: 'Hello world' } }); const buttonElement = screen.getByText(/Hello world/i); expect(buttonElement).not.toBeNull(); diff --git a/docs/_snippets/portable-stories-vitest-multi-snapshot-test.md b/docs/_snippets/portable-stories-vitest-multi-snapshot-test.md index 51d3dac4797..b2cece86e6f 100644 --- a/docs/_snippets/portable-stories-vitest-multi-snapshot-test.md +++ b/docs/_snippets/portable-stories-vitest-multi-snapshot-test.md @@ -10,7 +10,7 @@ describe(options.suite, () => { stories.forEach(({ name, story }) => { // ...Previously existing code testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); diff --git a/docs/_snippets/portable-stories-vitest-override-globals.md b/docs/_snippets/portable-stories-vitest-override-globals.md index 3be33992d7c..ae2a362b77e 100644 --- a/docs/_snippets/portable-stories-vitest-override-globals.md +++ b/docs/_snippets/portable-stories-vitest-override-globals.md @@ -12,13 +12,13 @@ test('renders in English', async () => { { globals: { locale: 'en' } } // 👈 Project annotations to override the locale ); - await Primary.play(); + await Primary.run(); }); test('renders in Spanish', async () => { const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } }); - await Primary.play(); + await Primary.run(); }); ``` @@ -36,13 +36,13 @@ test('renders in English', async () => { { globals: { locale: 'en' } } // 👈 Project annotations to override the locale ); - await Primary.play(); + await Primary.run(); }); test('renders in Spanish', async () => { const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } }); - await Primary.play(); + await Primary.run(); }); ``` @@ -60,12 +60,12 @@ test('renders in English', async () => { { globals: { locale: 'en' } } // 👈 Project annotations to override the locale ); - await Primary.play(); + await Primary.run(); }); test('renders in Spanish', async () => { const Primary = composeStory(PrimaryStory, meta, { globals: { locale: 'es' } }); - await Primary.play(); + await Primary.run(); }); ``` diff --git a/docs/_snippets/portable-stories-vitest-snapshot-test.md b/docs/_snippets/portable-stories-vitest-snapshot-test.md index 623c451c84c..07b8cf4c772 100644 --- a/docs/_snippets/portable-stories-vitest-snapshot-test.md +++ b/docs/_snippets/portable-stories-vitest-snapshot-test.md @@ -69,7 +69,7 @@ describe(options.suite, () => { const testFn = story.parameters.storyshots?.skip ? test.skip : test; testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); @@ -160,7 +160,7 @@ describe(options.suite, () => { const testFn = story.parameters.storyshots?.skip ? test.skip : test; testFn(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); diff --git a/docs/_snippets/portable-stories-vitest-with-play-function.md b/docs/_snippets/portable-stories-vitest-with-play-function.md index d5dbe5d8a50..024ea33056b 100644 --- a/docs/_snippets/portable-stories-vitest-with-play-function.md +++ b/docs/_snippets/portable-stories-vitest-with-play-function.md @@ -8,7 +8,7 @@ const { Primary } = composeStories(stories); test('renders and executes the play function', async () => { // Mount story and run interactions - await Primary.play(); + await Primary.run(); }); ``` @@ -22,7 +22,7 @@ const { Primary } = composeStories(stories); test('renders and executes the play function', async () => { // Mount story and run interactions - await Primary.play(); + await Primary.run(); }); ``` @@ -36,6 +36,6 @@ const { Primary } = composeStories(stories); test('renders and executes the play function', async () => { // Mount story and run interactions - await Primary.play(); + await Primary.run(); }); ``` diff --git a/docs/_snippets/single-story-test.md b/docs/_snippets/single-story-test.md index 14452f76c66..5ba0de1dbef 100644 --- a/docs/_snippets/single-story-test.md +++ b/docs/_snippets/single-story-test.md @@ -8,7 +8,7 @@ import Meta, { ValidForm as ValidFormStory } from './LoginForm.stories'; const ValidForm = composeStory(ValidFormStory, Meta); test('Validates form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -31,7 +31,7 @@ import Meta, { ValidForm as ValidFormStory } from './LoginForm.stories'; const ValidForm = composeStory(ValidFormStory, Meta); test('Validates form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -54,7 +54,7 @@ import Meta, { ValidForm as ValidFormStory } from './LoginForm.stories'; const ValidForm = composeStory(ValidFormStory, Meta); test('Validates form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', @@ -77,7 +77,7 @@ import Meta, { ValidForm as ValidFormStory } from './LoginForm.stories'; const ValidForm = composeStory(ValidFormStory, Meta); test('Validates form', async () => { - await ValidForm.play(); + await ValidForm.run(); const buttonElement = screen.getByRole('button', { name: 'Submit', diff --git a/docs/_snippets/snapshot-tests-portable-stories.md b/docs/_snippets/snapshot-tests-portable-stories.md index 5a7de482c55..dfbd20efbde 100644 --- a/docs/_snippets/snapshot-tests-portable-stories.md +++ b/docs/_snippets/snapshot-tests-portable-stories.md @@ -117,7 +117,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); expect(document.body.firstChild).toMatchSnapshot(); @@ -176,7 +176,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); expect(document.body.firstChild).toMatchSnapshot(); @@ -245,7 +245,7 @@ describe('Stories Snapshots', () => { stories.forEach(({ name, story }) => { test(name, async () => { - await story.play(); + await story.run(); // Ensures a consistent snapshot by waiting for the component to render by adding a delay of 1 ms before taking the snapshot. await new Promise((resolve) => setTimeout(resolve, 1)); expect(document.body.firstChild).toMatchSnapshot(); diff --git a/docs/api/doc-blocks/doc-block-colorpalette.mdx b/docs/api/doc-blocks/doc-block-colorpalette.mdx index ef1b9fa305f..a0f31720ea4 100644 --- a/docs/api/doc-blocks/doc-block-colorpalette.mdx +++ b/docs/api/doc-blocks/doc-block-colorpalette.mdx @@ -46,6 +46,18 @@ import { Meta, ColorPalette, ColorItem } from '@storybook/blocks'; Apple30: 'rgba(102,191,60,.3)', }} /> + + ``` @@ -79,7 +91,7 @@ import { ColorItem } from '@storybook/blocks'; Type: `string[] | { [key: string]: string }` -Provides the list of colors to be displayed. Accepts any valid CSS color format (hex, RGB, HSL, etc.). When an object is provided, the keys will be displayed above the values. +Provides the list of colors to be displayed. Accepts any valid CSS color format (hex, RGB, HSL, etc.). When an object is provided, the keys will be displayed above the values. Additionally, it supports gradients such as 'linear-gradient(to right, white, black)' or 'linear-gradient(65deg, white, black)', etc. ### `subtitle` diff --git a/docs/api/portable-stories/portable-stories-jest.mdx b/docs/api/portable-stories/portable-stories-jest.mdx index 28ed8e65b2f..de7b50880af 100644 --- a/docs/api/portable-stories/portable-stories-jest.mdx +++ b/docs/api/portable-stories/portable-stories-jest.mdx @@ -83,13 +83,14 @@ sidebar: | Property | Type | Description | | ---------- | ----------------------------------------- | ------------------------------------------------------------------------------------- | - | storyName | `string` | The story's name | | args | `Record` | The story's [args](../../writing-stories/args.mdx) | | argTypes | `ArgType` | The story's [argTypes](../arg-types.mdx) | | id | `string` | The story's id | - | tags | `string[]` | The story's [tags](../../writing-stories/tags.mdx) | | parameters | `Record` | The story's [parameters](../parameters.mdx) | - | play | `(context) => Promise \| undefined` | Mounts and executes the [play function](#3-play) of a given story | + | play | `(context) => Promise \| undefined` | Executes the play function of a given story | + | run | `(context) => Promise \| undefined` | [Mounts and executes the play function](#3-run) of a given story | + | storyName | `string` | The story's name | + | tags | `string[]` | The story's [tags](../../writing-stories/tags.mdx) | ## composeStory @@ -202,7 +203,7 @@ sidebar: To preview your stories in Storybook, Storybook runs a story pipeline, which includes applying project annotations, loading data, rendering the story, and playing interactions. This is a simplified version of the pipeline: - ![A flow diagram of the story pipeline. First, set project annotations. Collect annotations (decorators, args, etc) which are exported by addons and the preview file. Second, compose story. Create renderable elements based on the stories passed onto the API. Third, play. Mount the component and execute all the story lifecycle hooks.](../../_assets/api/story-pipeline.png) + ![A flow diagram of the story pipeline. First, set project annotations. Collect annotations (decorators, args, etc) which are exported by addons and the preview file. Second, compose story. Create renderable elements based on the stories passed onto the API. Third, run. Mount the component and execute all the story lifecycle hooks, including the play function.](../../_assets/api/story-pipeline.png) When you want to reuse a story in a different environment, however, it's crucial to understand that all these steps make a story. The portable stories API provides you with the mechanism to recreate that story pipeline in your external environment: @@ -216,11 +217,11 @@ sidebar: The story is prepared by running [`composeStories`](#composestories) or [`composeStory`](#composestory). The outcome is a renderable component that represents the render function of the story. - ### 3. Play + ### 3. Run - Finally, stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining [loaders](../../writing-stories/loaders.mdx), [beforeEach](../../writing-tests/interaction-testing.mdx#run-code-before-each-story) or by having all the story code in the play function when using the [mount](../../writing-tests/interaction-testing.mdx#run-code-before-the-component-gets-rendered). In portable stories, all of these steps will be executed when you run the `play` method of the composed story. + Finally, stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining [loaders](../../writing-stories/loaders.mdx), [beforeEach](../../writing-tests/interaction-testing.mdx#run-code-before-each-story) or by having all the story code in the play function when using the [mount](../../writing-tests/interaction-testing.mdx#run-code-before-the-component-gets-rendered). In portable stories, all of these steps will be executed when you call the `run` method of the composed story. - 👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `play` method to be called. + 👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `run` method to be called. {/* prettier-ignore-start */} diff --git a/docs/api/portable-stories/portable-stories-playwright.mdx b/docs/api/portable-stories/portable-stories-playwright.mdx index 5ba64aab882..2cbae20f3dd 100644 --- a/docs/api/portable-stories/portable-stories-playwright.mdx +++ b/docs/api/portable-stories/portable-stories-playwright.mdx @@ -116,7 +116,7 @@ sidebar: ## setProjectAnnotations This API should be called once, before the tests run, in [`playwright/index.ts`](https://playwright.dev/docs/test-components#step-1-install-playwright-test-for-components-for-your-respective-framework). This will make sure that when `mount` is called, the project annotations are taken into account as well. - + These are the configurations needed in the setup file: - preview annotations: those defined in `.storybook/preview.ts` - addon annotations (optional): those exported by addons diff --git a/docs/api/portable-stories/portable-stories-vitest.mdx b/docs/api/portable-stories/portable-stories-vitest.mdx index 82fc3b54f1f..84ad6619da5 100644 --- a/docs/api/portable-stories/portable-stories-vitest.mdx +++ b/docs/api/portable-stories/portable-stories-vitest.mdx @@ -89,13 +89,14 @@ sidebar: | Property | Type | Description | | ---------- | ----------------------------------------- | ------------------------------------------------------------------------------------- | - | storyName | `string` | The story's name | | args | `Record` | The story's [args](../../writing-stories/args.mdx) | | argTypes | `ArgType` | The story's [argTypes](../arg-types.mdx) | | id | `string` | The story's id | - | tags | `string[]` | The story's [tags](../../writing-stories/tags.mdx) | | parameters | `Record` | The story's [parameters](../parameters.mdx) | - | play | `(context) => Promise \| undefined` | Mounts and executes the [play function](#3-play) of a given story | + | play | `(context) => Promise \| undefined` | Executes the play function of a given story | + | run | `(context) => Promise \| undefined` | [Mounts and executes the play function](#3-run) of a given story | + | storyName | `string` | The story's name | + | tags | `string[]` | The story's [tags](../../writing-stories/tags.mdx) | ## composeStory @@ -216,7 +217,7 @@ sidebar: To preview your stories in Storybook, Storybook runs a story pipeline, which includes applying project annotations, loading data, rendering the story, and playing interactions. This is a simplified version of the pipeline: - ![A flow diagram of the story pipeline. First, set project annotations. Collect annotations (decorators, args, etc) which are exported by addons and the preview file. Second, compose story. Create renderable elements based on the stories passed onto the API. Third, play. Mount the component and execute all the story lifecycle hooks.](../../_assets/api/story-pipeline.png) + ![A flow diagram of the story pipeline. First, set project annotations. Collect annotations (decorators, args, etc) which are exported by addons and the preview file. Second, compose story. Create renderable elements based on the stories passed onto the API. Third, run. Mount the component and execute all the story lifecycle hooks, including the play function.](../../_assets/api/story-pipeline.png) When you want to reuse a story in a different environment, however, it's crucial to understand that all these steps make a story. The portable stories API provides you with the mechanism to recreate that story pipeline in your external environment: @@ -230,11 +231,11 @@ sidebar: The story is prepared by running [`composeStories`](#composestories) or [`composeStory`](#composestory). The outcome is a renderable component that represents the render function of the story. - ### 3. Play + ### 3. Run - Finally, stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining [loaders](../../writing-stories/loaders.mdx), [beforeEach](../../writing-tests/interaction-testing.mdx#run-code-before-each-story) or by having all the story code in the play function when using the [mount](../../writing-tests/interaction-testing.mdx#run-code-before-the-component-gets-rendered). In portable stories, all of these steps will be executed when you run the `play` method of the composed story. + Finally, stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining [loaders](../../writing-stories/loaders.mdx), [beforeEach](../../writing-tests/interaction-testing.mdx#run-code-before-each-story) or by having all the story code in the play function when using the [mount](../../writing-tests/interaction-testing.mdx#run-code-before-the-component-gets-rendered). In portable stories, all of these steps will be executed when you call the `run` method of the composed story. - 👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `play` method to be called. + 👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `run` method to be called. {/* prettier-ignore-start */} diff --git a/docs/versions/latest.json b/docs/versions/latest.json index 37aeda8693b..54748b36928 100644 --- a/docs/versions/latest.json +++ b/docs/versions/latest.json @@ -1 +1 @@ -{"version":"8.2.6","info":{"plain":"- CPC: Fix missing exports for addon-kit - [#28691](https://github.com/storybookjs/storybook/pull/28691), thanks @ndelangen!"}} +{"version":"8.2.7","info":{"plain":"- CPC: Fix type usage in renderers - [#28745](https://github.com/storybookjs/storybook/pull/28745), thanks @ndelangen!\n- Core: Introduce run over play in portable stories, and revert back play changes of 8.2 - [#28764](https://github.com/storybookjs/storybook/pull/28764), thanks @kasperpeulen!"}} diff --git a/docs/versions/next.json b/docs/versions/next.json index f2daa3fb9b6..955a2c69a49 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.3.0-alpha.2","info":{"plain":"- Addon-Interactions: Fix status in panel tab - [#28580](https://github.com/storybookjs/storybook/pull/28580), thanks @yannbf!\n- Build: Remove external overrides, use package.json as source of truth - [#28632](https://github.com/storybookjs/storybook/pull/28632), thanks @kasperpeulen!\n- CLI: Add conditional logging for manager and preview start - [#28603](https://github.com/storybookjs/storybook/pull/28603), thanks @tobiasdiez!\n- CPC: Add the globals export for manager - [#28650](https://github.com/storybookjs/storybook/pull/28650), thanks @ndelangen!\n- CPC: Correct path to the `@storybook/theming/create` alias - [#28643](https://github.com/storybookjs/storybook/pull/28643), thanks @Averethel!\n- Core: Fix manager-builder `tsconfig` to emit `react-jsx` - [#28541](https://github.com/storybookjs/storybook/pull/28541), thanks @williamhelmrath!\n- Fix: Add header for MountMustBeDestructuredError message - [#28590](https://github.com/storybookjs/storybook/pull/28590), thanks @0916dhkim!\n- Fix: Prevent iframe from capturing mouse events in composed Storybooks - [#28568](https://github.com/storybookjs/storybook/pull/28568), thanks @Vincentdevreede!\n- Onboarding: Fix code snippet when story name differs from export name - [#28649](https://github.com/storybookjs/storybook/pull/28649), thanks @ghengeveld!\n- Vue: Fix out of memory error when using vue-component-meta - [#28589](https://github.com/storybookjs/storybook/pull/28589), thanks @larsrickert!"}} +{"version":"8.3.0-alpha.3","info":{"plain":"- Angular: Fix Angular template error for props with a circular reference - [#28498](https://github.com/storybookjs/storybook/pull/28498), thanks @Marklb!\n- Angular: Fix template props not able to use dot notation - [#28588](https://github.com/storybookjs/storybook/pull/28588), thanks @Marklb!\n- CLI: Fix the initialization of Storybook in workspaces - [#28699](https://github.com/storybookjs/storybook/pull/28699), thanks @valentinpalkovic!\n- CPC: Fix missing exports for addon-kit - [#28691](https://github.com/storybookjs/storybook/pull/28691), thanks @ndelangen!\n- CPC: Fix type usage in renderers - [#28745](https://github.com/storybookjs/storybook/pull/28745), thanks @ndelangen!\n- Controls: Add disableSave parameter - [#28734](https://github.com/storybookjs/storybook/pull/28734), thanks @valentinpalkovic!\n- React: Avoid 'Dynamic require of react is not possible' issue - [#28730](https://github.com/storybookjs/storybook/pull/28730), thanks @valentinpalkovic!\n- Telemetry: Add mount, beforeEach, moduleMock stats - [#28624](https://github.com/storybookjs/storybook/pull/28624), thanks @shilman!\n- Telemetry: CSF feature usage - [#28622](https://github.com/storybookjs/storybook/pull/28622), thanks @shilman!\n- Types: Adjust beforeAll to be non-nullable in NormalizedProjectAnnotations - [#28671](https://github.com/storybookjs/storybook/pull/28671), thanks @kasperpeulen!\n- Vue: Fix out of memory error when using vue-component-meta for events and slots - [#28674](https://github.com/storybookjs/storybook/pull/28674), thanks @larsrickert!\n- Vue: Improve generated code snippets - [#27194](https://github.com/storybookjs/storybook/pull/27194), thanks @larsrickert!"}} diff --git a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/portable-stories.test.tsx b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/portable-stories.test.tsx index 8b451f330de..f67542889e8 100644 --- a/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/portable-stories.test.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/nextjs/stories/portable-stories.test.tsx @@ -20,7 +20,7 @@ const runTests = (name: string, storiesModule: any) => { const composedStories = composeStories(storiesModule); Object.entries(composedStories).forEach(([name, Story]: [any, any]) => { it(`renders ${name}`, async () => { - await Story.play?.(); + await Story.run?.(); expect(document.body).toMatchSnapshot(); }); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.test.tsx b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.test.tsx index 391e9f0f563..314d3a0d031 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.test.tsx +++ b/test-storybooks/portable-stories-kitchen-sink/react/stories/Button.test.tsx @@ -92,7 +92,7 @@ describe('CSF3', () => { it('renders with play function', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - await CSF3InputFieldFilled.play(); + await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -117,6 +117,6 @@ it('should pass with decorators that need addons channel', () => { // Batch snapshot testing const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName, Story]); it.each(testCases)('Renders %s story', async (_storyName, Story) => { - await Story.play(); + await Story.run(); expect(document.body).toMatchSnapshot(); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.test.ts b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.test.ts index 3c013ef06ef..094d2a59232 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.test.ts +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/Button.test.ts @@ -55,7 +55,7 @@ describe('renders', () => { expect(getByTestId('spy-data').textContent).toEqual('mockFn return value'); expect(getByTestId('loaded-data').textContent).toEqual('loaded data'); // spy assertions happen in the play function and should work - await LoaderStory.play!(); + await LoaderStory.run!(); }); }); @@ -114,7 +114,7 @@ describe('CSF3', () => { it('renders with play function without canvas element', async () => { const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); - await CSF3InputFieldFilled.play(); + await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -126,7 +126,7 @@ describe('CSF3', () => { const div = document.createElement('div'); document.body.appendChild(div); - await CSF3InputFieldFilled.play({ canvasElement: div }); + await CSF3InputFieldFilled.run({ canvasElement: div }); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); @@ -141,6 +141,6 @@ const testCases = Object.values(composeStories(stories)).map( ); it.each(testCases)('Renders %s story', async (_storyName, Story) => { if (_storyName === 'CSF2StoryWithLocale') return; - await Story.play(); + await Story.run(); expect(document.body).toMatchSnapshot(); }); diff --git a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/__snapshots__/Button.test.ts.snap b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/__snapshots__/Button.test.ts.snap index cfede6f889b..976716d156c 100644 --- a/test-storybooks/portable-stories-kitchen-sink/svelte/stories/__snapshots__/Button.test.ts.snap +++ b/test-storybooks/portable-stories-kitchen-sink/svelte/stories/__snapshots__/Button.test.ts.snap @@ -2,59 +2,44 @@ exports[`Renders CSF2Secondary story 1`] = ` - - -`; - -exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` - -
+
-
- - `; -exports[`Renders CSF3Button story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` - +
+
+ + +
+ + +
`; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders CSF3Button story 1`] = `
-

- I am a custom render function -

- +
+
+ +`; + exports[`Renders CSF3InputFieldFilled story 1`] = ` - +
+ +
`; exports[`Renders CSF3Primary story 1`] = ` - +
+ +
`; exports[`Renders LoaderStory story 1`] = `
-
- loaded data -
- -
- mockFn return value +
+
+ loaded data +
+ +
+ mockFn return value +
@@ -108,20 +122,22 @@ exports[`Renders LoaderStory story 1`] = ` exports[`Renders NewStory story 1`] = ` -
- + + +
+
- - `;