Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<h1>Migration</h1>

- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
- [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated)
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
- [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react)
Expand Down Expand Up @@ -311,6 +312,55 @@

## From version 7.5.0 to 7.6.0

#### Using implicit actions during rendering is deprecated

In Storybook 7, we inferred if the component accepts any action props,
by checking if it starts with `onX` (for example `onClick`), or as configured by `actions.argTypesRegex`.
If that was the case, we would fill in jest spies for those args automatically.

```ts
export default {
component: Button,
};

export const ButtonClick = {
play: async ({ args, canvasElement }) => {
await userEvent.click(within(canvasElement).getByRole('button'));
// args.onClick is a jest spy in 7.0
await expect(args.onClick).toHaveBeenCalled();
},
};
```

In Storybook 8 this feature will be removed, and spies have to added explicitly:

```ts
import { fn } from '@storybook/test';

export default {
component: Button,
args: {
onClick: fn(),
},
};

export const ButtonClick = {
play: async ({ args, canvasElement }) => {
await userEvent.click(within(canvasElement).getByRole('button'));
await expect(args.onClick).toHaveBeenCalled();
},
};
```

For more context, see this RFC:
https://github.com/storybookjs/storybook/discussions/23649

To summarize:

- This makes CSF files less magical and more portable, so that CSF files will render the same in a test environment where docgen is not available.
- This allows users and (test) integrators to run or build storybook without docgen, boosting the user performance and allows tools to give quicker feedback.
- This will make sure that we can one day lazy load docgen, without changing how stories are rendered.

#### typescript.skipBabel deprecated

We will remove the `typescript.skipBabel` option in Storybook 8.0.0. Please use `typescirpt.skipCompiler` instead.
Expand Down
3 changes: 2 additions & 1 deletion code/addons/actions/src/addArgsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const inferActionsFromArgTypesRegex: ArgsEnhancer<Renderer> = (context) =
const {
initialArgs,
argTypes,
id,
parameters: { actions },
} = context;
if (!actions || actions.disable || !actions.argTypesRegex || !argTypes) {
Expand All @@ -32,7 +33,7 @@ export const inferActionsFromArgTypesRegex: ArgsEnhancer<Renderer> = (context) =

return argTypesMatchingRegex.reduce((acc, [name, argType]) => {
if (isInInitialArgs(name, initialArgs)) {
acc[name] = action(name, { implicit: true });
acc[name] = action(name, { implicit: true, id });
}
return acc;
}, {} as Args);
Expand Down
1 change: 1 addition & 0 deletions code/addons/actions/src/models/ActionOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Options {
clearOnStoryChange: boolean;
limit: number;
implicit: boolean;
id: string;
}

export type ActionOptions = Partial<Options> & Partial<TelejsonOptions>;
45 changes: 29 additions & 16 deletions code/addons/actions/src/runtime/action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { v4 as uuidv4 } from 'uuid';
import type { PreviewWeb } from '@storybook/preview-api';
import { addons } from '@storybook/preview-api';
import type { Renderer } from '@storybook/types';
import { global } from '@storybook/global';
import { ImplicitActionsDuringRendering } from '@storybook/core-events/preview-errors';
import { EVENT_ID } from '../constants';
import type { ActionDisplay, ActionOptions, HandlerFunction } from '../models';
import { config } from './configureActions';
Expand Down Expand Up @@ -54,22 +58,31 @@ export function action(name: string, options: ActionOptions = {}): HandlerFuncti
};

const handler = function actionHandler(...args: any[]) {
// TODO: Enable once codemods are finished
// if (options.implicit) {
// const preview =
// '__STORYBOOK_PREVIEW__' in global
// ? (global.__STORYBOOK_PREVIEW__ as PreviewWeb<Renderer>)
// : undefined;
// if (
// preview?.storyRenders.some(
// (render) => render.phase === 'playing' || render.phase === 'rendering'
// )
// ) {
// console.warn(
// 'Can not use implicit actions during rendering or playing of a story.'
// );
// }
// }
if (options.implicit) {
const preview =
'__STORYBOOK_PREVIEW__' in global
? // eslint-disable-next-line no-underscore-dangle
(global.__STORYBOOK_PREVIEW__ as PreviewWeb<Renderer>)
: undefined;
const storyRenderer = preview?.storyRenders.find(
(render) => render.phase === 'playing' || render.phase === 'rendering'
);

if (storyRenderer) {
const deprecated = !window?.FEATURES?.disallowImplicitActionsInRenderV8;
const error = new ImplicitActionsDuringRendering({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
phase: storyRenderer.phase!,
name,
deprecated,
});
if (deprecated) {
console.warn(error);
} else {
throw error;
}
}
}

const channel = addons.getChannel();
// this makes sure that in js enviroments like react native you can still get an id
Expand Down
1 change: 1 addition & 0 deletions code/addons/actions/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare var FEATURES: import('@storybook/types').StorybookConfig['features'];
26 changes: 26 additions & 0 deletions code/lib/core-events/src/errors/preview-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,29 @@ export class MissingStoryAfterHmrError extends StorybookError {
- Also check the browser console and terminal for potential error messages.`;
}
}

export class ImplicitActionsDuringRendering extends StorybookError {
readonly category = Category.PREVIEW_API;

readonly code = 2;

readonly documentation =
'https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#using-implicit-actions-during-rendering-is-deprecated-for-example-in-the-play-function';

constructor(public data: { phase: string; name: string; deprecated: boolean }) {
super();
}

template() {
return dedent`
We detected that you use an implicit action arg during ${this.data.phase} of your story.
${this.data.deprecated ? `\nThis is deprecated and won't work in Storybook 8 anymore.\n` : ``}
Please provide an explicit spy to your args like this:
import { fn } from '@storybook/test';
...
args: {
${this.data.name}: fn()
}
`;
}
}
1 change: 1 addition & 0 deletions code/lib/core-server/src/presets/common-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const features = async (
storyStoreV7: true,
argTypeTargetsV7: true,
legacyDecoratorFileOrder: false,
disallowImplicitActionsInRenderV8: false,
});

export const csfIndexer: Indexer = {
Expand Down
7 changes: 7 additions & 0 deletions code/lib/types/src/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ export interface StorybookConfig {
* Apply decorators from preview.js before decorators from addons or frameworks
*/
legacyDecoratorFileOrder?: boolean;

/**
* Disallow implicit actions during rendering. This will be the default in Storybook 8.
*
* This will make sure that your story renders the same no matter if docgen is enabled or not.
*/
disallowImplicitActionsInRenderV8?: boolean;
};

build?: TestBuildConfig;
Expand Down