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

Commit 24ee251

Browse files
committed
feat: support CSF3 format
- Add support for stories using object notation
1 parent cb0c7b2 commit 24ee251

File tree

5 files changed

+73
-11
lines changed

5 files changed

+73
-11
lines changed

example/src/components/Button.stories.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,23 @@ export const StoryWithParamsAndDecorator: Story<ButtonProps> = (args) => {
5151
return <Button {...args} />;
5252
};
5353
StoryWithParamsAndDecorator.args = {
54-
children: 'foo'
55-
}
54+
children: 'foo',
55+
};
5656
StoryWithParamsAndDecorator.parameters = {
57-
layout: 'centered'
58-
}
59-
StoryWithParamsAndDecorator.decorators = [(StoryFn) => <StoryFn />]
57+
layout: 'centered',
58+
};
59+
StoryWithParamsAndDecorator.decorators = [(StoryFn) => <StoryFn />];
60+
61+
export const CSF3Button = {
62+
args: { children: 'foo' },
63+
};
64+
65+
export const CSF3ButtonWithRender = {
66+
render: (args: ButtonProps) => (
67+
<div>
68+
<p data-testid="custom-render">I am a custom render function</p>
69+
<Button {...args} />
70+
</div>
71+
),
72+
args: { children: 'foo' },
73+
};

example/src/components/Button.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,28 @@ describe('GlobalConfig', () => {
6060
expect(buttonElement).not.toBeNull();
6161
});
6262
});
63+
64+
describe('CSF3', () => {
65+
test('renders with inferred globalRender', () => {
66+
const Primary = composeStory(
67+
//@ts-ignore TODO: fix this once CSF3 types are created
68+
stories.CSF3Button,
69+
stories.default
70+
) as any;
71+
72+
render(<Primary>Hello world</Primary>);
73+
const buttonElement = screen.getByText(/Hello world/i);
74+
expect(buttonElement).not.toBeNull();
75+
});
76+
77+
test('renders with custom render function', () => {
78+
const Primary = composeStory(
79+
//@ts-ignore TODO: fix this once CSF3 types are created
80+
stories.CSF3ButtonWithRender,
81+
stories.default
82+
) as any;
83+
84+
render(<Primary>Hello world</Primary>);
85+
expect(screen.getByTestId("custom-render")).not.toBeNull();
86+
});
87+
});

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!,

src/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import addons, { mockChannel } from '@storybook/addons';
33
import type { Meta, Story, StoryContext } from '@storybook/react';
44

55
import type { GlobalConfig, StoriesWithPartialProps } from './types';
6+
import { globalRender, isInvalidStory } from './utils';
67

78
// Some addons use the channel api to communicate between manager/preview, and this is a client only feature, therefore we must mock it.
89
addons.setChannel(mockChannel());
@@ -59,13 +60,14 @@ export function composeStory<GenericArgs>(
5960
meta: Meta,
6061
globalConfig: GlobalConfig = globalStorybookConfig
6162
) {
62-
if (typeof story !== 'function') {
63+
64+
if (isInvalidStory(story)) {
6365
throw new Error(
64-
`Cannot compose story due to invalid format. @storybook/testing-react expected a function but received ${typeof story} instead.`
66+
`Cannot compose story due to invalid format. @storybook/testing-react expected a function/object but received ${typeof story} instead.`
6567
);
6668
}
6769

68-
if((story as any).story !== undefined) {
70+
if ((story as any).story !== undefined) {
6971
throw new Error(
7072
`StoryFn.story object-style annotation is not supported. @storybook/testing-react expects hoisted CSF stories.
7173
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#hoisted-csf-annotations`
@@ -79,7 +81,14 @@ export function composeStory<GenericArgs>(
7981
'composeStory does not support legacy style stories (with passArgsFirst = false).'
8082
);
8183
}
82-
return story(context.args as GenericArgs, context);
84+
85+
let renderFn = story
86+
if (typeof story === 'object') {
87+
// @ts-ignore TODO: fix this once CSF3 types are created
88+
renderFn = story.render ?? globalRender;
89+
}
90+
91+
return renderFn(context.args as GenericArgs, context);
8392
};
8493

8594
const combinedDecorators = [
@@ -105,7 +114,8 @@ export function composeStory<GenericArgs>(
105114
const combinedParameters = combineParameters(
106115
globalConfig.parameters || {},
107116
meta.parameters || {},
108-
story.parameters || {}
117+
story.parameters || {},
118+
{ component: meta.component }
109119
)
110120

111121
const combinedArgs = {

src/utils.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import type { FunctionComponent } from 'react';
3+
import type { Story } from '@storybook/react';
4+
5+
export const globalRender: Story = (args, { parameters }) => {
6+
const Component = parameters.component as FunctionComponent;
7+
return <Component {...args} />;
8+
};
9+
10+
const invalidStoryTypes = new Set(['string', 'number', 'boolean', 'symbol']);
11+
12+
export const isInvalidStory = (story?: any) => (!story || Array.isArray(story) || invalidStoryTypes.has(typeof story))

0 commit comments

Comments
 (0)