fix(testing): handle existing jest preset file correctly (#23437)

<!-- 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` -->

## 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 #20449
This commit is contained in:
Leosvel Pérez Espinosa 2024-05-17 16:26:29 +02:00 committed by GitHub
parent 937019b172
commit 217a349adc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 132 additions and 74 deletions

5
packages/jest/preset.ts Normal file
View File

@ -0,0 +1,5 @@
import { nxPreset } from './preset/jest-preset';
export { nxPreset };
export default nxPreset;

View File

@ -262,19 +262,33 @@ describe('jestProject', () => {
expect(tree.exists('libs/lib1/jest.config.js')).toBeTruthy(); expect(tree.exists('libs/lib1/jest.config.js')).toBeTruthy();
}); });
it('should always use jest.preset.js with --js', async () => { it('should generate a jest.preset.js when it does not exist', async () => {
tree.write('jest.preset.ts', '');
await configurationGenerator(tree, { await configurationGenerator(tree, {
...defaultOptions, ...defaultOptions,
project: 'lib1', project: 'lib1',
js: true, js: true,
} as JestProjectSchema); } as JestProjectSchema);
expect(tree.exists('libs/lib1/jest.config.js')).toBeTruthy(); expect(tree.exists('libs/lib1/jest.config.js')).toBeTruthy();
expect(tree.exists('jest.preset.js')).toBeTruthy();
expect(tree.read('libs/lib1/jest.config.js', 'utf-8')).toContain( expect(tree.read('libs/lib1/jest.config.js', 'utf-8')).toContain(
"preset: '../../jest.preset.js'," "preset: '../../jest.preset.js',"
); );
}); });
it('should not override existing jest preset file and should point to it in jest.config files', async () => {
tree.write('jest.preset.mjs', 'export default {}');
await configurationGenerator(tree, {
...defaultOptions,
project: 'lib1',
js: true,
} as JestProjectSchema);
expect(tree.exists('libs/lib1/jest.config.js')).toBeTruthy();
expect(tree.exists('jest.preset.mjs')).toBeTruthy();
expect(tree.read('libs/lib1/jest.config.js', 'utf-8')).toContain(
"preset: '../../jest.preset.mjs',"
);
});
it('should use module.exports with --js flag', async () => { it('should use module.exports with --js flag', async () => {
await configurationGenerator(tree, { await configurationGenerator(tree, {
...defaultOptions, ...defaultOptions,

View File

@ -17,7 +17,7 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { initGenerator as jsInitGenerator } from '@nx/js'; import { initGenerator as jsInitGenerator } from '@nx/js';
import { JestPluginOptions } from '../../plugins/plugin'; import { JestPluginOptions } from '../../plugins/plugin';
import { isPresetCjs } from '../../utils/config/is-preset-cjs'; import { getPresetExt } from '../../utils/config/config-file';
const schemaDefaults = { const schemaDefaults = {
setupFile: 'none', setupFile: 'none',
@ -90,7 +90,7 @@ export async function configurationGeneratorInternal(
tasks.push(ensureDependencies(tree, options)); tasks.push(ensureDependencies(tree, options));
} }
const presetExt = isPresetCjs(tree) ? 'cjs' : 'js'; const presetExt = getPresetExt(tree);
await createJestConfig(tree, options, presetExt); await createJestConfig(tree, options, presetExt);
checkForTestTarget(tree, options); checkForTestTarget(tree, options);

View File

@ -11,7 +11,7 @@ projects: await getJestProjectsAsync()
exports[`createJestConfig should generate files 2`] = ` exports[`createJestConfig should generate files 2`] = `
"const nxPreset = require('@nx/jest/preset').default; "const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset }" module.exports = { ...nxPreset };"
`; `;
exports[`createJestConfig should generate files with --js flag 1`] = ` exports[`createJestConfig should generate files with --js flag 1`] = `
@ -25,5 +25,5 @@ projects: await getJestProjectsAsync()
exports[`createJestConfig should generate files with --js flag 2`] = ` exports[`createJestConfig should generate files with --js flag 2`] = `
"const nxPreset = require('@nx/jest/preset').default; "const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset }" module.exports = { ...nxPreset };"
`; `;

View File

@ -5,12 +5,13 @@ import {
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { join } from 'path'; import { join } from 'path';
import type { JestPresetExtension } from '../../../utils/config/config-file';
import { NormalizedJestProjectSchema } from '../schema'; import { NormalizedJestProjectSchema } from '../schema';
export function createFiles( export function createFiles(
tree: Tree, tree: Tree,
options: NormalizedJestProjectSchema, options: NormalizedJestProjectSchema,
presetExt: 'cjs' | 'js' presetExt: JestPresetExtension
) { ) {
const projectConfig = readProjectConfiguration(tree, options.project); const projectConfig = readProjectConfiguration(tree, options.project);

View File

@ -8,23 +8,34 @@ import {
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
import { findRootJestConfig } from '../../../utils/config/find-root-jest-files'; import {
findRootJestConfig,
type JestPresetExtension,
} from '../../../utils/config/config-file';
import type { NormalizedJestProjectSchema } from '../schema'; import type { NormalizedJestProjectSchema } from '../schema';
export async function createJestConfig( export async function createJestConfig(
tree: Tree, tree: Tree,
options: Partial<NormalizedJestProjectSchema>, options: Partial<NormalizedJestProjectSchema>,
presetExt: 'cjs' | 'js' presetExt: JestPresetExtension
) { ) {
if (!tree.exists(`jest.preset.${presetExt}`)) { if (!tree.exists(`jest.preset.${presetExt}`)) {
// preset is always js file. if (presetExt === 'mjs') {
tree.write( tree.write(
`jest.preset.${presetExt}`, `jest.preset.${presetExt}`,
` `import { nxPreset } from '@nx/jest/preset.js';
const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset }` export default { ...nxPreset };`
); );
} else {
// js or cjs
tree.write(
`jest.preset.${presetExt}`,
`const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset };`
);
}
} }
if (options.rootProject) { if (options.rootProject) {
// we don't want any config to be made because the `configurationGenerator` will do it. // we don't want any config to be made because the `configurationGenerator` will do it.

View File

@ -1,7 +1,7 @@
import { findRootJestConfig } from '../../../utils/config/find-root-jest-files'; import { readProjectConfiguration, type Tree } from '@nx/devkit';
import { NormalizedJestProjectSchema } from '../schema'; import { findRootJestConfig } from '../../../utils/config/config-file';
import { addPropertyToJestConfig } from '../../../utils/config/update-config'; import { addPropertyToJestConfig } from '../../../utils/config/update-config';
import { readProjectConfiguration, Tree } from '@nx/devkit'; import type { NormalizedJestProjectSchema } from '../schema';
function isUsingUtilityFunction(host: Tree) { function isUsingUtilityFunction(host: Tree) {
const rootConfig = findRootJestConfig(host); const rootConfig = findRootJestConfig(host);

View File

@ -1,5 +1,6 @@
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
createProjectGraphAsync,
formatFiles, formatFiles,
readNxJson, readNxJson,
removeDependenciesFromPackageJson, removeDependenciesFromPackageJson,
@ -7,15 +8,17 @@ import {
updateNxJson, updateNxJson,
type GeneratorCallback, type GeneratorCallback,
type Tree, type Tree,
createProjectGraphAsync,
} from '@nx/devkit'; } from '@nx/devkit';
import { createNodes } from '../../plugins/plugin';
import { jestVersion, nxVersion } from '../../utils/versions';
import { isPresetCjs } from '../../utils/config/is-preset-cjs';
import type { JestInitSchema } from './schema';
import { addPlugin } from '@nx/devkit/src/utils/add-plugin'; import { addPlugin } from '@nx/devkit/src/utils/add-plugin';
import { createNodes } from '../../plugins/plugin';
import {
getPresetExt,
type JestPresetExtension,
} from '../../utils/config/config-file';
import { jestVersion, nxVersion } from '../../utils/versions';
import type { JestInitSchema } from './schema';
function updateProductionFileSet(tree: Tree, presetExt: 'cjs' | 'js') { function updateProductionFileSet(tree: Tree) {
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
const productionFileSet = nxJson.namedInputs?.production; const productionFileSet = nxJson.namedInputs?.production;
@ -40,7 +43,7 @@ function updateProductionFileSet(tree: Tree, presetExt: 'cjs' | 'js') {
updateNxJson(tree, nxJson); updateNxJson(tree, nxJson);
} }
function addJestTargetDefaults(tree: Tree, presetEnv: 'cjs' | 'js') { function addJestTargetDefaults(tree: Tree, presetExt: JestPresetExtension) {
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
nxJson.targetDefaults ??= {}; nxJson.targetDefaults ??= {};
@ -53,7 +56,7 @@ function addJestTargetDefaults(tree: Tree, presetEnv: 'cjs' | 'js') {
nxJson.targetDefaults['@nx/jest:jest'].inputs ??= [ nxJson.targetDefaults['@nx/jest:jest'].inputs ??= [
'default', 'default',
productionFileSet ? '^production' : '^default', productionFileSet ? '^production' : '^default',
`{workspaceRoot}/jest.preset.${presetEnv}`, `{workspaceRoot}/jest.preset.${presetExt}`,
]; ];
nxJson.targetDefaults['@nx/jest:jest'].options ??= { nxJson.targetDefaults['@nx/jest:jest'].options ??= {
@ -96,10 +99,10 @@ export async function jestInitGeneratorInternal(
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault; options.addPlugin ??= addPluginDefault;
const presetExt = isPresetCjs(tree) ? 'cjs' : 'js'; const presetExt = getPresetExt(tree);
if (!tree.exists('jest.preset.js') && !tree.exists('jest.preset.cjs')) { if (!tree.exists(`jest.preset.${presetExt}`)) {
updateProductionFileSet(tree, presetExt); updateProductionFileSet(tree);
if (options.addPlugin) { if (options.addPlugin) {
await addPlugin( await addPlugin(
tree, tree,

View File

@ -0,0 +1,49 @@
import { readJson, type Tree } from '@nx/devkit';
export const jestConfigExtensions = [
'js',
'ts',
'mjs',
'cjs',
'mts',
'cts',
] as const;
export type JestConfigExtension = typeof jestConfigExtensions[number];
export const jestPresetExtensions = ['js', 'cjs', 'mjs'] as const;
export type JestPresetExtension = typeof jestPresetExtensions[number];
export function getPresetExt(tree: Tree): JestPresetExtension {
const ext = jestPresetExtensions.find((ext) =>
tree.exists(`jest.preset.${ext}`)
);
if (ext) {
return ext;
}
const rootPkgJson = readJson(tree, 'package.json');
if (rootPkgJson.type && rootPkgJson.type === 'module') {
// use cjs if package.json type is module
return 'cjs';
}
// default to js
return 'js';
}
export function findRootJestConfig(tree: Tree): string | null {
const ext = jestConfigExtensions.find((ext) =>
tree.exists(`jest.config.${ext}`)
);
return ext ? `jest.config.${ext}` : null;
}
export function findRootJestPreset(tree: Tree): string | null {
const ext = jestPresetExtensions.find((ext) =>
tree.exists(`jest.preset.${ext}`)
);
return ext ? `jest.preset.${ext}` : null;
}

View File

@ -1,25 +0,0 @@
import { Tree } from '@nx/devkit';
export function findRootJestConfig(tree: Tree): string | null {
if (tree.exists('jest.config.js')) {
return 'jest.config.js';
}
if (tree.exists('jest.config.ts')) {
return 'jest.config.ts';
}
return null;
}
export function findRootJestPreset(tree: Tree): string | null {
if (tree.exists('jest.preset.js')) {
return 'jest.preset.js';
}
if (tree.exists('jest.preset.cjs')) {
return 'jest.preset.cjs';
}
return null;
}

View File

@ -1,14 +0,0 @@
import { type Tree, readJson } from '@nx/devkit';
export function isPresetCjs(tree: Tree) {
if (tree.exists('jest.preset.cjs')) {
return true;
}
const rootPkgJson = readJson(tree, 'package.json');
if (rootPkgJson.type && rootPkgJson.type === 'module') {
return true;
}
return false;
}

View File

@ -20,7 +20,7 @@ if (swcJestConfig.swcrc === undefined) {
<% if(js) {%>module.exports =<% } else { %>export default<% } %> { <% if(js) {%>module.exports =<% } else { %>export default<% } %> {
displayName: '<%= project %>', displayName: '<%= project %>',
preset: '<%= offsetFromRoot %>jest.preset.js', preset: '<%= offsetFromRoot %><%= jestPreset %>',
transform: { transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
}, },

View File

@ -603,10 +603,12 @@ function replaceJestConfig(tree: Tree, options: NormalizedSchema) {
if (tree.exists(existingJestConfig)) { if (tree.exists(existingJestConfig)) {
tree.delete(existingJestConfig); tree.delete(existingJestConfig);
} }
const jestPreset = findRootJestPreset(tree) ?? 'jest.presets.js';
// replace with JS:SWC specific jest config // replace with JS:SWC specific jest config
generateFiles(tree, filesDir, options.projectRoot, { generateFiles(tree, filesDir, options.projectRoot, {
ext: options.js ? 'js' : 'ts', ext: options.js ? 'js' : 'ts',
jestPreset,
js: !!options.js, js: !!options.js,
project: options.name, project: options.name,
offsetFromRoot: offsetFromRoot(options.projectRoot), offsetFromRoot: offsetFromRoot(options.projectRoot),
@ -1013,4 +1015,12 @@ function logNxReleaseDocsInfo() {
}); });
} }
function findRootJestPreset(tree: Tree): string | null {
const ext = ['js', 'cjs', 'mjs'].find((ext) =>
tree.exists(`jest.preset.${ext}`)
);
return ext ? `jest.preset.${ext}` : null;
}
export default libraryGenerator; export default libraryGenerator;

View File

@ -28,6 +28,7 @@ import {
replaceOverridesInLintConfig, replaceOverridesInLintConfig,
} from '@nx/eslint/src/generators/utils/eslint-file'; } from '@nx/eslint/src/generators/utils/eslint-file';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { findRootJestPreset } from '@nx/jest/src/utils/config/config-file';
export async function e2eProjectGenerator(host: Tree, options: Schema) { export async function e2eProjectGenerator(host: Tree, options: Schema) {
return await e2eProjectGeneratorInternal(host, { return await e2eProjectGeneratorInternal(host, {
@ -90,6 +91,7 @@ export async function e2eProjectGeneratorInternal(
}); });
} }
const jestPreset = findRootJestPreset(host) ?? 'jest.preset.js';
if (options.projectType === 'server') { if (options.projectType === 'server') {
generateFiles( generateFiles(
host, host,
@ -99,6 +101,7 @@ export async function e2eProjectGeneratorInternal(
...options, ...options,
...names(options.rootProject ? 'server' : options.project), ...names(options.rootProject ? 'server' : options.project),
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
jestPreset,
tmpl: '', tmpl: '',
} }
); );
@ -127,6 +130,7 @@ export async function e2eProjectGeneratorInternal(
...names(options.rootProject ? 'cli' : options.project), ...names(options.rootProject ? 'cli' : options.project),
mainFile, mainFile,
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
jestPreset,
tmpl: '', tmpl: '',
} }
); );

View File

@ -1,7 +1,7 @@
/* eslint-disable */ /* eslint-disable */
export default { export default {
displayName: '<%= e2eProjectName %>', displayName: '<%= e2eProjectName %>',
preset: '<%= offsetFromRoot %>/jest.preset.js', preset: '<%= offsetFromRoot %><%= jestPreset %>',
setupFiles: ['<rootDir>/src/test-setup.ts'], setupFiles: ['<rootDir>/src/test-setup.ts'],
testEnvironment: 'node', testEnvironment: 'node',
transform: { transform: {
@ -10,5 +10,5 @@ export default {
}], }],
}, },
moduleFileExtensions: ['ts', 'js', 'html'], moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '<%= offsetFromRoot %>/coverage/<%= e2eProjectName %>', coverageDirectory: '<%= offsetFromRoot %>coverage/<%= e2eProjectName %>',
}; };

View File

@ -1,7 +1,7 @@
/* eslint-disable */ /* eslint-disable */
export default { export default {
displayName: '<%= e2eProjectName %>', displayName: '<%= e2eProjectName %>',
preset: '<%= offsetFromRoot %>jest.preset.js', preset: '<%= offsetFromRoot %><%= jestPreset %>',
globalSetup: '<rootDir>/src/support/global-setup.ts', globalSetup: '<rootDir>/src/support/global-setup.ts',
globalTeardown: '<rootDir>/src/support/global-teardown.ts', globalTeardown: '<rootDir>/src/support/global-teardown.ts',
setupFiles: ['<rootDir>/src/support/test-setup.ts'], setupFiles: ['<rootDir>/src/support/test-setup.ts'],

View File

@ -284,7 +284,7 @@ export async function remixApplicationGeneratorInternal(
if (options.unitTestRunner === 'jest') { if (options.unitTestRunner === 'jest') {
tree.write( tree.write(
'jest.preset.js', 'jest.preset.js',
`import { nxPreset } from '@nx/jest/preset/jest-preset.js'; `import { nxPreset } from '@nx/jest/preset.js';
export default {...nxPreset}; export default {...nxPreset};
` `
); );