feat(testing): add setupFilesAfterEnv and other configs to project's jest config file (#3224)
* feat(testing): add util to update jest configs. * feat(testing): place configurations in jest config file rather than just the builder * feat(testing): create migration and unit tests * feat(testing): fix jest template * feat(testing): fix jest template to correct unit tests * feat(testing): include globals.ts-jest for all non babel configs * feat(testing): include globals.ts-jest for node e2e * feat(testing): fix migration to run properly. Also check for angular tests using the setupfile rather than builder * feat(testing): clean up jest config functions and fix errors with some migrations * feat(testing): add new line to package.json * feat(testing): update object check to actually check for undefined * chore(testing): loop through all project targets as well as targets * chore(testing): update migration to be 10.0.0-beta.2
This commit is contained in:
parent
4968b6e6e3
commit
3b8a10f073
@ -6,6 +6,9 @@
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ignorePatterns": ["**/*"],
|
||||
"plugins": ["@typescript-eslint", "@nrwl/nx"],
|
||||
"extends": [
|
||||
|
||||
@ -136,7 +136,7 @@ Run all tests serially in the current process (rather than creating a worker poo
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of a setup file used by Jest. (https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)
|
||||
[Deprecated] The name of a setup file used by Jest. (use Jest config file https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)
|
||||
|
||||
### showConfig
|
||||
|
||||
@ -186,7 +186,7 @@ Node module that implements a custom results processor. (https://jestjs.io/docs/
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the Typescript configuration file.
|
||||
[Deprecated] The name of the Typescript configuration file. Set the tsconfig option in the jest config file.
|
||||
|
||||
### updateSnapshot
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ Run all tests serially in the current process (rather than creating a worker poo
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of a setup file used by Jest. (https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)
|
||||
[Deprecated] The name of a setup file used by Jest. (use Jest config file https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)
|
||||
|
||||
### showConfig
|
||||
|
||||
@ -187,7 +187,7 @@ Node module that implements a custom results processor. (https://jestjs.io/docs/
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the Typescript configuration file.
|
||||
[Deprecated] The name of the Typescript configuration file. Set the tsconfig option in the jest config file.
|
||||
|
||||
### updateSnapshot
|
||||
|
||||
|
||||
@ -337,6 +337,11 @@ forEachCli((currentCLIName) => {
|
||||
stripIndents`module.exports = {
|
||||
name: '${nestlib}',
|
||||
preset: '../../jest.config.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
export {
|
||||
addPropertyToJestConfig,
|
||||
removePropertyFromJestConfig,
|
||||
} from './src/utils/config/update-config';
|
||||
export {
|
||||
jestConfigObjectAst,
|
||||
jestConfigObject,
|
||||
} from './src/utils/config/functions';
|
||||
@ -24,6 +24,11 @@
|
||||
"version": "9.2.0-beta.3",
|
||||
"description": "Update jest to v25",
|
||||
"factory": "./src/migrations/update-9-2-0/update-9-2-0"
|
||||
},
|
||||
"update-10.0.0": {
|
||||
"version": "10.0.0-beta.2",
|
||||
"description": "update jest configs to include setup env files",
|
||||
"factory": "./src/migrations/update-10-0-0/update-jest-configs"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -61,16 +61,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
testPathPattern: [],
|
||||
watch: false,
|
||||
},
|
||||
@ -103,16 +93,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: ['lib.spec.ts'],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
coverage: false,
|
||||
runInBand: true,
|
||||
testNamePattern: 'should load',
|
||||
@ -147,16 +127,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: ['file1.ts', 'file2.ts'],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
coverage: false,
|
||||
findRelatedTests: true,
|
||||
runInBand: true,
|
||||
@ -206,16 +176,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
coverage: true,
|
||||
bail: 1,
|
||||
color: false,
|
||||
@ -260,16 +220,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
maxWorkers: '50%',
|
||||
testPathPattern: [],
|
||||
},
|
||||
@ -292,16 +242,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: JSON.stringify({
|
||||
'ts-jest': {
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
setupFilesAfterEnv: ['/root/test-setup.ts'],
|
||||
testPathPattern: [],
|
||||
watch: false,
|
||||
@ -350,18 +290,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: JSON.stringify({
|
||||
hereToStay: true,
|
||||
'ts-jest': {
|
||||
diagnostics: false,
|
||||
tsConfig: '/root/tsconfig.test.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
}),
|
||||
setupFilesAfterEnv: ['/root/test-setup.ts'],
|
||||
testPathPattern: [],
|
||||
watch: false,
|
||||
@ -400,7 +328,6 @@ describe('Jest Builder', () => {
|
||||
expect(runCLI).toHaveBeenCalledWith(
|
||||
{
|
||||
_: [],
|
||||
globals: '{}',
|
||||
testPathPattern: [],
|
||||
watch: false,
|
||||
},
|
||||
|
||||
@ -11,14 +11,14 @@ import { JestBuilderOptions } from './schema';
|
||||
|
||||
try {
|
||||
require('dotenv').config();
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV == null || process.env.NODE_ENV == undefined) {
|
||||
(process.env as any).NODE_ENV = 'test';
|
||||
}
|
||||
|
||||
export default createBuilder<JestBuilderOptions>(run);
|
||||
|
||||
function run(
|
||||
options: JestBuilderOptions,
|
||||
context: BuilderContext
|
||||
@ -28,9 +28,11 @@ function run(
|
||||
const jestConfig: {
|
||||
transform: any;
|
||||
globals: any;
|
||||
setupFilesAfterEnv: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
} = require(options.jestConfig);
|
||||
|
||||
let transformers = Object.values<string>(jestConfig.transform || {});
|
||||
const transformers = Object.values<string>(jestConfig.transform || {});
|
||||
if (transformers.includes('babel-jest') && transformers.includes('ts-jest')) {
|
||||
throw new Error(
|
||||
'Using babel-jest and ts-jest together is not supported.\n' +
|
||||
@ -38,35 +40,6 @@ function run(
|
||||
);
|
||||
}
|
||||
|
||||
// use ts-jest by default
|
||||
const globals = jestConfig.globals || {};
|
||||
if (!transformers.includes('babel-jest')) {
|
||||
const tsJestConfig = {
|
||||
tsConfig: path.resolve(context.workspaceRoot, options.tsConfig),
|
||||
};
|
||||
|
||||
// TODO: This is hacky, We should probably just configure it in the user's workspace
|
||||
// If jest-preset-angular is installed, apply settings
|
||||
try {
|
||||
require.resolve('jest-preset-angular');
|
||||
Object.assign(tsJestConfig, {
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
// merge the jestConfig globals with our 'ts-jest' override
|
||||
Object.assign(globals, {
|
||||
'ts-jest': {
|
||||
...(globals['ts-jest'] || {}),
|
||||
...tsJestConfig,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const config: any = {
|
||||
_: [],
|
||||
config: options.config,
|
||||
@ -95,13 +68,15 @@ function run(
|
||||
useStderr: options.useStderr,
|
||||
watch: options.watch,
|
||||
watchAll: options.watchAll,
|
||||
globals: JSON.stringify(globals),
|
||||
};
|
||||
|
||||
// for backwards compatibility
|
||||
if (options.setupFile) {
|
||||
config.setupFilesAfterEnv = [
|
||||
const setupFilesAfterEnvSet = new Set([
|
||||
...(jestConfig.setupFilesAfterEnv ?? []),
|
||||
path.resolve(context.workspaceRoot, options.setupFile),
|
||||
];
|
||||
]);
|
||||
config.setupFilesAfterEnv = Array.from(setupFilesAfterEnvSet);
|
||||
}
|
||||
|
||||
if (options.testFile) {
|
||||
@ -132,3 +107,5 @@ function run(
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default createBuilder<JestBuilderOptions>(run);
|
||||
|
||||
@ -29,12 +29,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
"tsConfig": {
|
||||
"description": "The name of the Typescript configuration file.",
|
||||
"type": "string"
|
||||
"description": "[Deprecated] The name of the Typescript configuration file. Set the tsconfig option in the jest config file. ",
|
||||
"type": "string",
|
||||
"x-deprecated": true
|
||||
},
|
||||
"setupFile": {
|
||||
"description": "The name of a setup file used by Jest. (https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)",
|
||||
"type": "string"
|
||||
"description": "[Deprecated] The name of a setup file used by Jest. (use Jest config file https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)",
|
||||
"type": "string",
|
||||
"x-deprecated": true
|
||||
},
|
||||
"bail": {
|
||||
"alias": "b",
|
||||
@ -150,5 +152,5 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["jestConfig", "tsConfig"]
|
||||
"required": ["jestConfig"]
|
||||
}
|
||||
|
||||
@ -0,0 +1,156 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import * as path from 'path';
|
||||
import { serializeJson } from '@nrwl/workspace';
|
||||
import { jestConfigObject } from '../../..';
|
||||
|
||||
describe('update 10.0.0', () => {
|
||||
let initialTree: Tree;
|
||||
let schematicRunner: SchematicTestRunner;
|
||||
|
||||
const jestConfig = String.raw`
|
||||
module.exports = {
|
||||
name: 'test-jest',
|
||||
preset: '../../jest.config.js',
|
||||
coverageDirectory: '../../coverage/libs/test-jest',
|
||||
globals: {
|
||||
"existing-global": "test"
|
||||
},
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
|
||||
'jest-preset-angular/build/AngularSnapshotSerializer.js',
|
||||
'jest-preset-angular/build/HTMLCommentSerializer.js'
|
||||
]
|
||||
}
|
||||
`;
|
||||
|
||||
const jestConfigReact = String.raw`
|
||||
module.exports = {
|
||||
name: 'my-react-app',
|
||||
preset: '../../jest.config.js',
|
||||
transform: {
|
||||
'^(?!.*\\\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
|
||||
'^.+\\\\.[tj]sx?$': [
|
||||
'babel-jest',
|
||||
{ cwd: __dirname, configFile: './babel-jest.config.json' }
|
||||
]
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
|
||||
coverageDirectory: '../../coverage/apps/my-react-app'
|
||||
}
|
||||
`;
|
||||
|
||||
beforeEach(() => {
|
||||
initialTree = createEmptyWorkspace(Tree.empty());
|
||||
|
||||
initialTree.create('apps/products/jest.config.js', jestConfig);
|
||||
initialTree.create(
|
||||
'apps/products/src/test-setup.ts',
|
||||
`import 'jest-preset-angular'`
|
||||
);
|
||||
initialTree.create('apps/cart/jest.config.js', jestConfigReact);
|
||||
initialTree.overwrite(
|
||||
'workspace.json',
|
||||
serializeJson({
|
||||
version: 1,
|
||||
projects: {
|
||||
products: {
|
||||
root: 'apps/products',
|
||||
sourceRoot: 'apps/products/src',
|
||||
architect: {
|
||||
build: {
|
||||
builder: '@angular-devkit/build-angular:browser',
|
||||
},
|
||||
test: {
|
||||
builder: '@nrwl/jest:jest',
|
||||
options: {
|
||||
jestConfig: 'apps/products/jest.config.js',
|
||||
tsConfig: 'apps/products/tsconfig.spec.json',
|
||||
setupFile: 'apps/products/src/test-setup.ts',
|
||||
passWithNoTests: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cart: {
|
||||
root: 'apps/cart',
|
||||
sourceRoot: 'apps/cart/src',
|
||||
architect: {
|
||||
build: {
|
||||
builder: '@nrwl/web:build',
|
||||
},
|
||||
test: {
|
||||
builder: '@nrwl/jest:jest',
|
||||
options: {
|
||||
jestConfig: 'apps/cart/jest.config.js',
|
||||
passWithNoTests: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
schematicRunner = new SchematicTestRunner(
|
||||
'@nrwl/jest',
|
||||
path.join(__dirname, '../../../migrations.json')
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove setupFile and tsconfig in test architect from workspace.json', async (done) => {
|
||||
const result = await schematicRunner
|
||||
.runSchematicAsync('update-10.0.0', {}, initialTree)
|
||||
.toPromise();
|
||||
|
||||
const updatedWorkspace = JSON.parse(result.readContent('workspace.json'));
|
||||
expect(updatedWorkspace.projects.products.architect.test.options).toEqual({
|
||||
jestConfig: expect.anything(),
|
||||
passWithNoTests: expect.anything(),
|
||||
});
|
||||
expect(updatedWorkspace.projects.cart.architect.test.options).toEqual({
|
||||
jestConfig: expect.anything(),
|
||||
passWithNoTests: expect.anything(),
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('should update the jest.config files', async (done) => {
|
||||
await schematicRunner
|
||||
.runSchematicAsync('update-10.0.0', {}, initialTree)
|
||||
.toPromise();
|
||||
|
||||
const jestObject = jestConfigObject(
|
||||
initialTree,
|
||||
'apps/products/jest.config.js'
|
||||
);
|
||||
|
||||
const angularSetupFiles = jestObject.setupFilesAfterEnv;
|
||||
const angularGlobals = jestObject.globals;
|
||||
|
||||
expect(angularSetupFiles).toEqual(['<rootDir>/src/test-setup.ts']);
|
||||
expect(angularGlobals).toEqual({
|
||||
'existing-global': 'test',
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const reactJestObject = jestConfigObject(
|
||||
initialTree,
|
||||
'apps/cart/jest.config.js'
|
||||
);
|
||||
|
||||
const reactSetupFiles = reactJestObject.setupFilesAfterEnv;
|
||||
const reactGlobals = reactJestObject.globals;
|
||||
expect(reactSetupFiles).toBeUndefined();
|
||||
expect(reactGlobals).toBeUndefined();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,173 @@
|
||||
import {
|
||||
chain,
|
||||
Rule,
|
||||
SchematicContext,
|
||||
Tree,
|
||||
} from '@angular-devkit/schematics';
|
||||
import {
|
||||
formatFiles,
|
||||
getWorkspace,
|
||||
getWorkspacePath,
|
||||
serializeJson,
|
||||
updateWorkspace,
|
||||
} from '@nrwl/workspace';
|
||||
import { addPropertyToJestConfig, jestConfigObject } from '../../..';
|
||||
|
||||
function checkJestPropertyObject(object: unknown): object is object {
|
||||
return object !== null && object !== undefined;
|
||||
}
|
||||
|
||||
function modifyJestConfig(
|
||||
host: Tree,
|
||||
context: SchematicContext,
|
||||
project: string,
|
||||
setupFile: string,
|
||||
jestConfig: string,
|
||||
tsConfig: string,
|
||||
isAngular: boolean
|
||||
) {
|
||||
if (setupFile === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let globalTsJest: any = {
|
||||
tsConfig,
|
||||
};
|
||||
|
||||
if (isAngular) {
|
||||
globalTsJest = {
|
||||
...globalTsJest,
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const jestObject = jestConfigObject(host, jestConfig);
|
||||
|
||||
// add set up env file
|
||||
// setupFilesAfterEnv
|
||||
const existingSetupFiles = jestObject.setupFilesAfterEnv;
|
||||
|
||||
let setupFilesAfterEnv: string | string[] = [setupFile];
|
||||
if (Array.isArray(existingSetupFiles)) {
|
||||
setupFilesAfterEnv = setupFile;
|
||||
}
|
||||
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
jestConfig,
|
||||
'setupFilesAfterEnv',
|
||||
setupFilesAfterEnv
|
||||
);
|
||||
|
||||
// check if jest config has babel transform
|
||||
const transformProperty = jestObject.transform;
|
||||
|
||||
let hasBabelTransform = false;
|
||||
if (transformProperty) {
|
||||
for (const prop in transformProperty) {
|
||||
const transformPropValue = transformProperty[prop];
|
||||
if (Array.isArray(transformPropValue)) {
|
||||
hasBabelTransform = transformPropValue.some(
|
||||
(value) => typeof value === 'string' && value.includes('babel')
|
||||
);
|
||||
} else if (typeof transformPropValue === 'string') {
|
||||
transformPropValue.includes('babel');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBabelTransform) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add ts-jest configurations
|
||||
const existingGlobals = jestObject.globals;
|
||||
if (!existingGlobals) {
|
||||
addPropertyToJestConfig(host, jestConfig, 'globals', {
|
||||
'ts-jest': globalTsJest,
|
||||
});
|
||||
} else {
|
||||
const existingGlobalTsJest = existingGlobals['ts-jest'];
|
||||
if (!checkJestPropertyObject(existingGlobalTsJest)) {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
jestConfig,
|
||||
'globals.ts-jest',
|
||||
globalTsJest
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
context.logger.warn(`
|
||||
Cannot update jest config for the ${project} project.
|
||||
This is most likely caused because the jest config at ${jestConfig} it not in a expected configuration format (ie. module.exports = {}).
|
||||
|
||||
Since this migration could not be ran on this project, please make sure to modify the Jest config file to have the following configured:
|
||||
* setupFilesAfterEnv with: "${setupFile}"
|
||||
* globals.ts-jest with:
|
||||
"${serializeJson(globalTsJest)}"
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateJestConfigForProjects() {
|
||||
return async (host: Tree, context: SchematicContext) => {
|
||||
const workspace = await getWorkspace(host, getWorkspacePath(host));
|
||||
|
||||
for (const [projectName, projectDefinition] of workspace.projects) {
|
||||
for (const [, testTarget] of projectDefinition.targets) {
|
||||
if (testTarget.builder !== '@nrwl/jest:jest') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const setupfile = testTarget.options?.setupFile;
|
||||
const jestConfig = (testTarget.options?.jestConfig as string) ?? '';
|
||||
const tsConfig = (testTarget.options?.tsConfig as string) ?? '';
|
||||
const tsConfigWithRootDir = tsConfig.replace(
|
||||
projectDefinition.root,
|
||||
'<rootDir>'
|
||||
);
|
||||
|
||||
let isAngular = false;
|
||||
let setupFileWithRootDir = '';
|
||||
if (typeof setupfile === 'string') {
|
||||
isAngular = host
|
||||
.read(setupfile)
|
||||
?.toString()
|
||||
.includes('jest-preset-angular');
|
||||
setupFileWithRootDir = setupfile.replace(
|
||||
projectDefinition.root,
|
||||
'<rootDir>'
|
||||
);
|
||||
}
|
||||
|
||||
modifyJestConfig(
|
||||
host,
|
||||
context,
|
||||
projectName,
|
||||
setupFileWithRootDir,
|
||||
jestConfig,
|
||||
tsConfigWithRootDir,
|
||||
isAngular
|
||||
);
|
||||
|
||||
const updatedOptions = { ...testTarget.options };
|
||||
delete updatedOptions.setupFile;
|
||||
delete updatedOptions.tsConfig;
|
||||
|
||||
testTarget.options = updatedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
return updateWorkspace(workspace);
|
||||
};
|
||||
}
|
||||
|
||||
export default function update(): Rule {
|
||||
return chain([updateJestConfigForProjects(), formatFiles()]);
|
||||
}
|
||||
@ -1,6 +1,18 @@
|
||||
module.exports = {
|
||||
name: '<%= project %>',
|
||||
preset: '<%= offsetFromRoot %>jest.config.js',<% if(testEnvironment) { %>
|
||||
preset: '<%= offsetFromRoot %>jest.config.js',<% if(setupFile !== 'none') { %>
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
<% } %><% if (transformer === 'ts-jest') { %>
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',<%if (setupFile === 'angular') { %>
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer'
|
||||
],<% } %>
|
||||
}
|
||||
},<% } %><% if(testEnvironment) { %>
|
||||
testEnvironment: '<%= testEnvironment %>',<% } %><% if (supportTsx) { %>
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': <% if (transformer == 'babel-jest') { %>[ 'babel-jest',
|
||||
|
||||
@ -2,6 +2,7 @@ import { Tree } from '@angular-devkit/schematics';
|
||||
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { callRule, runSchematic } from '../../utils/testing';
|
||||
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
|
||||
|
||||
describe('jestProject', () => {
|
||||
let appTree: Tree;
|
||||
@ -84,10 +85,15 @@ describe('jestProject', () => {
|
||||
},
|
||||
appTree
|
||||
);
|
||||
expect(resultTree.readContent('libs/lib1/jest.config.js'))
|
||||
.toBe(`module.exports = {
|
||||
expect(stripIndents`${resultTree.readContent('libs/lib1/jest.config.js')}`)
|
||||
.toBe(stripIndents`module.exports = {
|
||||
name: 'lib1',
|
||||
preset: '../../jest.config.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
}
|
||||
},
|
||||
coverageDirectory: '../../coverage/libs/lib1',
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
|
||||
@ -145,6 +151,47 @@ describe('jestProject', () => {
|
||||
appTree
|
||||
);
|
||||
expect(resultTree.exists('src/test-setup.ts')).toBeFalsy();
|
||||
expect(resultTree.readContent('libs/lib1/jest.config.js')).not.toContain(
|
||||
`setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have setupFilesAfterEnv in the jest.config when generated for web-components', async () => {
|
||||
const resultTree = await runSchematic(
|
||||
'jest-project',
|
||||
{
|
||||
project: 'lib1',
|
||||
setupFile: 'web-components',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
expect(resultTree.readContent('libs/lib1/jest.config.js')).toContain(
|
||||
`setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have setupFilesAfterEnv and globals.ts-ject in the jest.config when generated for angular', async () => {
|
||||
const resultTree = await runSchematic(
|
||||
'jest-project',
|
||||
{
|
||||
project: 'lib1',
|
||||
setupFile: 'angular',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
|
||||
const jestConfig = resultTree.readContent('libs/lib1/jest.config.js');
|
||||
expect(jestConfig).toContain(
|
||||
`setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],`
|
||||
);
|
||||
expect(stripIndents`${jestConfig}`).toContain(stripIndents`globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
astTransformers: [
|
||||
'jest-preset-angular/build/InlineFilesTransformer',
|
||||
'jest-preset-angular/build/StripStylesTransformer'
|
||||
],`);
|
||||
});
|
||||
|
||||
it('should not list the setup file in workspace.json', async () => {
|
||||
@ -261,4 +308,50 @@ describe('jestProject', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--babelJest', () => {
|
||||
it('should have globals.ts-jest configured when babelJest is false', async () => {
|
||||
const resultTree = await runSchematic(
|
||||
'jest-project',
|
||||
{
|
||||
project: 'lib1',
|
||||
babelJest: false,
|
||||
setupFile: 'none',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const jestConfig = stripIndents`${resultTree.readContent(
|
||||
'libs/lib1/jest.config.js'
|
||||
)}`;
|
||||
expect(jestConfig).toContain(
|
||||
stripIndents`globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
}
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should have NOT have globals.ts-jest configured when babelJest is true', async () => {
|
||||
const resultTree = await runSchematic(
|
||||
'jest-project',
|
||||
{
|
||||
project: 'lib1',
|
||||
babelJest: true,
|
||||
setupFile: 'none',
|
||||
},
|
||||
appTree
|
||||
);
|
||||
const jestConfig = stripIndents`${resultTree.readContent(
|
||||
'libs/lib1/jest.config.js'
|
||||
)}`;
|
||||
expect(jestConfig).not.toContain(
|
||||
stripIndents`globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||
}
|
||||
}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
209
packages/jest/src/utils/config/functions.ts
Normal file
209
packages/jest/src/utils/config/functions.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import * as ts from 'typescript';
|
||||
import { findNodes, InsertChange, ReplaceChange } from '@nrwl/workspace';
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import * as stripJsonComments from 'strip-json-comments';
|
||||
import { Config } from '@jest/types';
|
||||
|
||||
function trailingCommaNeeded(needed: boolean) {
|
||||
return needed ? ',' : '';
|
||||
}
|
||||
|
||||
function createInsertChange(
|
||||
path: string,
|
||||
value: unknown,
|
||||
position: number,
|
||||
commaNeeded: boolean
|
||||
) {
|
||||
return new InsertChange(
|
||||
path,
|
||||
position,
|
||||
`${trailingCommaNeeded(!commaNeeded)}${value}${trailingCommaNeeded(
|
||||
commaNeeded
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
function findPropertyAssignment(
|
||||
object: ts.ObjectLiteralExpression,
|
||||
propertyName: string
|
||||
) {
|
||||
return object.properties.find((prop) => {
|
||||
const propNameText = prop.name.getText();
|
||||
if (propNameText.match(/^["'].+["']$/g)) {
|
||||
return JSON.parse(propNameText.replace(/'/g, '"')) === propertyName;
|
||||
}
|
||||
|
||||
return propNameText === propertyName;
|
||||
}) as ts.PropertyAssignment | undefined;
|
||||
}
|
||||
|
||||
export function getJsonObject(object: string) {
|
||||
const value = stripJsonComments(object);
|
||||
// react babel-jest has __dirname in the config.
|
||||
// Put a temp variable in the anon function so that it doesnt fail.
|
||||
// Migration script has a catch handler to give instructions on how to update the jest config if this fails.
|
||||
return Function(`
|
||||
"use strict";
|
||||
let __dirname = '';
|
||||
return (${value});
|
||||
`)();
|
||||
}
|
||||
|
||||
export function addOrUpdateProperty(
|
||||
object: ts.ObjectLiteralExpression,
|
||||
properties: string[],
|
||||
value: unknown,
|
||||
path: string
|
||||
) {
|
||||
const propertyName = properties.shift();
|
||||
const propertyAssignment = findPropertyAssignment(object, propertyName);
|
||||
|
||||
if (propertyAssignment) {
|
||||
if (
|
||||
propertyAssignment.initializer.kind === ts.SyntaxKind.StringLiteral ||
|
||||
propertyAssignment.initializer.kind === ts.SyntaxKind.NumericLiteral ||
|
||||
propertyAssignment.initializer.kind === ts.SyntaxKind.FalseKeyword ||
|
||||
propertyAssignment.initializer.kind === ts.SyntaxKind.TrueKeyword
|
||||
) {
|
||||
return [
|
||||
new ReplaceChange(
|
||||
path,
|
||||
propertyAssignment.initializer.pos,
|
||||
propertyAssignment.initializer.getFullText(),
|
||||
value as string
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (
|
||||
propertyAssignment.initializer.kind ===
|
||||
ts.SyntaxKind.ArrayLiteralExpression
|
||||
) {
|
||||
const arrayLiteral = propertyAssignment.initializer as ts.ArrayLiteralExpression;
|
||||
|
||||
if (
|
||||
arrayLiteral.elements.some((element) => {
|
||||
return element.getText().replace(/'/g, '"') === value;
|
||||
})
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
createInsertChange(
|
||||
path,
|
||||
value,
|
||||
arrayLiteral.elements.end,
|
||||
arrayLiteral.elements.hasTrailingComma
|
||||
),
|
||||
];
|
||||
} else if (
|
||||
propertyAssignment.initializer.kind ===
|
||||
ts.SyntaxKind.ObjectLiteralExpression
|
||||
) {
|
||||
return addOrUpdateProperty(
|
||||
propertyAssignment.initializer as ts.ObjectLiteralExpression,
|
||||
properties,
|
||||
value,
|
||||
path
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (propertyName === undefined) {
|
||||
throw new Error(
|
||||
`Please use dot delimited paths to update an existing object. Eg. object.property `
|
||||
);
|
||||
}
|
||||
return [
|
||||
createInsertChange(
|
||||
path,
|
||||
`${JSON.stringify(propertyName)}: ${value}`,
|
||||
object.properties.end,
|
||||
object.properties.hasTrailingComma
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function removeProperty(
|
||||
object: ts.ObjectLiteralExpression,
|
||||
properties: string[]
|
||||
): ts.PropertyAssignment | null {
|
||||
const propertyName = properties.shift();
|
||||
const propertyAssignment = findPropertyAssignment(object, propertyName);
|
||||
|
||||
if (propertyAssignment) {
|
||||
if (
|
||||
properties.length > 0 &&
|
||||
propertyAssignment.initializer.kind ===
|
||||
ts.SyntaxKind.ObjectLiteralExpression
|
||||
) {
|
||||
return removeProperty(
|
||||
propertyAssignment.initializer as ts.ObjectLiteralExpression,
|
||||
properties
|
||||
);
|
||||
}
|
||||
return propertyAssignment;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used to get the jest config object.
|
||||
*
|
||||
* @param host
|
||||
* @param path
|
||||
*/
|
||||
export function jestConfigObjectAst(
|
||||
host: Tree,
|
||||
path: string
|
||||
): ts.ObjectLiteralExpression {
|
||||
if (!host.exists(path)) {
|
||||
throw new Error(`Cannot find '${path}' in your workspace.`);
|
||||
}
|
||||
|
||||
const fileContent = host.read(path).toString('utf-8');
|
||||
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'jest.config.js',
|
||||
fileContent,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
const expressions = findNodes(
|
||||
sourceFile,
|
||||
ts.SyntaxKind.BinaryExpression
|
||||
) as ts.BinaryExpression[];
|
||||
|
||||
const moduleExports = expressions.find(
|
||||
(node) =>
|
||||
node.left.getText() === 'module.exports' &&
|
||||
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
||||
ts.isObjectLiteralExpression(node.right)
|
||||
);
|
||||
|
||||
if (!moduleExports) {
|
||||
throw new Error(
|
||||
`
|
||||
The provided jest config file does not have the expected 'module.exports' expression.
|
||||
See https://jestjs.io/docs/en/configuration for more details.`
|
||||
);
|
||||
}
|
||||
|
||||
return moduleExports.right as ts.ObjectLiteralExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the jest config object
|
||||
* @param host
|
||||
* @param path
|
||||
*/
|
||||
export function jestConfigObject(
|
||||
host: Tree,
|
||||
path: string
|
||||
): Partial<Config.InitialOptions> & { [index: string]: any } {
|
||||
const jestConfigAst = jestConfigObjectAst(host, path);
|
||||
return getJsonObject(jestConfigAst.getText());
|
||||
}
|
||||
235
packages/jest/src/utils/config/update-config.spec.ts
Normal file
235
packages/jest/src/utils/config/update-config.spec.ts
Normal file
@ -0,0 +1,235 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import {
|
||||
addPropertyToJestConfig,
|
||||
removePropertyFromJestConfig,
|
||||
} from './update-config';
|
||||
import { jestConfigObject } from './functions';
|
||||
|
||||
describe('Update jest.config.js', () => {
|
||||
let host: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
host = Tree.empty();
|
||||
// create
|
||||
host.create(
|
||||
'jest.config.js',
|
||||
String.raw`
|
||||
module.exports = {
|
||||
name: 'test',
|
||||
boolean: false,
|
||||
preset: 'nrwl-preset',
|
||||
"update-me": "hello",
|
||||
alreadyExistingArray: ['something'],
|
||||
alreadyExistingObject: {
|
||||
nestedProperty: {
|
||||
primitive: 'string',
|
||||
childArray: ['value1', 'value2']
|
||||
},
|
||||
'nested-object': {
|
||||
childArray: ['value1', 'value2']
|
||||
}
|
||||
},
|
||||
numeric: 0
|
||||
}
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
describe('inserting or updating an existing property', () => {
|
||||
it('should be able to update an existing property with a primitive value ', () => {
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'name', 'test-case');
|
||||
|
||||
let json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.name).toBe('test-case');
|
||||
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'boolean', true);
|
||||
json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.boolean).toBe(true);
|
||||
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'numeric', 1);
|
||||
json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.numeric).toBe(1);
|
||||
});
|
||||
|
||||
it('should be able to insert a new property with a primitive value', () => {
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'bail', 0);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.bail).toBe(0);
|
||||
});
|
||||
|
||||
it('it should be able to insert a new property with an array value', () => {
|
||||
const arrayValue = ['value', 'value2'];
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'myArrayProperty', [
|
||||
'value',
|
||||
'value2',
|
||||
]);
|
||||
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.myArrayProperty).toEqual(arrayValue);
|
||||
});
|
||||
|
||||
it('should be able to insert a new property with an object value', () => {
|
||||
const objectValue = {
|
||||
'some-property': { config1: '1', config2: ['value1', 'value2'] },
|
||||
};
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'myObjectProperty',
|
||||
objectValue
|
||||
);
|
||||
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.myObjectProperty).toEqual(objectValue);
|
||||
});
|
||||
|
||||
it('should be able to update an existing array', () => {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingArray',
|
||||
'something new'
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.alreadyExistingArray).toEqual(['something', 'something new']);
|
||||
});
|
||||
|
||||
it('should not add duplicate values in an existing array', () => {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingArray',
|
||||
'something'
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.alreadyExistingArray).toEqual(['something']);
|
||||
});
|
||||
|
||||
it('should be able to update an existing object', () => {
|
||||
const newPropertyValue = ['my new object'];
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject.something-new',
|
||||
newPropertyValue
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.alreadyExistingObject['something-new']).toEqual(
|
||||
newPropertyValue
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to update an existing array in a nested object', () => {
|
||||
const newPropertyValue = 'new value';
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject.nestedProperty.childArray',
|
||||
newPropertyValue
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.alreadyExistingObject.nestedProperty.childArray).toEqual([
|
||||
'value1',
|
||||
'value2',
|
||||
newPropertyValue,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to update an existing value in a nested object', () => {
|
||||
const newPropertyValue = false;
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject.nestedProperty.primitive',
|
||||
newPropertyValue
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json.alreadyExistingObject.nestedProperty.primitive).toEqual(
|
||||
newPropertyValue
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to modify an object with a string identifier', () => {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'something-here',
|
||||
'newPropertyValue'
|
||||
);
|
||||
let json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json['something-here']).toEqual('newPropertyValue');
|
||||
|
||||
addPropertyToJestConfig(host, 'jest.config.js', 'update-me', 'goodbye');
|
||||
json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json['update-me']).toEqual('goodbye');
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should throw an error when trying to add a value to an already existing object without being dot delimited', () => {
|
||||
expect(() => {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject',
|
||||
'should fail'
|
||||
);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should throw an error if the jest.config doesnt match module.exports = {} style', () => {
|
||||
host.create(
|
||||
'jest.unconventional.js',
|
||||
String.raw`
|
||||
jestObject = {
|
||||
stuffhere: true
|
||||
}
|
||||
|
||||
module.exports = jestObject;
|
||||
`
|
||||
);
|
||||
expect(() => {
|
||||
addPropertyToJestConfig(
|
||||
host,
|
||||
'jest.unconventional.js',
|
||||
'stuffhere',
|
||||
'should fail'
|
||||
);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should throw if the provided config does not exist in the tree', () => {
|
||||
expect(() => {
|
||||
addPropertyToJestConfig(host, 'jest.doesnotexist.js', '', '');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removing values', () => {
|
||||
it('should remove single nested properties in the jest config, ', () => {
|
||||
removePropertyFromJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject.nested-object.childArray'
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(
|
||||
json['alreadyExistingObject']['nested-object']['childArray']
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('should remove single properties', () => {
|
||||
removePropertyFromJestConfig(host, 'jest.config.js', 'update-me');
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json['update-me']).toEqual(undefined);
|
||||
});
|
||||
it('should remove a whole object', () => {
|
||||
removePropertyFromJestConfig(
|
||||
host,
|
||||
'jest.config.js',
|
||||
'alreadyExistingObject'
|
||||
);
|
||||
const json = jestConfigObject(host, 'jest.config.js');
|
||||
expect(json['alreadyExistingObject']).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
61
packages/jest/src/utils/config/update-config.ts
Normal file
61
packages/jest/src/utils/config/update-config.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { insert, RemoveChange } from '@nrwl/workspace';
|
||||
import {
|
||||
addOrUpdateProperty,
|
||||
jestConfigObjectAst,
|
||||
removeProperty,
|
||||
} from './functions';
|
||||
|
||||
/**
|
||||
* Add a property to the jest config
|
||||
* @param host
|
||||
* @param path - path to the jest config file
|
||||
* @param propertyName - Property to update. Can be dot delimited to access deeply nested properties
|
||||
* @param value
|
||||
*/
|
||||
export function addPropertyToJestConfig(
|
||||
host: Tree,
|
||||
path: string,
|
||||
propertyName: string,
|
||||
value: unknown
|
||||
) {
|
||||
const configObject = jestConfigObjectAst(host, path);
|
||||
const properties = propertyName.split('.');
|
||||
const changes = addOrUpdateProperty(
|
||||
configObject,
|
||||
properties,
|
||||
JSON.stringify(value),
|
||||
path
|
||||
);
|
||||
insert(host, path, changes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property value from the jest config
|
||||
* @param host
|
||||
* @param path
|
||||
* @param propertyName - Property to remove. Can be dot delimited to access deeply nested properties
|
||||
*/
|
||||
export function removePropertyFromJestConfig(
|
||||
host: Tree,
|
||||
path: string,
|
||||
propertyName: string
|
||||
) {
|
||||
const configObject = jestConfigObjectAst(host, path);
|
||||
const propertyAssignment = removeProperty(
|
||||
configObject,
|
||||
propertyName.split('.')
|
||||
);
|
||||
|
||||
if (propertyAssignment) {
|
||||
const file = host.read(path).toString('utf-8');
|
||||
const commaNeeded = file[propertyAssignment.end] === ',';
|
||||
insert(host, path, [
|
||||
new RemoveChange(
|
||||
path,
|
||||
propertyAssignment.getStart(),
|
||||
`${propertyAssignment.getText()}${commaNeeded ? ',' : ''}`
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,9 @@ export {
|
||||
updateNxJsonInTree,
|
||||
addProjectToNxJsonInTree,
|
||||
readNxJsonInTree,
|
||||
InsertChange,
|
||||
ReplaceChange,
|
||||
RemoveChange,
|
||||
} from './src/utils/ast-utils';
|
||||
|
||||
export {
|
||||
|
||||
@ -163,8 +163,8 @@ export class RemoveChange implements Change {
|
||||
|
||||
constructor(
|
||||
public path: string,
|
||||
private pos: number,
|
||||
private toRemove: string
|
||||
public pos: number,
|
||||
public toRemove: string
|
||||
) {
|
||||
if (pos < 0) {
|
||||
throw new Error('Negative positions are invalid');
|
||||
@ -189,9 +189,9 @@ export class ReplaceChange implements Change {
|
||||
|
||||
constructor(
|
||||
public path: string,
|
||||
private pos: number,
|
||||
private oldText: string,
|
||||
private newText: string
|
||||
public pos: number,
|
||||
public oldText: string,
|
||||
public newText: string
|
||||
) {
|
||||
if (pos < 0) {
|
||||
throw new Error('Negative positions are invalid');
|
||||
@ -350,22 +350,25 @@ export function addGlobal(
|
||||
}
|
||||
}
|
||||
|
||||
export function insert(host: Tree, modulePath: string, changes: any[]) {
|
||||
export function insert(host: Tree, modulePath: string, changes: Change[]) {
|
||||
if (changes.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// sort changes so that the highest pos goes first
|
||||
const orderedChanges = changes.sort((a, b) => b.order - a.order);
|
||||
|
||||
const recorder = host.beginUpdate(modulePath);
|
||||
for (const change of changes) {
|
||||
if (change.type === 'insert') {
|
||||
for (const change of orderedChanges) {
|
||||
if (change instanceof InsertChange) {
|
||||
recorder.insertLeft(change.pos, change.toAdd);
|
||||
} else if (change.type === 'remove') {
|
||||
recorder.remove((<any>change).pos - 1, (<any>change).toRemove.length + 1);
|
||||
} else if (change instanceof RemoveChange) {
|
||||
recorder.remove(change.pos - 1, change.toRemove.length + 1);
|
||||
} else if (change instanceof ReplaceChange) {
|
||||
recorder.remove(change.pos, change.oldText.length);
|
||||
recorder.insertLeft(change.pos, change.newText);
|
||||
} else if (change.type === 'noop') {
|
||||
// do nothing
|
||||
} else if (change.type === 'replace') {
|
||||
const action = <any>change;
|
||||
recorder.remove(action.pos, action.oldText.length);
|
||||
recorder.insertLeft(action.pos, action.newText);
|
||||
} else {
|
||||
throw new Error(`Unexpected Change '${change.constructor.name}'`);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user