Skip to content

Commit

Permalink
Merge branch 'next' into valentin/add-webpack5-compiler-automigration
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinpalkovic authored Feb 13, 2024
2 parents 000b7de + 3ae39b6 commit a16f2ba
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 68 deletions.
18 changes: 18 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.x to 8.0.0](#from-version-7x-to-800)
- [Type change in `composeStories` API](#type-change-in-composestories-api)
- [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter)
- [Default keyboard shortcuts changed](#default-keyboard-shortcuts-changed)
- [Manager addons are now rendered with React 18](#manager-addons-are-now-rendered-with-react-18)
Expand Down Expand Up @@ -392,6 +393,23 @@

## From version 7.x to 8.0.0

### Type change in `composeStories` API

There is a TypeScript type change in the `play` function returned from `composeStories` or `composeStory` in `@storybook/react` or `@storybook/vue3`, where before it was always defined, now it is potentially undefined. This means that you might have to make a small change in your code, such as:

```ts
const { Primary } = composeStories(stories)

// before
await Primary.play(...)

// after
await Primary.play?.(...) // if you don't care whether the play function exists
await Primary.play!(...) // if you want a runtime error when the play function does not exist
```

There are plans to make the type of the play function be inferred based on your imported story's play function in a near future, so the types will be 100% accurate.

### Tab addons are now routed to a query parameter

The URL of a tab used to be: `http://localhost:6006/?path=/my-addon-tab/my-story`.
Expand Down
2 changes: 2 additions & 0 deletions code/lib/cli/src/automigrate/fixes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { reactDocgen } from './react-docgen';
import { removeReactDependency } from './prompt-remove-react';
import { storyshotsMigration } from './storyshots-migration';
import { webpack5Migration } from './webpack5-compiler-setup';
import { removeJestTestingLibrary } from './remove-jest-testing-library';

export * from '../types';

Expand All @@ -35,6 +36,7 @@ export const allFixes: Fix[] = [
sbBinary,
sbScripts,
incompatibleAddons,
removeJestTestingLibrary,
removedGlobalClientAPIs,
mdx1to2,
mdxgfm,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { expect, it } from 'vitest';

import type { StorybookConfig } from '@storybook/types';
import type { JsPackageManager } from '@storybook/core-common';
import { removeJestTestingLibrary } from './remove-jest-testing-library';
import ansiRegex from 'ansi-regex';

const check = async ({
packageManager,
main: mainConfig = {},
storybookVersion = '8.0.0',
}: {
packageManager: Partial<JsPackageManager>;
main?: Partial<StorybookConfig> & Record<string, unknown>;
storybookVersion?: string;
}) => {
return removeJestTestingLibrary.check({
packageManager: packageManager as any,
configDir: '',
mainConfig: mainConfig as any,
storybookVersion,
});
};

it('should prompt to install the test package and run the codemod', async () => {
const options = await check({
packageManager: {
getAllDependencies: async () => ({
'@storybook/jest': '1.0.0',
'@storybook/testing-library': '1.0.0',
}),
},
main: { addons: ['@storybook/essentials', '@storybook/addon-info'] },
});

await expect(options).toMatchInlineSnapshot(`
{
"incompatiblePackages": [
"@storybook/jest",
"@storybook/testing-library",
],
}
`);

expect.addSnapshotSerializer({
serialize: (value) => {
const stringVal = typeof value === 'string' ? value : value.toString();
return stringVal.replace(ansiRegex(), '');
},
test: () => true,
});

expect(await removeJestTestingLibrary.prompt(options!)).toMatchInlineSnapshot(`
Attention: We've detected that you're using the following packages which are known to be incompatible with Storybook 8:
- @storybook/jest
- @storybook/testing-library
Install the replacement for those packages: @storybook/test
And run the following codemod:
npx storybook migrate migrate-to-test-package --glob="**/*.stories.@(js|jsx|ts|tsx)"
`);
});
32 changes: 32 additions & 0 deletions code/lib/cli/src/automigrate/fixes/remove-jest-testing-library.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import chalk from 'chalk';
import dedent from 'ts-dedent';
import type { Fix } from '../types';

export const removeJestTestingLibrary: Fix<{ incompatiblePackages: string[] }> = {
id: 'remove-jest-testing-library',
promptOnly: true,
async check({ mainConfig, packageManager }) {
const deps = await packageManager.getAllDependencies();

const incompatiblePackages = Object.keys(deps).filter(
(it) => it === '@storybook/jest' || it === '@storybook/testing-library'
);
return incompatiblePackages.length ? { incompatiblePackages } : null;
},
prompt({ incompatiblePackages }) {
return dedent`
${chalk.bold(
'Attention'
)}: We've detected that you're using the following packages which are known to be incompatible with Storybook 8:
${incompatiblePackages.map((name) => `- ${chalk.cyan(`${name}`)}`).join('\n')}
Install the replacement for those packages: ${chalk.cyan('@storybook/test')}
And run the following codemod:
${chalk.cyan(
'npx storybook migrate migrate-to-test-package --glob="**/*.stories.@(js|jsx|ts|tsx)"'
)}
`;
},
};
2 changes: 2 additions & 0 deletions code/lib/codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"./dist/transforms/csf-hoist-story-annotations.js": "./dist/transforms/csf-hoist-story-annotations.js",
"./dist/transforms/move-builtin-addons.js": "./dist/transforms/move-builtin-addons.js",
"./dist/transforms/mdx-to-csf.js": "./dist/transforms/mdx-to-csf.js",
"./dist/transforms/migrate-to-test-package.js": "./dist/transforms/migrate-to-test-package.js",
"./dist/transforms/storiesof-to-csf.js": "./dist/transforms/storiesof-to-csf.js",
"./dist/transforms/update-addon-info.js": "./dist/transforms/update-addon-info.js",
"./dist/transforms/update-organisation-name.js": "./dist/transforms/update-organisation-name.js",
Expand Down Expand Up @@ -93,6 +94,7 @@
"./src/transforms/csf-2-to-3.ts",
"./src/transforms/csf-hoist-story-annotations.js",
"./src/transforms/mdx-to-csf.ts",
"./src/transforms/migrate-to-test-package.ts",
"./src/transforms/move-builtin-addons.js",
"./src/transforms/storiesof-to-csf.js",
"./src/transforms/update-addon-info.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from 'vitest';
import transform from '../migrate-to-test-package';
import dedent from 'ts-dedent';

expect.addSnapshotSerializer({
serialize: (val: any) => (typeof val === 'string' ? val : val.toString()),
test: () => true,
});

const tsTransform = async (source: string) =>
(await transform({ source, path: 'Component.stories.tsx' })).trim();

test('replace jest and testing-library with the test package', async () => {
const input = dedent`
import { expect } from '@storybook/jest';
import { within, userEvent } from '@storybook/testing-library';
`;

expect(await tsTransform(input)).toMatchInlineSnapshot(`
import { expect } from '@storybook/test';
import { within, userEvent } from '@storybook/test';
`);
});

test('Make jest imports namespace imports', async () => {
const input = dedent`
import { expect, jest } from '@storybook/jest';
import { within, userEvent } from '@storybook/testing-library';
const onFocusMock = jest.fn();
const onSearchMock = jest.fn();
jest.spyOn(window, 'Something');
`;

expect(await tsTransform(input)).toMatchInlineSnapshot(`
import { expect } from '@storybook/test';
import * as test from '@storybook/test';
import { within, userEvent } from '@storybook/test';
const onFocusMock = test.fn();
const onSearchMock = test.fn();
test.spyOn(window, 'Something');
`);
});
10 changes: 1 addition & 9 deletions code/lib/codemod/src/transforms/csf-2-to-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,8 @@ export default async function transform(info: FileInfo, api: API, options: { par
let output = printCsf(csf).code;

try {
const prettierConfig = (await prettier.resolveConfig(info.path)) ?? {
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
};

output = await prettier.format(output, {
...prettierConfig,
...(await prettier.resolveConfig(info.path)),
filepath: info.path,
});
} catch (e) {
Expand Down
13 changes: 3 additions & 10 deletions code/lib/codemod/src/transforms/mdx-to-csf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,17 +291,10 @@ export async function transform(info: FileInfo, baseName: string): Promise<[stri
const newMdx = mdxProcessor.stringify(root);
let output = recast.print(file.path.node).code;

const prettierConfig = (await prettier.resolveConfig(`${info.path}.jsx`)) || {
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
};

const path = `${info.path}.jsx`;
output = await prettier.format(output.trim(), {
...prettierConfig,
filepath: `${info.path}.jsx`,
...(await prettier.resolveConfig(path)),
filepath: path,
});

return [newMdx, output];
Expand Down
63 changes: 63 additions & 0 deletions code/lib/codemod/src/transforms/migrate-to-test-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable no-underscore-dangle */
import type { FileInfo } from 'jscodeshift';
import { loadCsf, printCsf } from '@storybook/csf-tools';
import type { BabelFile } from '@babel/core';
import * as babel from '@babel/core';
import * as t from '@babel/types';
import prettier from 'prettier';

export default async function transform(info: FileInfo) {
const csf = loadCsf(info.source, { makeTitle: (title) => title });
const fileNode = csf._ast;
// @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
const file: BabelFile = new babel.File(
{ filename: info.path },
{ code: info.source, ast: fileNode }
);

file.path.traverse({
ImportDeclaration: (path) => {
if (
path.node.source.value === '@storybook/jest' ||
path.node.source.value === '@storybook/testing-library'
) {
if (path.node.source.value === '@storybook/jest') {
path.get('specifiers').forEach((specifier) => {
if (specifier.isImportSpecifier()) {
const imported = specifier.get('imported');
if (!imported.isIdentifier()) return;
if (imported.node.name === 'jest') {
specifier.remove();
path.insertAfter(
t.importDeclaration(
[t.importNamespaceSpecifier(t.identifier('test'))],
t.stringLiteral('@storybook/test')
)
);
}
}
});
}
path.get('source').replaceWith(t.stringLiteral('@storybook/test'));
}
},
Identifier: (path) => {
if (path.node.name === 'jest') {
path.replaceWith(t.identifier('test'));
}
},
});

let output = printCsf(csf).code;
try {
output = await prettier.format(output, {
...(await prettier.resolveConfig(info.path)),
filepath: info.path,
});
} catch (e) {
console.warn(`Failed applying prettier to ${info.path}.`);
}
return output;
}

export const parser = 'tsx';
9 changes: 1 addition & 8 deletions code/lib/codemod/src/transforms/storiesof-to-csf.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,7 @@ export default async function transformer(file, api, options) {
let output = source;

try {
const prettierConfig = (await prettier.resolveConfig(file.path)) || {
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
};

const prettierConfig = await prettier.resolveConfig(file.path);
output = prettier.format(source, {
...prettierConfig,
parser: jscodeshiftToPrettierParser(options.parser),
Expand Down
13 changes: 4 additions & 9 deletions code/lib/codemod/src/transforms/upgrade-deprecated-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,10 @@ export default async function transform(info: FileInfo, api: API, options: { par
let output = printCsf(csf).code;

try {
const prettierConfig = (await prettier.resolveConfig(info.path)) || {
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
};

output = await prettier.format(output, { ...prettierConfig, filepath: info.path });
output = await prettier.format(output, {
...(await prettier.resolveConfig(info.path)),
filepath: info.path,
});
} catch (e) {
logger.log(`Failed applying prettier to ${info.path}.`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('composeStory', () => {
};

const composedStory = composeStory(Story, meta);
await composedStory.play({ canvasElement: null });
await composedStory.play!({ canvasElement: null });
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
args: {
Expand All @@ -52,16 +52,6 @@ describe('composeStory', () => {
);
});

it('should throw when executing the play function but the story does not have one', async () => {
const Story = () => {};
Story.args = {
primary: true,
};

const composedStory = composeStory(Story, meta);
expect(composedStory.play({ canvasElement: null })).rejects.toThrow();
});

it('should throw an error if Story is undefined', () => {
expect(() => {
// @ts-expect-error (invalid input)
Expand Down
17 changes: 7 additions & 10 deletions code/lib/preview-api/src/modules/store/csf/portable-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,13 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
parameters: story.parameters as Parameters,
argTypes: story.argTypes as StrictArgTypes<TArgs>,
id: story.id,
play: (async (extraContext: ComposedStoryPlayContext<TRenderer, TArgs>) => {
if (story.playFunction === undefined) {
throw new Error('The story does not have a play function. Make sure to add one.');
}

await story.playFunction({
...context,
...extraContext,
});
}) as unknown as ComposedStoryPlayFn<TRenderer, Partial<TArgs>>,
play: story.playFunction
? ((async (extraContext: ComposedStoryPlayContext<TRenderer, TArgs>) =>
story.playFunction!({
...context,
...extraContext,
})) as unknown as ComposedStoryPlayFn<TRenderer, Partial<TArgs>>)
: undefined,
}
);

Expand Down
Loading

0 comments on commit a16f2ba

Please sign in to comment.