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();
});
it('should always use jest.preset.js with --js', async () => {
tree.write('jest.preset.ts', '');
it('should generate a jest.preset.js when it does not exist', async () => {
await configurationGenerator(tree, {
...defaultOptions,
project: 'lib1',
js: true,
} as JestProjectSchema);
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(
"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 () => {
await configurationGenerator(tree, {
...defaultOptions,

View File

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

View File

@ -11,7 +11,7 @@ projects: await getJestProjectsAsync()
exports[`createJestConfig should generate files 2`] = `
"const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset }"
module.exports = { ...nxPreset };"
`;
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`] = `
"const nxPreset = require('@nx/jest/preset').default;
module.exports = { ...nxPreset }"
module.exports = { ...nxPreset };"
`;

View File

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

View File

@ -8,23 +8,34 @@ import {
type Tree,
} from '@nx/devkit';
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';
export async function createJestConfig(
tree: Tree,
options: Partial<NormalizedJestProjectSchema>,
presetExt: 'cjs' | 'js'
presetExt: JestPresetExtension
) {
if (!tree.exists(`jest.preset.${presetExt}`)) {
// preset is always js file.
if (presetExt === 'mjs') {
tree.write(
`jest.preset.${presetExt}`,
`
const nxPreset = require('@nx/jest/preset').default;
`import { nxPreset } from '@nx/jest/preset.js';
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) {
// 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 { NormalizedJestProjectSchema } from '../schema';
import { readProjectConfiguration, type Tree } from '@nx/devkit';
import { findRootJestConfig } from '../../../utils/config/config-file';
import { addPropertyToJestConfig } from '../../../utils/config/update-config';
import { readProjectConfiguration, Tree } from '@nx/devkit';
import type { NormalizedJestProjectSchema } from '../schema';
function isUsingUtilityFunction(host: Tree) {
const rootConfig = findRootJestConfig(host);

View File

@ -1,5 +1,6 @@
import {
addDependenciesToPackageJson,
createProjectGraphAsync,
formatFiles,
readNxJson,
removeDependenciesFromPackageJson,
@ -7,15 +8,17 @@ import {
updateNxJson,
type GeneratorCallback,
type Tree,
createProjectGraphAsync,
} 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 { 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 productionFileSet = nxJson.namedInputs?.production;
@ -40,7 +43,7 @@ function updateProductionFileSet(tree: Tree, presetExt: 'cjs' | 'js') {
updateNxJson(tree, nxJson);
}
function addJestTargetDefaults(tree: Tree, presetEnv: 'cjs' | 'js') {
function addJestTargetDefaults(tree: Tree, presetExt: JestPresetExtension) {
const nxJson = readNxJson(tree);
nxJson.targetDefaults ??= {};
@ -53,7 +56,7 @@ function addJestTargetDefaults(tree: Tree, presetEnv: 'cjs' | 'js') {
nxJson.targetDefaults['@nx/jest:jest'].inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
`{workspaceRoot}/jest.preset.${presetEnv}`,
`{workspaceRoot}/jest.preset.${presetExt}`,
];
nxJson.targetDefaults['@nx/jest:jest'].options ??= {
@ -96,10 +99,10 @@ export async function jestInitGeneratorInternal(
nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault;
const presetExt = isPresetCjs(tree) ? 'cjs' : 'js';
const presetExt = getPresetExt(tree);
if (!tree.exists('jest.preset.js') && !tree.exists('jest.preset.cjs')) {
updateProductionFileSet(tree, presetExt);
if (!tree.exists(`jest.preset.${presetExt}`)) {
updateProductionFileSet(tree);
if (options.addPlugin) {
await addPlugin(
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<% } %> {
displayName: '<%= project %>',
preset: '<%= offsetFromRoot %>jest.preset.js',
preset: '<%= offsetFromRoot %><%= jestPreset %>',
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
},

View File

@ -603,10 +603,12 @@ function replaceJestConfig(tree: Tree, options: NormalizedSchema) {
if (tree.exists(existingJestConfig)) {
tree.delete(existingJestConfig);
}
const jestPreset = findRootJestPreset(tree) ?? 'jest.presets.js';
// replace with JS:SWC specific jest config
generateFiles(tree, filesDir, options.projectRoot, {
ext: options.js ? 'js' : 'ts',
jestPreset,
js: !!options.js,
project: options.name,
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;

View File

@ -28,6 +28,7 @@ import {
replaceOverridesInLintConfig,
} from '@nx/eslint/src/generators/utils/eslint-file';
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) {
return await e2eProjectGeneratorInternal(host, {
@ -90,6 +91,7 @@ export async function e2eProjectGeneratorInternal(
});
}
const jestPreset = findRootJestPreset(host) ?? 'jest.preset.js';
if (options.projectType === 'server') {
generateFiles(
host,
@ -99,6 +101,7 @@ export async function e2eProjectGeneratorInternal(
...options,
...names(options.rootProject ? 'server' : options.project),
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
jestPreset,
tmpl: '',
}
);
@ -127,6 +130,7 @@ export async function e2eProjectGeneratorInternal(
...names(options.rootProject ? 'cli' : options.project),
mainFile,
offsetFromRoot: offsetFromRoot(options.e2eProjectRoot),
jestPreset,
tmpl: '',
}
);

View File

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

View File

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

View File

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