Skip to content

Commit f743808

Browse files
authored
fix(testing): migration for getJestProjects -> getJestProjectsAsync handles both CJS and ESM (#28299)
This PR updates the Jest migration so it handles both CJS and ESM format for Jest config file. We now generate with ESM so those need to be handled. There are four combinations: 1. `require` (CJS) with `module.export` (CJS) 2. `import` (ESM) with `export default` (ESM) 3. `require` (CJS) with `export default` (ESM) 4. `import` (ESM) with `module.export` (CJS) (1) and (2) should cover almost all cases, and (3) and (4) are there just in case. If the format isn't matching what we generate, then just bail. <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
1 parent 27beba8 commit f743808

File tree

2 files changed

+185
-13
lines changed

2 files changed

+185
-13
lines changed

packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.spec.ts

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => {
99
tree = createTree();
1010
});
1111

12-
it('should replace getJestProjects with getJestProjectsAsync', async () => {
12+
it('should replace getJestProjects with getJestProjectsAsync using `require`', async () => {
1313
tree.write(
1414
'jest.config.ts',
1515
`
@@ -26,14 +26,86 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => {
2626
"
2727
const { getJestProjectsAsync } = require('@nx/jest');
2828
29+
module.exports = async () => ({
30+
projects: await getJestProjectsAsync(),
31+
});
32+
"
33+
`);
34+
});
35+
36+
it('should replace getJestProjects with getJestProjectsAsync using `import`', async () => {
37+
tree.write(
38+
'jest.config.ts',
39+
`
40+
import { getJestProjects } from '@nx/jest';
41+
42+
export default {
43+
projects: getJestProjects(),
44+
};
45+
`
46+
);
47+
await update(tree);
48+
const updatedJestConfig = tree.read('jest.config.ts')?.toString();
49+
expect(updatedJestConfig).toMatchInlineSnapshot(`
50+
"
51+
import { getJestProjectsAsync } from '@nx/jest';
52+
2953
export default async () => ({
3054
projects: await getJestProjectsAsync(),
3155
});
3256
"
3357
`);
3458
});
3559

36-
it('should replace getJestProjects with getJestProjectsAsync with additonal properties', async () => {
60+
it('should replace getJestProjects with getJestProjectsAsync using `require` with `export default`', async () => {
61+
tree.write(
62+
'jest.config.ts',
63+
`
64+
const { getJestProjects } = require('@nx/jest');
65+
66+
export default {
67+
projects: getJestProjects(),
68+
};
69+
`
70+
);
71+
await update(tree);
72+
const updatedJestConfig = tree.read('jest.config.ts')?.toString();
73+
expect(updatedJestConfig).toMatchInlineSnapshot(`
74+
"
75+
const { getJestProjectsAsync } = require('@nx/jest');
76+
77+
export default async () => ({
78+
projects: await getJestProjectsAsync(),
79+
});
80+
"
81+
`);
82+
});
83+
84+
it('should replace getJestProjects with getJestProjectsAsync using `import` with `module.exports`', async () => {
85+
tree.write(
86+
'jest.config.ts',
87+
`
88+
import { getJestProjects } from '@nx/jest';
89+
90+
module.exports = {
91+
projects: getJestProjects(),
92+
};
93+
`
94+
);
95+
await update(tree);
96+
const updatedJestConfig = tree.read('jest.config.ts')?.toString();
97+
expect(updatedJestConfig).toMatchInlineSnapshot(`
98+
"
99+
import { getJestProjectsAsync } from '@nx/jest';
100+
101+
module.exports = async () => ({
102+
projects: await getJestProjectsAsync(),
103+
});
104+
"
105+
`);
106+
});
107+
108+
it('should replace getJestProjects with getJestProjectsAsync with additional properties', async () => {
37109
tree.write(
38110
'jest.config.ts',
39111
`
@@ -53,7 +125,7 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => {
53125
"
54126
const { getJestProjectsAsync } = require('@nx/jest');
55127
56-
export default async () => ({
128+
module.exports = async () => ({
57129
projects: await getJestProjectsAsync(),
58130
filename: __filename,
59131
env: process.env,
@@ -62,4 +134,58 @@ describe('replace-getJestProjects-with-getJestProjectsAsync', () => {
62134
"
63135
`);
64136
});
137+
138+
it('should not update config that are not in supported format', async () => {
139+
// Users don't tend to update the root jest config file since it's only meant to be able to run
140+
// `jest` command from the root of the repo. If the AST doesn't match what we generate
141+
// then bail on the update. Users will still see that `getJestProjects` is deprecated when
142+
// viewing the file.
143+
tree.write(
144+
'jest.config.ts',
145+
`
146+
import { getJestProjects } from '@nx/jest';
147+
148+
const obj = {
149+
projects: getJestProjects(),
150+
};
151+
export default obj
152+
`
153+
);
154+
await update(tree);
155+
let updatedJestConfig = tree.read('jest.config.ts')?.toString();
156+
expect(updatedJestConfig).toMatchInlineSnapshot(`
157+
"
158+
import { getJestProjects } from '@nx/jest';
159+
160+
const obj = {
161+
projects: getJestProjects(),
162+
};
163+
export default obj
164+
"
165+
`);
166+
167+
tree.write(
168+
'jest.config.ts',
169+
`
170+
const { getJestProjects } = require('@nx/jest');
171+
172+
const obj = {
173+
projects: getJestProjects(),
174+
};
175+
module.exports = obj;
176+
`
177+
);
178+
await update(tree);
179+
updatedJestConfig = tree.read('jest.config.ts')?.toString();
180+
expect(updatedJestConfig).toMatchInlineSnapshot(`
181+
"
182+
const { getJestProjects } = require('@nx/jest');
183+
184+
const obj = {
185+
projects: getJestProjects(),
186+
};
187+
module.exports = obj;
188+
"
189+
`);
190+
});
65191
});

packages/jest/src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
import { globAsync, Tree } from '@nx/devkit';
66
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
7-
import { BinaryExpression, ExpressionStatement } from 'typescript';
7+
import {
8+
BinaryExpression,
9+
ExpressionStatement,
10+
ExportAssignment,
11+
} from 'typescript';
812

913
let tsModule: typeof import('typescript');
1014

@@ -26,8 +30,8 @@ export default async function update(tree: Tree) {
2630
true
2731
);
2832

29-
// find the import statement for @nx/jest
30-
const importStatement = sourceFile.statements.find(
33+
// find `require('@nx/jest')` or `import { getJestProjects } from '@nx/jest`
34+
const requireStatement = sourceFile.statements.find(
3135
(statement) =>
3236
tsModule.isVariableStatement(statement) &&
3337
statement.declarationList.declarations.some(
@@ -39,9 +43,14 @@ export default async function update(tree: Tree) {
3943
declaration.initializer.arguments[0].text === '@nx/jest'
4044
)
4145
);
42-
if (importStatement) {
43-
// find export statement with `projects: getJestProjects()`
44-
let exportStatement = sourceFile.statements.find(
46+
const importStatement = sourceFile.statements.find(
47+
(statement) =>
48+
tsModule.isImportDeclaration(statement) &&
49+
statement.moduleSpecifier.getText() === `'@nx/jest'`
50+
);
51+
if (requireStatement || importStatement) {
52+
// find `module.exports` statement with `projects: getJestProjects()`
53+
const moduleExports = sourceFile.statements.find(
4554
(statement) =>
4655
tsModule.isExpressionStatement(statement) &&
4756
tsModule.isBinaryExpression(statement.expression) &&
@@ -65,18 +74,18 @@ export default async function update(tree: Tree) {
6574
)
6675
) as ExpressionStatement;
6776

68-
if (exportStatement) {
77+
if (moduleExports) {
6978
// replace getJestProjects with getJestProjectsAsync in export statement
7079
const rightExpression = (
71-
exportStatement.expression as BinaryExpression
80+
moduleExports.expression as BinaryExpression
7281
).right.getText();
7382
const newExpression = rightExpression.replace(
7483
'getJestProjects()',
7584
'await getJestProjectsAsync()'
7685
);
77-
const newStatement = `export default async () => (${newExpression});`;
86+
const newStatement = `module.exports = async () => (${newExpression});`;
7887
let newContent = oldContent.replace(
79-
exportStatement.getText(),
88+
moduleExports.getText(),
8089
newStatement
8190
);
8291

@@ -87,6 +96,43 @@ export default async function update(tree: Tree) {
8796
);
8897

8998
tree.write(jestConfigPath, newContent);
99+
} else {
100+
// find `export default` statement with `projects: getJestProjects()`
101+
const exportAssignment = sourceFile.statements.find((statement) =>
102+
tsModule.isExportAssignment(statement)
103+
) as ExportAssignment;
104+
const defaultExport =
105+
exportAssignment?.expression &&
106+
tsModule.isObjectLiteralExpression(exportAssignment?.expression)
107+
? exportAssignment?.expression
108+
: null;
109+
const projectProperty = defaultExport?.properties.find(
110+
(property) =>
111+
tsModule.isPropertyAssignment(property) &&
112+
property.name.getText() === 'projects' &&
113+
tsModule.isCallExpression(property.initializer) &&
114+
tsModule.isIdentifier(property.initializer.expression) &&
115+
property.initializer.expression.escapedText === 'getJestProjects'
116+
);
117+
if (projectProperty) {
118+
// replace getJestProjects with getJestProjectsAsync in export statement
119+
const newExpression = defaultExport
120+
.getText()
121+
.replace('getJestProjects()', 'await getJestProjectsAsync()');
122+
const newStatement = `export default async () => (${newExpression});`;
123+
let newContent = oldContent.replace(
124+
exportAssignment.getText(),
125+
newStatement
126+
);
127+
128+
// replace getJestProjects with getJestProjectsAsync in import statement
129+
newContent = newContent.replace(
130+
'getJestProjects',
131+
'getJestProjectsAsync'
132+
);
133+
134+
tree.write(jestConfigPath, newContent);
135+
}
90136
}
91137
}
92138
}

0 commit comments

Comments
 (0)