diff --git a/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.spec.ts b/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.spec.ts index e654118a65884..b136c3b05a1c8 100644 --- a/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.spec.ts +++ b/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.spec.ts @@ -9,7 +9,7 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => { tree = createTree(); }); - it('should replace getJestProjects with getJestProjectsAsync', async () => { + it('should replace getJestProjects with getJestProjectsAsync using `require`', async () => { tree.write( 'jest.config.ts', ` @@ -26,6 +26,30 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => { " const { getJestProjectsAsync } = require('@nx/jest'); + module.exports = async () => ({ + projects: await getJestProjectsAsync(), + }); + " + `); + }); + + it('should replace getJestProjects with getJestProjectsAsync using `import`', async () => { + tree.write( + 'jest.config.ts', + ` + import { getJestProjects } from '@nx/jest'; + + export default { + projects: getJestProjects(), + }; + ` + ); + await update(tree); + const updatedJestConfig = tree.read('jest.config.ts')?.toString(); + expect(updatedJestConfig).toMatchInlineSnapshot(` + " + import { getJestProjectsAsync } from '@nx/jest'; + export default async () => ({ projects: await getJestProjectsAsync(), }); @@ -33,7 +57,55 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => { `); }); - it('should replace getJestProjects with getJestProjectsAsync with additonal properties', async () => { + it('should replace getJestProjects with getJestProjectsAsync using `require` with `export default`', async () => { + tree.write( + 'jest.config.ts', + ` + const { getJestProjects } = require('@nx/jest'); + + export default { + projects: getJestProjects(), + }; + ` + ); + await update(tree); + const updatedJestConfig = tree.read('jest.config.ts')?.toString(); + expect(updatedJestConfig).toMatchInlineSnapshot(` + " + const { getJestProjectsAsync } = require('@nx/jest'); + + export default async () => ({ + projects: await getJestProjectsAsync(), + }); + " + `); + }); + + it('should replace getJestProjects with getJestProjectsAsync using `import` with `module.exports`', async () => { + tree.write( + 'jest.config.ts', + ` + import { getJestProjects } from '@nx/jest'; + + module.exports = { + projects: getJestProjects(), + }; + ` + ); + await update(tree); + const updatedJestConfig = tree.read('jest.config.ts')?.toString(); + expect(updatedJestConfig).toMatchInlineSnapshot(` + " + import { getJestProjectsAsync } from '@nx/jest'; + + module.exports = async () => ({ + projects: await getJestProjectsAsync(), + }); + " + `); + }); + + it('should replace getJestProjects with getJestProjectsAsync with additional properties', async () => { tree.write( 'jest.config.ts', ` @@ -53,7 +125,7 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => { " const { getJestProjectsAsync } = require('@nx/jest'); - export default async () => ({ + module.exports = async () => ({ projects: await getJestProjectsAsync(), filename: __filename, env: process.env, @@ -62,4 +134,58 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => { " `); }); + + it('should not update config that are not in supported format', async () => { + // Users don't tend to update the root jest config file since it's only meant to be able to run + // `jest` command from the root of the repo. If the AST doesn't match what we generate + // then bail on the update. Users will still see that `getJestProjects` is deprecated when + // viewing the file. + tree.write( + 'jest.config.ts', + ` + import { getJestProjects } from '@nx/jest'; + + const obj = { + projects: getJestProjects(), + }; + export default obj + ` + ); + await update(tree); + let updatedJestConfig = tree.read('jest.config.ts')?.toString(); + expect(updatedJestConfig).toMatchInlineSnapshot(` + " + import { getJestProjects } from '@nx/jest'; + + const obj = { + projects: getJestProjects(), + }; + export default obj + " + `); + + tree.write( + 'jest.config.ts', + ` + const { getJestProjects } = require('@nx/jest'); + + const obj = { + projects: getJestProjects(), + }; + module.exports = obj; + ` + ); + await update(tree); + updatedJestConfig = tree.read('jest.config.ts')?.toString(); + expect(updatedJestConfig).toMatchInlineSnapshot(` + " + const { getJestProjects } = require('@nx/jest'); + + const obj = { + projects: getJestProjects(), + }; + module.exports = obj; + " + `); + }); }); diff --git a/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.ts b/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.ts index ae005222f1cdc..a607beda9c617 100644 --- a/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.ts +++ b/packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.ts @@ -4,7 +4,11 @@ import { globAsync, Tree } from '@nx/devkit'; import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; -import { BinaryExpression, ExpressionStatement } from 'typescript'; +import { + BinaryExpression, + ExpressionStatement, + ExportAssignment, +} from 'typescript'; let tsModule: typeof import('typescript'); @@ -26,8 +30,8 @@ export default async function update(tree: Tree) { true ); - // find the import statement for @nx/jest - const importStatement = sourceFile.statements.find( + // find `require('@nx/jest')` or `import { getJestProjects } from '@nx/jest` + const requireStatement = sourceFile.statements.find( (statement) => tsModule.isVariableStatement(statement) && statement.declarationList.declarations.some( @@ -39,9 +43,14 @@ export default async function update(tree: Tree) { declaration.initializer.arguments[0].text === '@nx/jest' ) ); - if (importStatement) { - // find export statement with `projects: getJestProjects()` - let exportStatement = sourceFile.statements.find( + const importStatement = sourceFile.statements.find( + (statement) => + tsModule.isImportDeclaration(statement) && + statement.moduleSpecifier.getText() === `'@nx/jest'` + ); + if (requireStatement || importStatement) { + // find `module.exports` statement with `projects: getJestProjects()` + const moduleExports = sourceFile.statements.find( (statement) => tsModule.isExpressionStatement(statement) && tsModule.isBinaryExpression(statement.expression) && @@ -65,18 +74,18 @@ export default async function update(tree: Tree) { ) ) as ExpressionStatement; - if (exportStatement) { + if (moduleExports) { // replace getJestProjects with getJestProjectsAsync in export statement const rightExpression = ( - exportStatement.expression as BinaryExpression + moduleExports.expression as BinaryExpression ).right.getText(); const newExpression = rightExpression.replace( 'getJestProjects()', 'await getJestProjectsAsync()' ); - const newStatement = `export default async () => (${newExpression});`; + const newStatement = `module.exports = async () => (${newExpression});`; let newContent = oldContent.replace( - exportStatement.getText(), + moduleExports.getText(), newStatement ); @@ -87,6 +96,43 @@ export default async function update(tree: Tree) { ); tree.write(jestConfigPath, newContent); + } else { + // find `export default` statement with `projects: getJestProjects()` + const exportAssignment = sourceFile.statements.find((statement) => + tsModule.isExportAssignment(statement) + ) as ExportAssignment; + const defaultExport = + exportAssignment?.expression && + tsModule.isObjectLiteralExpression(exportAssignment?.expression) + ? exportAssignment?.expression + : null; + const projectProperty = defaultExport?.properties.find( + (property) => + tsModule.isPropertyAssignment(property) && + property.name.getText() === 'projects' && + tsModule.isCallExpression(property.initializer) && + tsModule.isIdentifier(property.initializer.expression) && + property.initializer.expression.escapedText === 'getJestProjects' + ); + if (projectProperty) { + // replace getJestProjects with getJestProjectsAsync in export statement + const newExpression = defaultExport + .getText() + .replace('getJestProjects()', 'await getJestProjectsAsync()'); + const newStatement = `export default async () => (${newExpression});`; + let newContent = oldContent.replace( + exportAssignment.getText(), + newStatement + ); + + // replace getJestProjects with getJestProjectsAsync in import statement + newContent = newContent.replace( + 'getJestProjects', + 'getJestProjectsAsync' + ); + + tree.write(jestConfigPath, newContent); + } } } }