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

Commit a1889cf

Browse files
authored
Merge pull request #46 from storybookjs/feature/support-csf3
feat: support CSF3 format
2 parents f639378 + d6a0cfe commit a1889cf

File tree

11 files changed

+1507
-779
lines changed

11 files changed

+1507
-779
lines changed

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,59 @@ Primary.args.children;
156156
Primary.args!.children;
157157
```
158158

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, but you have to match the requisites:
162+
163+
1 - Either your **story** has a `render` method or your **meta** contains a `component` property:
164+
165+
```js
166+
// Example 1: Meta with component property
167+
export default {
168+
title: 'Button',
169+
component: Button // <-- This is strictly necessary
170+
}
171+
172+
// Example 2: Story with render method:
173+
export const Primary = {
174+
render: (args) => <Button {...args}>
175+
}
176+
```
177+
178+
2 - For typescript users, you need to be using Storybook 6.4 or higher.
179+
180+
#### CSF3 - Interactions with play function
181+
182+
CSF3 also brings a new function called `play`, where you can write automated interactions to the story.
183+
184+
In @storybook/testing-react, the `play` function does not run automatically for you, but rather comes in the returned component, and you can execute it as you please.
185+
186+
Consider the following example:
187+
188+
```tsx
189+
export const InputFieldFilled: Story<InputFieldProps> = {
190+
play: async () => {
191+
await userEvent.type(screen.getByRole('textbox'), 'Hello world!');
192+
},
193+
};
194+
```
195+
196+
You can use the play function like this:
197+
198+
```tsx
199+
const { InputFieldFilled } = composeStories(stories);
200+
201+
test('renders with play function', async () => {
202+
render(<InputFieldFilled />);
203+
204+
// play an interaction that fills the input
205+
await InputFieldFilled.play!();
206+
207+
const input = screen.getByRole('textbox') as HTMLInputElement;
208+
expect(input.value).toEqual('Hello world!');
209+
});
210+
```
211+
159212
## Typescript
160213

161214
`@storybook/testing-react` is typescript ready and provides autocompletion to easily detect all stories of your component:

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.3.0",
39-
"@storybook/react": "^6.3.0",
40-
"@storybook/components": "^6.3.0",
41-
"@storybook/theming": "^6.3.0",
38+
"@storybook/addon-essentials": "^6.4.0-alpha.30",
39+
"@storybook/react": "^6.4.0-alpha.30",
40+
"@storybook/components": "^6.4.0-alpha.30",
41+
"@storybook/theming": "^6.4.0-alpha.30",
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: 30 additions & 5 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

@@ -51,9 +53,32 @@ export const StoryWithParamsAndDecorator: Story<ButtonProps> = (args) => {
5153
return <Button {...args} />;
5254
};
5355
StoryWithParamsAndDecorator.args = {
54-
children: 'foo'
55-
}
56+
children: 'foo',
57+
};
5658
StoryWithParamsAndDecorator.parameters = {
57-
layout: 'centered'
58-
}
59-
StoryWithParamsAndDecorator.decorators = [(StoryFn) => <StoryFn />]
59+
layout: 'centered',
60+
};
61+
StoryWithParamsAndDecorator.decorators = [(StoryFn) => <StoryFn />];
62+
63+
export const CSF3Button: Story<ButtonProps> = {
64+
args: { children: 'foo' },
65+
};
66+
67+
export const CSF3ButtonWithRender: Story<ButtonProps> = {
68+
...CSF3Button,
69+
render: (args: ButtonProps) => (
70+
<div>
71+
<p data-testid="custom-render">I am a custom render function</p>
72+
<Button {...args} />
73+
</div>
74+
),
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+
}
84+
};

example/src/components/Button.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,40 @@ describe('GlobalConfig', () => {
6060
expect(buttonElement).not.toBeNull();
6161
});
6262
});
63+
64+
describe('CSF3', () => {
65+
test('renders with inferred globalRender', () => {
66+
const Primary = composeStory(
67+
stories.CSF3Button,
68+
stories.default
69+
);
70+
71+
render(<Primary>Hello world</Primary>);
72+
const buttonElement = screen.getByText(/Hello world/i);
73+
expect(buttonElement).not.toBeNull();
74+
});
75+
76+
test('renders with custom render function', () => {
77+
const Primary = composeStory(
78+
stories.CSF3ButtonWithRender,
79+
stories.default
80+
);
81+
82+
render(<Primary />);
83+
expect(screen.getByTestId("custom-render")).not.toBeNull();
84+
});
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+
});
99+
});

example/src/internals.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ test('returns composed parameters from story', () => {
1313
expect(StoryWithParamsAndDecorator.args).toEqual(stories.StoryWithParamsAndDecorator.args);
1414
expect(StoryWithParamsAndDecorator.parameters).toEqual({
1515
...stories.StoryWithParamsAndDecorator.parameters,
16-
...globalConfig.parameters
16+
...globalConfig.parameters,
17+
component: stories.default.component
1718
});
1819
expect(StoryWithParamsAndDecorator.decorators).toEqual([
1920
...stories.StoryWithParamsAndDecorator.decorators!,

0 commit comments

Comments
 (0)