diff --git a/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts b/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts new file mode 100644 index 000000000000..8ccb3a093631 --- /dev/null +++ b/code/lib/codemod/src/transforms/__tests__/migrate-to-test-package.test.ts @@ -0,0 +1,44 @@ +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 = (source: string) => transform({ source, path: 'Component.stories.tsx' }).trim(); + +test('replace jest and testing-library with the test package', () => { + const input = dedent` + import { expect } from '@storybook/jest'; + import { within, userEvent } from '@storybook/testing-library'; + `; + + expect(tsTransform(input)).toMatchInlineSnapshot(` + import { expect } from "@storybook/test"; + import { within, userEvent } from "@storybook/test"; + `); +}); + +test('Make jest imports namespace imports', () => { + 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(tsTransform(input)).toMatchInlineSnapshot(` + import * as test, { expect } from "@storybook/test"; + import { within, userEvent } from "@storybook/test"; + + const onFocusMock = test.fn(); + const onSearchMock = test.fn(); + + test.spyOn(window, 'Something'); + `); +}); diff --git a/code/lib/codemod/src/transforms/migrate-to-test-package.ts b/code/lib/codemod/src/transforms/migrate-to-test-package.ts new file mode 100644 index 000000000000..d30776f11155 --- /dev/null +++ b/code/lib/codemod/src/transforms/migrate-to-test-package.ts @@ -0,0 +1,49 @@ +/* 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'; + +export default function transform(info: FileInfo): string { + 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.node.specifiers.push(t.importNamespaceSpecifier(t.identifier('jest'))); + } + } + }); + } + path.get('source').replaceWith(t.stringLiteral('@storybook/test')); + } + }, + Identifier: (path) => { + console.log(path.node.name); + if (path.node.name === 'jest') { + path.replaceWith(t.identifier('test')); + } + }, + }); + + return printCsf(csf).code; +} + +export const parser = 'tsx';