Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Document portable stories usage #28452

Merged
merged 9 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file modified docs/_assets/api/story-pipeline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 2 additions & 11 deletions docs/_snippets/portable-stories-jest-with-play-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import meta, { Primary } from './Button.stories';

test('renders and executes the play function', async () => {
const PrimaryStory = composeStory(Primary, meta);

// First, render the story
render(<PrimaryStory />);

// Then, execute the play function
// Mount story and run interactions
await PrimaryStory.play();
});
```
Expand All @@ -26,12 +22,7 @@ import meta, { Primary } from './Button.stories';

test('renders and executes the play function', async () => {
const PrimaryStory = composeStory(Primary, meta);

// First, render the story
render(PrimaryStory);

// Then, execute the play function
// Mount story and run interactions
await PrimaryStory.play();
});
```

54 changes: 0 additions & 54 deletions docs/_snippets/portable-stories-vitest-with-loaders.md

This file was deleted.

19 changes: 3 additions & 16 deletions docs/_snippets/portable-stories-vitest-with-play-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import meta, { Primary } from './Button.stories';

test('renders and executes the play function', async () => {
const PrimaryStory = composeStory(Primary, meta);

// First, render the story
render(<PrimaryStory />);

// Then, execute the play function
// Mount story and run interactions
await PrimaryStory.play();
});
```
Expand All @@ -25,11 +21,7 @@ import meta, { Primary } from './Button.stories';

test('renders and executes the play function', async () => {
const PrimaryStory = composeStory(Primary, meta);

// First, render the story
render(PrimaryStory.Component, PrimaryStory.props);

// Then, execute the play function
// Mount story and run interactions
await PrimaryStory.play();
});
```
Expand All @@ -43,12 +35,7 @@ import meta, { Primary } from './Button.stories';

test('renders and executes the play function', async () => {
const PrimaryStory = composeStory(Primary, meta);

// First, render the story
render(PrimaryStory);

// Then, execute the play function
// Mount story and run interactions
await PrimaryStory.play();
});
```

Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
```js filename="setupFile.js|ts" renderer="react" language="js"
// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';
import * as previewAnnotations from './.storybook/preview';

import { setProjectAnnotations } from '@storybook/react';
import { render as testingLibraryRender } from '@testing-library/react';

setProjectAnnotations(globalStorybookConfig);
setProjectAnnotations([previewAnnotations, { testingLibraryRender }]);
yannbf marked this conversation as resolved.
Show resolved Hide resolved
```

```js filename="vitest.config.js" renderer="react" language="js" tabTitle="vite"
Expand All @@ -22,7 +23,7 @@ export default mergeConfig(
clearMocks: true,
setupFiles: './src/setupTests.js', //👈 Our configuration file enabled here
},
}),
})
);
```

Expand All @@ -42,17 +43,18 @@ export default mergeConfig(
clearMocks: true,
setupFiles: './src/setupTests.ts', //👈 Our configuration file enabled here
},
}),
})
);
```

```js filename="setupFile.js|ts" renderer="vue" language="js"
// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';
import * as previewAnnotations from './.storybook/preview';

import { setProjectAnnotations } from '@storybook/vue3';
import { render as testingLibraryRender } from '@testing-library/vue';

setProjectAnnotations(globalStorybookConfig);
setProjectAnnotations([previewAnnotations, { testingLibraryRender }]);
kylegach marked this conversation as resolved.
Show resolved Hide resolved
```

```js filename="vitest.config.js" renderer="vue" language="js" tabTitle="vite"
Expand All @@ -70,7 +72,7 @@ export default mergeConfig(
clearMocks: true,
setupFiles: './src/setupTests.js', //👈 Our configuration file enabled here
},
}),
})
);
```

Expand All @@ -90,7 +92,6 @@ export default mergeConfig(
clearMocks: true,
setupFiles: './src/setupTests.ts', //👈 Our configuration file enabled here
},
}),
})
);
```

91 changes: 34 additions & 57 deletions docs/api/portable-stories/portable-stories-jest.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
---
title: 'Portable stories in Jest'
draft: true
---

{/* This page is unpublished for now */}

<If notRenderer={['react', 'vue']}>
<Callout variant="info">
Portable stories in Jest are currently only supported in [React](?renderer=react) and [Vue](?renderer=vue) projects.
Expand Down Expand Up @@ -81,15 +78,15 @@ draft: true

Additionally, the composed story will have the following properties:

| Property | Type | Description |
| ---------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| storyName | `string` | The story's name |
| args | `Record<string, any>` | The story's [args](../../writing-stories/args.mdx) |
| argTypes | `ArgType` | The story's [argTypes](../arg-types.mdx) |
| id | `string` | The story's id |
| parameters | `Record<string, any>` | The story's [parameters](../parameters.mdx) |
| load | `() => Promise<void>` | [Prepares](#3-prepare) the story for rendering and and cleans up all previous stories |
| play | `(context?: StoryContext) => Promise<void> \| undefined` | Executes the [play function](#5-play) of a given story |
| Property | Type | Description |
| ---------- | ----------------------------------------- | ------------------------------------------------------------------------------------- |
| storyName | `string` | The story's name |
| args | `Record<string, any>` | 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<string, any>` | The story's [parameters](../parameters.mdx) |
| play | `(context) => Promise<void> \| undefined` | Mounts and executes the [play function](#3-play) of a given story |

## composeStory

Expand Down Expand Up @@ -159,25 +156,27 @@ draft: true
## setProjectAnnotations

This API should be called once, before the tests run, typically in a [setup file](https://jestjs.io/docs/configuration#setupfiles-array). This will make sure that whenever `composeStories` or `composeStory` are called, the project annotations are taken into account as well.

<If renderer="react">
<Callout variant="info">
**Using `Next.js`?** When you import [`composeStories`](#composestories) or [`composeStory`](#composestory) from the `@storybook/nextjs` package (e.g. `import { composeStories } from '@storybook/nextjs'`), you probably do not need to call `setProjectAnnotations` yourself. The Next.js framework will handle this for you.

If you are using an addon that is required for your stories to render, you will still need to include that addon's `preview` export in the project annotations set. See the example and callout below.
</Callout>

<p />
</If>

These are the configurations needed in the setup file:
- preview annotations: those defined in `.storybook/preview.ts`
- addon annotations (optional): those exported by addons
- beforeAll: code that runs before all tests
kylegach marked this conversation as resolved.
Show resolved Hide resolved
- testingLibraryRender: the render function from Testing Library

```ts
// setup-portable-stories.ts
// Replace <your-renderer> with your renderer, e.g. nextjs, react, vue3
import { setProjectAnnotations } from '@storybook/<your-renderer>';
import { beforeAll } from 'vitest';
// Replace your-renderer with the renderer you are using (e.g. react, vue3, svelte)
import { setProjectAnnotations } from '@storybook/your-renderer';
// Replace your-renderer with the renderer you are using (e.g. react, vue, svelte)
import { render as testingLibraryRender } from '@testing-library/your-renderer';
import * as addonAnnotations from 'my-addon/preview';
import * as previewAnnotations from './.storybook/preview';

setProjectAnnotations([previewAnnotations, addonAnnotations]);
const annotations = setProjectAnnotations([previewAnnotations, addonAnnotations, { testingLibraryRender }]);

// supports beforeAll hook from Storybook
beforeAll(annotations.beforeAll);
```

<Callout variant="warning">
Expand Down Expand Up @@ -208,56 +207,34 @@ draft: true

## Story pipeline

To preview your stories, 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:
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. Storybook automatically collects decorators etc. which are exported by addons and the .storybook/preview file. .storybook/preview.js produces project annotations; some-addon/preview produces addon annotations. Second, prepare. Storybook gathers all the metadata required for a story to be composed. Select.stories.js produces component annotations from the default export and story annotations from the named export. Third, load. Storybook executes all loaders (async). Fourth, render. Storybook renders the story as a component. Illustration of the rendered Select component. Fifth, play. Storybook runs the play function (interacting with component). Illustration of the renderer Select component, now open.](../../_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, render story. Load, mount, and execute the play function as part of the portable stories API.](../../_assets/api/story-pipeline.png)
kylegach marked this conversation as resolved.
Show resolved Hide resolved

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:

### 1. Apply project-level annotations

[Annotations](#annotations) come from the story itself, that story's component, and the project. The project-level annotatations are those defined in your `.storybook/preview.js` file and by addons you're using. In portable stories, these annotations are not applied automaticallyyou must apply them yourself.
[Annotations](#annotations) come from the story itself, that story's component, and the project. The project-level annotations are those defined in your `.storybook/preview.js` file and by addons you're using. In portable stories, these annotations are not applied automaticallyyou must apply them yourself.

👉 For this, you use the [`setProjectAnnotations`](#setprojectannotations) API.
👉 For this, you use the [`setProjectAnnotations`](#setprojectannotations) API.

### 2. Compose

The story is prepared by running [`composeStories`](#composestories) or [`composeStory`](#composestory). You do not need to do anything for this step.
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. Prepare
### 3. Play

Stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining [loaders](../../writing-stories/loaders.mdx) or [beforeEach](../../writing-tests/interaction-testing.mdx#run-code-before-each-test). In portable stories, loaders and beforeEach are not applied automatically — you have to apply them yourself.
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.

👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `load` method to be called **before** it is rendered.
👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed story will return a `play` method to be called.

<Callout variant="info">
It is recommended to always run `load` before rendering, even if the story doesn't have any loaders or beforeEach applied. By doing so, you ensure that the tests are cleaned up properly to maintain isolation and you will not have to update your test if you later add them to your story.
<Callout variant="warning">
If any of your stories mounts the component in the play function, you must set the `testingLibraryRender` annotation in [`setProjectAnnotations`](#setprojectannotations) to the render function from Testing Library. This is required to ensure that the component is mounted correctly in the test environment.
</Callout>

{/* prettier-ignore-start */}

<CodeSnippets path="portable-stories-jest-with-loaders.md" />

{/* prettier-ignore-end */}

### 4. Render

At this point, the story has been prepared and can be rendered. You pass it into the

The story has been prepared and can be rendered. To render, you pass it into the rendering mechanism of your choice (e.g. Testing Library render function, Vue test utils mount function, etc).

👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed Story is a renderable component that can be passed to your rendering mechanism.

### 5. Play

**(optional)**

Finally, stories can define a [play function](../../essentials/interactions.mdx#play-function-for-interactions) to interact with the story and assert on details after it has rendered. In portable stories, the play function does not run automatically—you have to call it yourself.

👉 For this, you use the [`composeStories`](#composestories) or [`composeStory`](#composestory) API. The composed Story will return a `play` method to be called **after** it has rendered.

{/* prettier-ignore-start */}

<CodeSnippets path="portable-stories-jest-with-play-function.md" />

{/* prettier-ignore-end */}
Expand Down
Loading