storybook/docs/api/portable-stories-jest.md
Kyle Gach e09480aefb Split into three separate pages
- Organizational and prose improvements
- Remove Cypress, for now
2024-02-27 11:09:42 -07:00

11 KiB

title
Portable stories in Jest

export const SUPPORTED_RENDERERS = ['react', 'vue'];

Portable stories in Jest are currently only supported in React and Vue projects.

Portable stories are Storybook stories which can be used in external environments, such as Jest.

Normally, Storybok composes a story and its annotations automatically, as part of the story pipeline. When using stories in Jest tests, you must handle the story pipeline yourself, which is what the composeStories and composeStory functions enable.

composeStories

composeStories will process the component's stories you specify, compose each of them with the necessary annotations, and return an object containing the composed stories.

By default, the composed story will render the component with the args that are defined in the story. You can also pass any props to the component in your test and those props will override the values passed in the story's args.

<CodeSnippets paths={[ 'react/portable-stories-jest-compose-stories.ts.mdx', 'vue/portable-stories-jest-compose-stories.ts.mdx', ]} />

Type

(
  csfExports: CSF file exports,
  projectAnnotations?: ProjectAnnotations
) => Record<string, ComposedStoryFn>

Parameters

csfExports

(Required)

Type: CSF file exports

Specifies which component's stories you want to compose. Pass the full set of exports from the CSF file (not the default export!). E.g. import * as stories from './Button.stories'

projectAnnotations

Type: ProjectAnnotation | ProjectAnnotation[]

Specifies the project annotations to be applied to the composed stories.

This parameter is provided for convenience. You should likely use setProjectAnnotations instead. Details about the ProjectAnnotation type can be found in that function's projectAnnotations parameter.

This parameter can be used to override the project annotations applied via setProjectAnnotations.

Return

Type: Record<string, ComposedStoryFn>

An object where the keys are the names of the stories and the values are the composed stories.

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
argTypes ArgType The story's argTypes
id string The story's id
parameters Record<string, any> The story's parameters
load () => Promise<void> Executes all the loaders for a given story
play (context) => Promise<void> | undefined Executes the play function of a given story

composeStory

You can use composeStory if you wish to compose a single story for a component.

<CodeSnippets paths={[ 'react/portable-stories-jest-compose-story.ts.mdx', 'vue/portable-stories-jest-compose-story.ts.mdx', ]} />

Type

(
  story: Story export,
  componentAnnotations: Meta,
  projectAnnotations?: ProjectAnnotations,
  exportsName?: string
) => ComposedStoryFn

Parameters

story

(Required)

Type: Story export

Specifies which story you want to compose.

componentAnnotations

(Required)

Type: Meta

The default export from the stories file containing the story.

projectAnnotations

Type: ProjectAnnotation | ProjectAnnotation[]

Specifies the project annotations to be applied to the composed story.

This parameter is provided for convenience. You should likely use setProjectAnnotations instead. Details about the ProjectAnnotation type can be found in that function's projectAnnotations parameter.

This parameter can be used to override the project annotations applied via setProjectAnnotations.

exportsName

Type: string

You probably don't need this. Because composeStory accepts a single story, it does not have access to the name of that story's export in the file (like composeStories does). If you must ensure unique story names in your tests and you cannot use composeStories, you can pass the name of the story's export here.

Return

Type: ComposedStoryFn

A single composed story.

setProjectAnnotations

This API should be called once, before the tests run, typically in a setup file. This will make sure that whenever composeStories or composeStory are called, the project annotations are taken into account as well.

// setup-portable-stories.ts
// Replace <your-renderer> with your renderer, e.g. react, vue3
import { setProjectAnnotations } from '@storybook/<your-renderer>';
import * as addonAnnotations from 'my-addon/preview';
import * as previewAnnotations from './.storybook/preview';

setProjectAnnotations([previewAnnotations, addonAnnotations]);

Sometimes a story can require an addon's decorator to render properly. For example, an addon can apply a decorator that wraps your story in the necessary router context. In this case, you must include that addon's preview export in the project annotations set.

Type

(projectAnnotations: ProjectAnnotation | ProjectAnnotation[]) => void

Parameters

projectAnnotations

(Required)

Type: ProjectAnnotation | ProjectAnnotation[]

A set of project annotations (those defined in .storybook/preview.js|ts) or an array of sets of project annotations, which will be applied to all composed stories.

Annotations

Annotations are the metadata applied to a story, like args, decorators, loaders, and play functions. They can be defined for a specific story, all stories for a component, or all stories in the project.

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:

TK - Proper alt text once proper image is in place

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 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 automatically—you must apply them yourself.

👉 For this, you use the setProjectAnnotations API.

2. Prepare

The story is prepared by running composeStories or composeStory. You do not need to do anything for this step.

3. Load

(optional)

Stories can prepare data they need (e.g. setting up some mocks or fetching data) before rendering by defining loaders. In portable stories, the loaders are not applied automatically—you have to apply them yourself.

👉 For this, you use the composeStories or composeStory API. The composed story will return a load method to be called before it is rendered.

<CodeSnippets paths={[ 'react/portable-stories-jest-with-loaders.ts.mdx', 'vue/portable-stories-jest-with-loaders.ts.mdx', ]} />

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 or 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 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 or composeStory API. The composed Story will return a play method to be called after it has rendered.

The play function needs a canvasElement, which should be passed by you. A canvasElement is the HTML element which wraps your component. Each testing utility provides different ways to retrieve such element, but here's how to do it with Testing Library:

<CodeSnippets paths={[ 'react/portable-stories-jest-with-play-function.ts.mdx', 'vue/portable-stories-jest-with-play-function.ts.mdx', ]} />

If your play function contains assertions (e.g. expect calls), your test will fail when those assertions fail.

Overriding globals

If your stories behave differently based on globals (e.g. rendering text in English or Spanish), you can define those global values in portable stories by overriding project annotations when composing a story:

<CodeSnippets paths={[ 'react/portable-stories-jest-override-globals.ts.mdx', 'vue/portable-stories-jest-override-globals.ts.mdx', ]} />