diff --git a/packages/codemod/package.json b/packages/codemod/package.json
index bd24f7a535..aefb3a5caf 100644
--- a/packages/codemod/package.json
+++ b/packages/codemod/package.json
@@ -34,9 +34,12 @@
"devDependencies": {
"@commercetools-frontend/application-components": "workspace:*",
"@emotion/react": "^11.11.4",
+ "@jest/globals": "29.7.0",
"@tsconfig/node16": "^16.1.1",
"@types/glob": "8.1.0",
"@types/jscodeshift": "0.11.11",
+ "@types/prop-types": "^15.7.5",
+ "prop-types": "15.8.1",
"rimraf": "5.0.7",
"typescript": "5.0.4"
},
diff --git a/packages/codemod/test/__snapshots__/transforms.spec.ts.snap b/packages/codemod/test/__snapshots__/transforms.spec.ts.snap
index 2f96ae8b4b..be9ba4caa4 100644
--- a/packages/codemod/test/__snapshots__/transforms.spec.ts.snap
+++ b/packages/codemod/test/__snapshots__/transforms.spec.ts.snap
@@ -1,6 +1,141 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`testing transform "remove-deprecated-modal-level-props" transforms correctly 1`] = `
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/simple-arrow-function.jsx 1`] = `
+"import * as PropTypes from 'prop-types';
+
+const MyComponent = ({ foo = 'bar', ...props }) => {
+ return
{foo}
;
+};
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/simple-arrow-function.tsx 1`] = `
+"type TMyComponentProps = {
+ foo?: string;
+};
+
+const MyComponent = ({ foo = 'bar', ...props }: TMyComponentProps) => {
+ return {foo}
;
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/simple-classic-function.jsx 1`] = `
+"import * as PropTypes from 'prop-types';
+
+function MyComponent({ foo = 'bar', ...props }) {
+ return {foo}
;
+}
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/simple-classic-function.tsx 1`] = `
+"type TMyComponentProps = {
+ foo?: string;
+};
+
+function MyComponent({ foo = 'bar', ...props }: TMyComponentProps) {
+ return {foo}
;
+}"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/with-already-destructured-props.jsx 1`] = `
+"import * as PropTypes from 'prop-types';
+
+const MyComponent = ({ foo = 'bar', baz, ...props }) => {
+ return (
+
+ - {foo}
+ - {props.bar}
+ - {baz}
+
+ );
+};
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+ baz: PropTypes.string,
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/with-already-destructured-props.tsx 1`] = `
+"type TMyComponentProps = {
+ foo?: string;
+ bar: string;
+ baz: string;
+};
+
+const MyComponent = ({ foo = 'bar', baz, ...props }: TMyComponentProps) => {
+ return (
+
+ - {foo}
+ - {props.bar}
+ - {baz}
+
+ );
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/with-subcomponent.jsx 1`] = `
+"import * as PropTypes from 'prop-types';
+
+function MySubcomponent(props) {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+
+ );
+}
+MySubcomponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+};
+
+function MyComponent({ foo = 'bar', ...props }) {
+ return (
+
+
+
+ );
+}
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+};"
+`;
+
+exports[`testing transform "react-default-props-migration" transforms correctly: react-default-props/with-subcomponent.tsx 1`] = `
+"type MySubcomponentProps = {
+ foo: string;
+ bar: string;
+};
+function MySubcomponent(props: MySubcomponentProps) {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+
+ );
+}
+
+type TMyComponentProps = {
+ foo?: string;
+ bar: string;
+};
+function MyComponent({ foo = 'bar', ...props }: TMyComponentProps) {
+ return (
+
+
+
+ );
+}"
+`;
+
+exports[`testing transform "remove-deprecated-modal-level-props" transforms correctly: remove-deprecated-modal-level-props.tsx 1`] = `
"import {
InfoModalPage,
FormModalPage,
@@ -43,9 +178,9 @@ function Modal() {
export default Modal;"
`;
-exports[`testing transform "rename-js-to-jsx" transforms correctly 1`] = `""`;
+exports[`testing transform "rename-js-to-jsx" transforms correctly: rename-js-to-jsx.js 1`] = `""`;
-exports[`testing transform "rename-mod-css-to-module-css" transforms correctly 1`] = `
+exports[`testing transform "rename-mod-css-to-module-css" transforms correctly: rename-mod-css-to-module-css.jsx 1`] = `
"// eslint-disable-next-line import/extensions, import/no-unresolved, no-unused-vars
import styles from './styles.module.css';"
`;
diff --git a/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.jsx b/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.jsx
new file mode 100644
index 0000000000..6a9c84cf37
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.jsx
@@ -0,0 +1,11 @@
+import * as PropTypes from 'prop-types';
+
+const MyComponent = (props) => {
+ return {props.foo}
;
+};
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.tsx b/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.tsx
new file mode 100644
index 0000000000..2875d11fdb
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/simple-arrow-function.tsx
@@ -0,0 +1,11 @@
+type TMyComponentProps = {
+ foo: string;
+};
+
+const MyComponent = (props: TMyComponentProps) => {
+ return {props.foo}
;
+};
+
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/simple-classic-function.jsx b/packages/codemod/test/fixtures/react-default-props/simple-classic-function.jsx
new file mode 100644
index 0000000000..5568aa4439
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/simple-classic-function.jsx
@@ -0,0 +1,11 @@
+import * as PropTypes from 'prop-types';
+
+function MyComponent(props) {
+ return {props.foo}
;
+}
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/simple-classic-function.tsx b/packages/codemod/test/fixtures/react-default-props/simple-classic-function.tsx
new file mode 100644
index 0000000000..ffa95843f9
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/simple-classic-function.tsx
@@ -0,0 +1,11 @@
+type TMyComponentProps = {
+ foo: string;
+};
+
+function MyComponent(props: TMyComponentProps) {
+ return {props.foo}
;
+}
+
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.jsx b/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.jsx
new file mode 100644
index 0000000000..5d6c0ed495
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.jsx
@@ -0,0 +1,19 @@
+import * as PropTypes from 'prop-types';
+
+const MyComponent = ({ baz, ...props }) => {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+ - {baz}
+
+ );
+};
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+ baz: PropTypes.string,
+};
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.tsx b/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.tsx
new file mode 100644
index 0000000000..af2dd5e3c7
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/with-already-destructured-props.tsx
@@ -0,0 +1,19 @@
+type TMyComponentProps = {
+ foo: string;
+ bar: string;
+ baz: string;
+};
+
+const MyComponent = ({ baz, ...props }: TMyComponentProps) => {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+ - {baz}
+
+ );
+};
+
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/with-subcomponent.jsx b/packages/codemod/test/fixtures/react-default-props/with-subcomponent.jsx
new file mode 100644
index 0000000000..3fe07d2162
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/with-subcomponent.jsx
@@ -0,0 +1,29 @@
+import * as PropTypes from 'prop-types';
+
+function MySubcomponent(props) {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+
+ );
+}
+MySubcomponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+};
+
+function MyComponent(props) {
+ return (
+
+
+
+ );
+}
+MyComponent.propTypes = {
+ foo: PropTypes.string,
+ bar: PropTypes.string,
+};
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/fixtures/react-default-props/with-subcomponent.tsx b/packages/codemod/test/fixtures/react-default-props/with-subcomponent.tsx
new file mode 100644
index 0000000000..269e47702a
--- /dev/null
+++ b/packages/codemod/test/fixtures/react-default-props/with-subcomponent.tsx
@@ -0,0 +1,27 @@
+type MySubcomponentProps = {
+ foo: string;
+ bar: string;
+};
+function MySubcomponent(props: MySubcomponentProps) {
+ return (
+
+ - {props.foo}
+ - {props.bar}
+
+ );
+}
+
+type TMyComponentProps = {
+ foo: string;
+ bar: string;
+};
+function MyComponent(props: TMyComponentProps) {
+ return (
+
+
+
+ );
+}
+MyComponent.defaultProps = {
+ foo: 'bar',
+};
diff --git a/packages/codemod/test/test-utils.ts b/packages/codemod/test/test-utils.ts
new file mode 100644
index 0000000000..56b514d928
--- /dev/null
+++ b/packages/codemod/test/test-utils.ts
@@ -0,0 +1,100 @@
+import { readFileSync } from 'node:fs';
+import { extname } from 'node:path';
+import { expect } from '@jest/globals';
+import { API, FileInfo, Options } from 'jscodeshift';
+
+type TTransformerModule =
+ | {
+ default: (
+ fileInfo: Partial,
+ api: API,
+ options: Options
+ ) => Promise | string;
+ parser: string;
+ }
+ | ((
+ fileInfo: Partial,
+ api: API,
+ options: Options
+ ) => Promise | string);
+
+type TApplyTransformParams = {
+ transformerModule: TTransformerModule;
+ transformerOptions?: Options;
+ codeToTransform: Partial;
+ testOptions?: {
+ parser?: string;
+ };
+};
+
+function applyTransform({
+ transformerModule,
+ transformerOptions,
+ codeToTransform,
+ testOptions = {},
+}: TApplyTransformParams) {
+ // Handle ES6 modules using default export for the transform
+ const transform =
+ 'default' in transformerModule
+ ? transformerModule.default
+ : transformerModule;
+ const moduleParser =
+ 'default' in transformerModule ? transformerModule.parser : null;
+
+ // Jest resets the module registry after each test, so we need to always get
+ // a fresh copy of jscodeshift on every test run.
+ let jscodeshift = require('jscodeshift');
+ if (testOptions.parser || moduleParser) {
+ jscodeshift = jscodeshift.withParser(testOptions.parser || moduleParser);
+ }
+
+ const transformationResult = transform(
+ codeToTransform,
+ {
+ jscodeshift,
+ j: jscodeshift,
+ stats: () => {},
+ report: (msg: string) => console.log(msg), // Add the missing report function
+ },
+ transformerOptions || {}
+ );
+
+ if (transformationResult instanceof Promise) {
+ return transformationResult.then((result) => (result || '').trim());
+ }
+
+ return (transformationResult || '').trim();
+}
+
+type TRunSnapshotTestParams = {
+ transformerModule: TTransformerModule;
+ transformerOptions?: Options;
+ codeToTransformPath: string;
+};
+export function runSnapshotTest({
+ transformerModule,
+ transformerOptions,
+ codeToTransformPath,
+}: TRunSnapshotTestParams): Promise | undefined {
+ const source = readFileSync(codeToTransformPath, 'utf8');
+ const transformationResult = applyTransform({
+ transformerModule,
+ transformerOptions,
+ codeToTransform: {
+ path: codeToTransformPath,
+ source,
+ },
+ testOptions: {
+ parser: extname(codeToTransformPath).slice(1),
+ },
+ });
+
+ if (transformationResult instanceof Promise) {
+ return transformationResult.then((result) => {
+ expect(result).toMatchSnapshot();
+ });
+ }
+
+ expect(transformationResult).toMatchSnapshot();
+ return undefined;
+}
diff --git a/packages/codemod/test/transforms.spec.ts b/packages/codemod/test/transforms.spec.ts
index 48f53d809d..42973431b3 100644
--- a/packages/codemod/test/transforms.spec.ts
+++ b/packages/codemod/test/transforms.spec.ts
@@ -3,8 +3,7 @@ jest.autoMockOff();
import fs from 'fs';
import path from 'path';
-// @ts-ignore
-import { runSnapshotTest } from 'jscodeshift/dist/testUtils';
+import { runSnapshotTest } from './test-utils';
const fixturesPath = path.join(__dirname, 'fixtures');
@@ -22,6 +21,14 @@ describe.each`
${'remove-deprecated-modal-level-props'} | ${'remove-deprecated-modal-level-props.tsx'}
${'rename-js-to-jsx'} | ${'rename-js-to-jsx.js'}
${'rename-mod-css-to-module-css'} | ${'rename-mod-css-to-module-css.jsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/simple-classic-function.jsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/simple-classic-function.tsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/simple-arrow-function.jsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/simple-arrow-function.tsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/with-subcomponent.jsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/with-subcomponent.tsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/with-already-destructured-props.jsx'}
+ ${'react-default-props-migration'} | ${'react-default-props/with-already-destructured-props.tsx'}
`('testing transform "$transformName"', ({ transformName, fixtureName }) => {
// Assumes transform is one level up from __tests__ directory
const module = require(path.join(
@@ -32,6 +39,7 @@ describe.each`
const inputPath = path.join(fixturesPath, fixtureName);
beforeEach(() => {
+ jest.spyOn(console, 'log').mockImplementation();
switch (transformName) {
case 'rename-js-to-jsx':
if (!doesFileExist(inputPath)) {
@@ -52,12 +60,10 @@ describe.each`
}
});
- it('transforms correctly', () => {
- const source = fs.readFileSync(inputPath, 'utf8');
-
- runSnapshotTest(module, null, {
- source,
- path: inputPath,
+ it(`transforms correctly: ${fixtureName}`, async () => {
+ return runSnapshotTest({
+ transformerModule: module,
+ codeToTransformPath: inputPath,
});
});
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 260e32820e..9956dc0966 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2087,6 +2087,9 @@ importers:
'@emotion/react':
specifier: ^11.11.4
version: 11.11.4(@types/react@17.0.83)(react@17.0.2)
+ '@jest/globals':
+ specifier: 29.7.0
+ version: 29.7.0
'@tsconfig/node16':
specifier: ^16.1.1
version: 16.1.1
@@ -2096,6 +2099,12 @@ importers:
'@types/jscodeshift':
specifier: 0.11.11
version: 0.11.11
+ '@types/prop-types':
+ specifier: ^15.7.5
+ version: 15.7.5
+ prop-types:
+ specifier: 15.8.1
+ version: 15.8.1
rimraf:
specifier: 5.0.7
version: 5.0.7
@@ -13696,7 +13705,7 @@ packages:
jest-haste-map: 29.7.0
jest-regex-util: 29.6.3
jest-util: 29.7.0
- micromatch: 4.0.5
+ micromatch: 4.0.8
pirates: 4.0.5
slash: 3.0.0
write-file-atomic: 4.0.2
@@ -18018,7 +18027,6 @@ packages:
engines: {node: '>=8'}
dependencies:
fill-range: 7.1.1
- dev: false
/breakword@1.0.5:
resolution: {integrity: sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==}
@@ -21473,7 +21481,6 @@ packages:
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
- dev: false
/finalhandler@1.1.2:
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
@@ -24079,7 +24086,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@babel/core': 7.25.2
- '@babel/generator': 7.24.5
+ '@babel/generator': 7.25.6
'@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2)
'@babel/plugin-syntax-typescript': 7.25.4(@babel/core@7.25.2)
'@babel/types': 7.25.6
@@ -24097,7 +24104,7 @@ packages:
jest-util: 29.7.0
natural-compare: 1.4.0
pretty-format: 29.7.0
- semver: 7.6.3
+ semver: 7.6.2
transitivePeerDependencies:
- supports-color
@@ -25701,7 +25708,6 @@ packages:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
- dev: false
/mime-db@1.33.0:
resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}