Skip to content

Commit c1612f3

Browse files
authored
Docs: Add docs for portable stories API (storybookjs#559)
1 parent f1b2d2c commit c1612f3

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

PORTABLE_STORIES.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
### Portable stories
2+
3+
Portable stories are Storybook stories which can be used in external environments, such as [Jest](https://jestjs.io).
4+
5+
You can make your stories portable by using Storybook's `composeStory` and `composeStories` utilities, which come from the `@storybook/react` package. This way, in your unit tests, you can just select which story you want to render, and all the necessary setup (args, decoratos, etc.) will be already done for you. This is the missing piece that allows for better shareability and maintenance between writing tests and writing Storybook stories.
6+
7+
You can find example tests using portable stories in this repository [here](https://github.com/storybookjs/react-native/blob/next/examples/expo-example/components/ActionExample/Actions.test.tsx).
8+
9+
## composeStories
10+
11+
`composeStories` will process the component's stories you specify, compose each of them with the necessary [annotations](#annotations), and return an object containing the composed stories.
12+
13+
By default, the composed story will render the component with the [args](https://storybook.js.org/docs/writing-stories/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.
14+
15+
```tsx
16+
// Button.test.tsx
17+
import { test, expect } from '@jest/globals';
18+
import { render, screen } from '@testing-library/react-native';
19+
import { composeStories } from '@storybook/react';
20+
21+
// Import all stories and the component annotations from the stories file
22+
import * as stories from './Button.stories';
23+
24+
// Every component that is returned maps 1:1 with the stories,
25+
// but they already contain all annotations from story, meta, and project levels
26+
const { Primary, Secondary } = composeStories(stories);
27+
28+
test('renders primary button with default args', () => {
29+
render(<Primary />);
30+
const buttonElement = screen.getByText('Text coming from args in stories file!');
31+
expect(buttonElement).not.toBeNull();
32+
});
33+
34+
test('renders primary button with overriden props', () => {
35+
// You can override props and they will get merged with values from the story's args
36+
render(<Primary>Hello world</Primary>);
37+
const buttonElement = screen.getByText(/Hello world/i);
38+
expect(buttonElement).not.toBeNull();
39+
});
40+
```
41+
42+
### Type
43+
44+
```ts
45+
(
46+
csfExports: CSF file exports,
47+
projectAnnotations?: ProjectAnnotations
48+
) => Record<string, ComposedStoryFn>
49+
```
50+
51+
### Parameters
52+
53+
#### `csfExports`
54+
55+
(**Required**)
56+
57+
Type: CSF file exports
58+
59+
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'`
60+
61+
#### `projectAnnotations`
62+
63+
Type: `ProjectAnnotation | ProjectAnnotation[]`
64+
65+
Specifies the project annotations to be applied to the composed stories.
66+
67+
This parameter is provided for convenience. You should likely use [`setProjectAnnotations`](#setprojectannotations) instead. Details about the `ProjectAnnotation` type can be found in that function's [`projectAnnotations`](#projectannotations-2) parameter.
68+
69+
This parameter can be used to override the project annotations applied via `setProjectAnnotations`.
70+
71+
### Return
72+
73+
Type: `Record<string, ComposedStoryFn>`
74+
75+
An object where the keys are the names of the stories and the values are the composed stories.
76+
77+
Additionally, the composed story will have the following properties:
78+
79+
| Property | Type | Description |
80+
| ---------- | ----------------------------------------- | ---------------------------- |
81+
| storyName | `string` | The story's name |
82+
| args | `Record<string, any>` | The story's args |
83+
| argTypes | `ArgType` | The story's argTypes |
84+
| id | `string` | The story's id |
85+
| parameters | `Record<string, any>` | The story's parameters |
86+
87+
## composeStory
88+
89+
You can use `composeStory` if you wish to compose a single story for a component.
90+
91+
```tsx
92+
// Button.test.tsx
93+
import { jest, test, expect } from '@jest/globals';
94+
import { render, screen, userEvent } from '@testing-library/react-native';
95+
import { composeStory } from '@storybook/react';
96+
97+
import meta, { Primary } from './Button.stories';
98+
99+
test('onclick handler is called', () => {
100+
// Returns a story which already contains all annotations from story, meta and global levels
101+
const PrimaryStory = composeStory(Primary, meta);
102+
103+
const onPressSpy = jest.fn();
104+
105+
render(<PrimaryStory onPress={onPressSpy} />);
106+
107+
const user = userEvent.setup({});
108+
109+
const actionButton = screen.getByText('Press me!');
110+
111+
await user.press(actionButton);
112+
113+
expect(onPress).toHaveBeenCalled();
114+
});
115+
```
116+
117+
### Type
118+
119+
```ts
120+
(
121+
story: Story export,
122+
componentAnnotations: Meta,
123+
projectAnnotations?: ProjectAnnotations,
124+
exportsName?: string
125+
) => ComposedStoryFn
126+
```
127+
128+
### Parameters
129+
130+
#### `story`
131+
132+
(**Required**)
133+
134+
Type: `Story export`
135+
136+
Specifies which story you want to compose.
137+
138+
#### `componentAnnotations`
139+
140+
(**Required**)
141+
142+
Type: `Meta`
143+
144+
The default export from the stories file containing the [`story`](#story).
145+
146+
#### `projectAnnotations`
147+
148+
Type: `ProjectAnnotation | ProjectAnnotation[]`
149+
150+
Specifies the project annotations to be applied to the composed story.
151+
152+
This parameter is provided for convenience. You should likely use [`setProjectAnnotations`](#setprojectannotations) instead. Details about the `ProjectAnnotation` type can be found in that function's [`projectAnnotations`](#projectannotations-2) parameter.
153+
154+
This parameter can be used to override the project annotations applied via `setProjectAnnotations`.
155+
156+
#### `exportsName`
157+
158+
Type: `string`
159+
160+
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.
161+
162+
### Return
163+
164+
Type: `ComposedStoryFn`
165+
166+
A single [composed story](#return).
167+
168+
## setProjectAnnotations
169+
170+
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.
171+
172+
```ts
173+
// setup-portable-stories.ts
174+
import { setProjectAnnotations } from '@storybook/react';
175+
import * as addonAnnotations from 'my-addon/preview';
176+
import * as previewAnnotations from './.storybook/preview';
177+
178+
setProjectAnnotations([previewAnnotations, addonAnnotations]);
179+
```
180+
181+
182+
> **Note:**
183+
> Sometimes a story can require an addon's [decorator](https://storybook.js.org/docs/writing-stories/decorators) or [loader](https://storybook.js.org/docs/writing-stories/loaders) 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. See `addonAnnotations` in the example above.
184+
185+
> **Note:** If the addon doesn't automatically apply the decorator or loader itself, but instead exports them for you to apply manually in `.storybook/preview.js|ts` (e.g. using `withThemeFromJSXProvider` from [@storybook/addon-themes](https://github.com/storybookjs/storybook/blob/next/code/addons/themes/docs/api.md#withthemefromjsxprovider)), then you do not need to do anything else. They are already included in the `previewAnnotations` in the example above.
186+
187+
### Type
188+
189+
```ts
190+
(projectAnnotations: ProjectAnnotation | ProjectAnnotation[]) => void
191+
```
192+
193+
### Parameters
194+
195+
#### `projectAnnotations`
196+
197+
(**Required**)
198+
199+
Type: `ProjectAnnotation | ProjectAnnotation[]`
200+
201+
A set of project [annotations](#annotations) (those defined in `.storybook/preview.js|ts`) or an array of sets of project annotations, which will be applied to all composed stories.
202+
203+
## Annotations
204+
205+
Annotations are the metadata applied to a story, like [args](https://storybook.js.org/docs/writing-stories/args), [decorators](https://storybook.js.org/docs/writing-stories/decorators), and [loaders](https://storybook.js.org/docs/writing-stories/loaders). They can be defined for a specific story, all stories for a component, or all stories in the project.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ _Pictured is from the template mentioned in [getting started](#getting-started)_
2323
- 🔌 [Addons](#addons)
2424
- 📱 [Hide/Show Storybook](#hideshow-storybook)
2525
- 🔧 [getStorybookUI](#getstorybookui-options)
26+
- 🧪 [Using stories in unit tests](#using-stories-in-unit-tests)
2627
- 🤝 [Contributing](#contributing)
2728
-[Examples](#examples)
2829

@@ -287,6 +288,10 @@ You can pass these parameters to getStorybookUI call in your storybook entry poi
287288
}
288289
```
289290

291+
## Using stories in unit tests
292+
293+
Storybook provides testing utilities that allow you to reuse your stories in external test environments, such as Jest. This way you can write unit tests easier and reuse the setup which is already done in Storybook, but in your unit tests. You can find more information about it in the [portable stories section](./PORTABLE_STORIES.md).
294+
290295
## Contributing
291296

292297
We welcome contributions to Storybook!

0 commit comments

Comments
 (0)