Skip to content

Commit

Permalink
Merge pull request #25958 from storybookjs/kasper/test-codemod
Browse files Browse the repository at this point in the history
Codemod: Migrate to test package
  • Loading branch information
kasperpeulen authored Feb 12, 2024
2 parents 5f33810 + b3e2aa7 commit c73de4b
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 44 deletions.
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 @@ -20,6 +20,7 @@ import { wrapRequire } from './wrap-require';
import { reactDocgen } from './react-docgen';
import { removeReactDependency } from './prompt-remove-react';
import { storyshotsMigration } from './storyshots-migration';
import { removeJestTestingLibrary } from './remove-jest-testing-library';

export * from '../types';

Expand All @@ -34,6 +35,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
21 changes: 13 additions & 8 deletions code/nx.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"implicitDependencies": {
"package.json": {
"dependencies": "*",
"devDependencies": "*"
}
},
"pluginsConfig": {
"@nrwl/js": {
"analyzeSourceFiles": false
Expand Down Expand Up @@ -47,10 +41,21 @@
"dependencies": true
}
],
"outputs": ["{projectRoot}/dist"],
"outputs": [
"{projectRoot}/dist"
],
"cache": true
}
},
"nxCloudAccessToken": "NGVmYTkxMmItYzY3OS00MjkxLTk1ZDktZDFmYTFmNmVlNGY4fHJlYWQ=",
"parallel": 1
"namedInputs": {
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"sharedGlobals": [],
"production": [
"default"
]
}
}

0 comments on commit c73de4b

Please sign in to comment.