Skip to content
This repository was archived by the owner on Jan 31, 2024. It is now read-only.

Commit ed1f139

Browse files
committed
feat: support CSF3 types and expose play function
1 parent a16f1e5 commit ed1f139

File tree

9 files changed

+1370
-634
lines changed

9 files changed

+1370
-634
lines changed

README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,68 @@ test('onclick handler is called', async () => {
126126
});
127127
```
128128

129+
### Reusing story properties
130+
131+
The components returend by `composeStories` or `composeStory` not only can be rendered as React components, but also come with the combined properties from story, meta and global configuration. This means that if you want to access `args` or `parameters`, for instance, you can do so:
132+
133+
```tsx
134+
import { render, screen } from '@testing-library/react';
135+
import { composeStory } from '@storybook/testing-react';
136+
import * as stories from './Button.stories';
137+
138+
const { Primary } = composeStories(stories);
139+
140+
test('reuses args from composed story', () => {
141+
render(<Primary />);
142+
143+
const buttonElement = screen.getByRole('button');
144+
// Testing against values coming from the story itself! No need for duplication
145+
expect(buttonElement.textContent).toEqual(Primary.args.children);
146+
});
147+
```
148+
149+
> **If you're using Typescript**: Given that some of the returned properties are not required, typescript might perceive them as nullable properties and present an error. If you are sure that they exist (e.g. certain arg that is set in the story), you can use the [non-null assertion operator](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator) to tell typescript that it's all good:
150+
151+
```tsx
152+
// ERROR: Object is possibly 'undefined'
153+
Primary.args.children;
154+
155+
// SUCCESS: 🎉
156+
Primary.args!.children;
157+
```
158+
159+
### CSF3
160+
161+
Storybook released a [new version of CSF](https://storybook.js.org/blog/component-story-format-3-0/), where the story can also be an object. This is supported in @storybook/testing-react. CSF3 also brings a new function called `play`, where you can write automated interactions to the story.
162+
163+
In @storybook/testing-react, the `play` does not run automatically for you, but rather comes in the returned component, and you can execute it as you please.
164+
165+
Consider the following example:
166+
167+
```tsx
168+
export const InputFieldFilled: Story<InputFieldProps> = {
169+
play: async () => {
170+
await userEvent.type(screen.getByRole('textbox'), 'Hello world!');
171+
},
172+
};
173+
```
174+
175+
You can use the play function like this:
176+
177+
```tsx
178+
const { InputFieldFilled } = composeStories(stories);
179+
180+
test('renders with play function', async () => {
181+
render(<InputFieldFilled />);
182+
183+
// play an interaction that fills the input
184+
await InputFieldFilled.play!();
185+
186+
const input = screen.getByRole('textbox') as HTMLInputElement;
187+
expect(input.value).toEqual('Hello world!');
188+
});
189+
```
190+
129191
## Typescript
130192

131193
`@storybook/testing-react` is typescript ready and provides autocompletion to easily detect all stories of your component:
@@ -151,7 +213,6 @@ Type inference is only possible in projects that have either `strict` or `strict
151213
}
152214
```
153215

154-
155216
### Disclaimer
156217

157218
For the types to be automatically picked up, your stories must be typed. See an example:

example/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
"typescript": "^3.9.7"
3636
},
3737
"devDependencies": {
38-
"@storybook/addon-essentials": "^6.4.0-alpha.12",
39-
"@storybook/react": "^6.4.0-alpha.12",
40-
"@storybook/components": "^6.4.0-alpha.12",
41-
"@storybook/theming": "^6.4.0-alpha.12",
38+
"@storybook/addon-essentials": "^6.4.0-alpha.17",
39+
"@storybook/react": "^6.4.0-alpha.17",
40+
"@storybook/components": "^6.4.0-alpha.17",
41+
"@storybook/theming": "^6.4.0-alpha.17",
4242
"@storybook/preset-create-react-app": "^3.1.5",
4343
"@testing-library/react": "^11.2.5",
4444
"@testing-library/user-event": "^13.1.9"

example/src/components/Button.stories.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22
import { Story, Meta } from '@storybook/react';
3+
import { screen } from '@testing-library/react';
4+
import userEvent from '@testing-library/user-event';
35

46
import { Button, ButtonProps } from './Button';
57

@@ -58,16 +60,25 @@ StoryWithParamsAndDecorator.parameters = {
5860
};
5961
StoryWithParamsAndDecorator.decorators = [(StoryFn) => <StoryFn />];
6062

61-
export const CSF3Button = {
63+
export const CSF3Button: Story<ButtonProps> = {
6264
args: { children: 'foo' },
6365
};
6466

65-
export const CSF3ButtonWithRender = {
67+
export const CSF3ButtonWithRender: Story<ButtonProps> = {
68+
...CSF3Button,
6669
render: (args: ButtonProps) => (
6770
<div>
6871
<p data-testid="custom-render">I am a custom render function</p>
6972
<Button {...args} />
7073
</div>
7174
),
72-
args: { children: 'foo' },
75+
};
76+
77+
export const InputFieldFilled: Story = {
78+
render: () => {
79+
return <input />
80+
},
81+
play: async () => {
82+
await userEvent.type(screen.getByRole('textbox'), 'Hello world!');
83+
}
7384
};

example/src/components/Button.test.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,9 @@ describe('GlobalConfig', () => {
6464
describe('CSF3', () => {
6565
test('renders with inferred globalRender', () => {
6666
const Primary = composeStory(
67-
//@ts-ignore TODO: fix this once CSF3 types are created
6867
stories.CSF3Button,
6968
stories.default
70-
) as any;
69+
);
7170

7271
render(<Primary>Hello world</Primary>);
7372
const buttonElement = screen.getByText(/Hello world/i);
@@ -76,12 +75,25 @@ describe('CSF3', () => {
7675

7776
test('renders with custom render function', () => {
7877
const Primary = composeStory(
79-
//@ts-ignore TODO: fix this once CSF3 types are created
8078
stories.CSF3ButtonWithRender,
8179
stories.default
82-
) as any;
80+
);
8381

84-
render(<Primary>Hello world</Primary>);
82+
render(<Primary />);
8583
expect(screen.getByTestId("custom-render")).not.toBeNull();
8684
});
85+
86+
test('renders with play function', async () => {
87+
const InputFieldFilled = composeStory(
88+
stories.InputFieldFilled,
89+
stories.default
90+
);
91+
92+
render(<InputFieldFilled />);
93+
94+
await InputFieldFilled.play!();
95+
96+
const input = screen.getByRole('textbox') as HTMLInputElement;
97+
expect(input.value).toEqual('Hello world!');
98+
});
8799
});

0 commit comments

Comments
 (0)