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",
|
"sourceType": "module",
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
"ignorePatterns": ["**/*"],
|
"ignorePatterns": ["**/*"],
|
||||||
"plugins": ["@typescript-eslint", "@nrwl/nx"],
|
"plugins": ["@typescript-eslint", "@nrwl/nx"],
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@ -136,7 +136,7 @@ Run all tests serially in the current process (rather than creating a worker poo
|
|||||||
|
|
||||||
Type: `string`
|
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
|
### showConfig
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ Node module that implements a custom results processor. (https://jestjs.io/docs/
|
|||||||
|
|
||||||
Type: `string`
|
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
|
### updateSnapshot
|
||||||
|
|
||||||
|
|||||||
@ -137,7 +137,7 @@ Run all tests serially in the current process (rather than creating a worker poo
|
|||||||
|
|
||||||
Type: `string`
|
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
|
### showConfig
|
||||||
|
|
||||||
@ -187,7 +187,7 @@ Node module that implements a custom results processor. (https://jestjs.io/docs/
|
|||||||
|
|
||||||
Type: `string`
|
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
|
### updateSnapshot
|
||||||
|
|
||||||
|
|||||||
@ -337,6 +337,11 @@ forEachCli((currentCLIName) => {
|
|||||||
stripIndents`module.exports = {
|
stripIndents`module.exports = {
|
||||||
name: '${nestlib}',
|
name: '${nestlib}',
|
||||||
preset: '../../jest.config.js',
|
preset: '../../jest.config.js',
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
'^.+\\.[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",
|
"version": "9.2.0-beta.3",
|
||||||
"description": "Update jest to v25",
|
"description": "Update jest to v25",
|
||||||
"factory": "./src/migrations/update-9-2-0/update-9-2-0"
|
"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": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -61,16 +61,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
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: [],
|
testPathPattern: [],
|
||||||
watch: false,
|
watch: false,
|
||||||
},
|
},
|
||||||
@ -103,16 +93,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
expect(runCLI).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
_: ['lib.spec.ts'],
|
_: ['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,
|
coverage: false,
|
||||||
runInBand: true,
|
runInBand: true,
|
||||||
testNamePattern: 'should load',
|
testNamePattern: 'should load',
|
||||||
@ -147,16 +127,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
expect(runCLI).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
_: ['file1.ts', 'file2.ts'],
|
_: ['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,
|
coverage: false,
|
||||||
findRelatedTests: true,
|
findRelatedTests: true,
|
||||||
runInBand: true,
|
runInBand: true,
|
||||||
@ -206,16 +176,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
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,
|
coverage: true,
|
||||||
bail: 1,
|
bail: 1,
|
||||||
color: false,
|
color: false,
|
||||||
@ -260,16 +220,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
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%',
|
maxWorkers: '50%',
|
||||||
testPathPattern: [],
|
testPathPattern: [],
|
||||||
},
|
},
|
||||||
@ -292,16 +242,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
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'],
|
setupFilesAfterEnv: ['/root/test-setup.ts'],
|
||||||
testPathPattern: [],
|
testPathPattern: [],
|
||||||
watch: false,
|
watch: false,
|
||||||
@ -350,18 +290,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
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'],
|
setupFilesAfterEnv: ['/root/test-setup.ts'],
|
||||||
testPathPattern: [],
|
testPathPattern: [],
|
||||||
watch: false,
|
watch: false,
|
||||||
@ -400,7 +328,6 @@ describe('Jest Builder', () => {
|
|||||||
expect(runCLI).toHaveBeenCalledWith(
|
expect(runCLI).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
_: [],
|
_: [],
|
||||||
globals: '{}',
|
|
||||||
testPathPattern: [],
|
testPathPattern: [],
|
||||||
watch: false,
|
watch: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,14 +11,14 @@ import { JestBuilderOptions } from './schema';
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV == null || process.env.NODE_ENV == undefined) {
|
if (process.env.NODE_ENV == null || process.env.NODE_ENV == undefined) {
|
||||||
(process.env as any).NODE_ENV = 'test';
|
(process.env as any).NODE_ENV = 'test';
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createBuilder<JestBuilderOptions>(run);
|
|
||||||
|
|
||||||
function run(
|
function run(
|
||||||
options: JestBuilderOptions,
|
options: JestBuilderOptions,
|
||||||
context: BuilderContext
|
context: BuilderContext
|
||||||
@ -28,9 +28,11 @@ function run(
|
|||||||
const jestConfig: {
|
const jestConfig: {
|
||||||
transform: any;
|
transform: any;
|
||||||
globals: any;
|
globals: any;
|
||||||
|
setupFilesAfterEnv: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
} = require(options.jestConfig);
|
} = 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')) {
|
if (transformers.includes('babel-jest') && transformers.includes('ts-jest')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Using babel-jest and ts-jest together is not supported.\n' +
|
'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 = {
|
const config: any = {
|
||||||
_: [],
|
_: [],
|
||||||
config: options.config,
|
config: options.config,
|
||||||
@ -95,13 +68,15 @@ function run(
|
|||||||
useStderr: options.useStderr,
|
useStderr: options.useStderr,
|
||||||
watch: options.watch,
|
watch: options.watch,
|
||||||
watchAll: options.watchAll,
|
watchAll: options.watchAll,
|
||||||
globals: JSON.stringify(globals),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for backwards compatibility
|
||||||
if (options.setupFile) {
|
if (options.setupFile) {
|
||||||
config.setupFilesAfterEnv = [
|
const setupFilesAfterEnvSet = new Set([
|
||||||
|
...(jestConfig.setupFilesAfterEnv ?? []),
|
||||||
path.resolve(context.workspaceRoot, options.setupFile),
|
path.resolve(context.workspaceRoot, options.setupFile),
|
||||||
];
|
]);
|
||||||
|
config.setupFilesAfterEnv = Array.from(setupFilesAfterEnvSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.testFile) {
|
if (options.testFile) {
|
||||||
@ -132,3 +107,5 @@ function run(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default createBuilder<JestBuilderOptions>(run);
|
||||||
|
|||||||
@ -29,12 +29,14 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tsConfig": {
|
"tsConfig": {
|
||||||
"description": "The name of the Typescript configuration file.",
|
"description": "[Deprecated] The name of the Typescript configuration file. Set the tsconfig option in the jest config file. ",
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"x-deprecated": true
|
||||||
},
|
},
|
||||||
"setupFile": {
|
"setupFile": {
|
||||||
"description": "The name of a setup file used by Jest. (https://jestjs.io/docs/en/configuration#setupfilesafterenv-array)",
|
"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"
|
"type": "string",
|
||||||
|
"x-deprecated": true
|
||||||
},
|
},
|
||||||
"bail": {
|
"bail": {
|
||||||
"alias": "b",
|
"alias": "b",
|
||||||
@ -150,5 +152,5 @@
|
|||||||
"type": "boolean"
|
"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 = {
|
module.exports = {
|
||||||
name: '<%= project %>',
|
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) { %>
|
testEnvironment: '<%= testEnvironment %>',<% } %><% if (supportTsx) { %>
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.[tj]sx?$': <% if (transformer == 'babel-jest') { %>[ 'babel-jest',
|
'^.+\\.[tj]sx?$': <% if (transformer == 'babel-jest') { %>[ 'babel-jest',
|
||||||
@ -13,4 +25,4 @@ module.exports = {
|
|||||||
'jest-preset-angular/build/AngularSnapshotSerializer.js',
|
'jest-preset-angular/build/AngularSnapshotSerializer.js',
|
||||||
'jest-preset-angular/build/HTMLCommentSerializer.js'
|
'jest-preset-angular/build/HTMLCommentSerializer.js'
|
||||||
]<% } %>
|
]<% } %>
|
||||||
};
|
};
|
||||||
@ -2,6 +2,7 @@ import { Tree } from '@angular-devkit/schematics';
|
|||||||
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
|
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
|
||||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||||
import { callRule, runSchematic } from '../../utils/testing';
|
import { callRule, runSchematic } from '../../utils/testing';
|
||||||
|
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
|
||||||
|
|
||||||
describe('jestProject', () => {
|
describe('jestProject', () => {
|
||||||
let appTree: Tree;
|
let appTree: Tree;
|
||||||
@ -84,10 +85,15 @@ describe('jestProject', () => {
|
|||||||
},
|
},
|
||||||
appTree
|
appTree
|
||||||
);
|
);
|
||||||
expect(resultTree.readContent('libs/lib1/jest.config.js'))
|
expect(stripIndents`${resultTree.readContent('libs/lib1/jest.config.js')}`)
|
||||||
.toBe(`module.exports = {
|
.toBe(stripIndents`module.exports = {
|
||||||
name: 'lib1',
|
name: 'lib1',
|
||||||
preset: '../../jest.config.js',
|
preset: '../../jest.config.js',
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
tsConfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
}
|
||||||
|
},
|
||||||
coverageDirectory: '../../coverage/libs/lib1',
|
coverageDirectory: '../../coverage/libs/lib1',
|
||||||
snapshotSerializers: [
|
snapshotSerializers: [
|
||||||
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
|
'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
|
||||||
@ -145,6 +151,47 @@ describe('jestProject', () => {
|
|||||||
appTree
|
appTree
|
||||||
);
|
);
|
||||||
expect(resultTree.exists('src/test-setup.ts')).toBeFalsy();
|
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 () => {
|
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,
|
updateNxJsonInTree,
|
||||||
addProjectToNxJsonInTree,
|
addProjectToNxJsonInTree,
|
||||||
readNxJsonInTree,
|
readNxJsonInTree,
|
||||||
|
InsertChange,
|
||||||
|
ReplaceChange,
|
||||||
|
RemoveChange,
|
||||||
} from './src/utils/ast-utils';
|
} from './src/utils/ast-utils';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -163,8 +163,8 @@ export class RemoveChange implements Change {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public path: string,
|
public path: string,
|
||||||
private pos: number,
|
public pos: number,
|
||||||
private toRemove: string
|
public toRemove: string
|
||||||
) {
|
) {
|
||||||
if (pos < 0) {
|
if (pos < 0) {
|
||||||
throw new Error('Negative positions are invalid');
|
throw new Error('Negative positions are invalid');
|
||||||
@ -189,9 +189,9 @@ export class ReplaceChange implements Change {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public path: string,
|
public path: string,
|
||||||
private pos: number,
|
public pos: number,
|
||||||
private oldText: string,
|
public oldText: string,
|
||||||
private newText: string
|
public newText: string
|
||||||
) {
|
) {
|
||||||
if (pos < 0) {
|
if (pos < 0) {
|
||||||
throw new Error('Negative positions are invalid');
|
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) {
|
if (changes.length < 1) {
|
||||||
return;
|
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);
|
const recorder = host.beginUpdate(modulePath);
|
||||||
for (const change of changes) {
|
for (const change of orderedChanges) {
|
||||||
if (change.type === 'insert') {
|
if (change instanceof InsertChange) {
|
||||||
recorder.insertLeft(change.pos, change.toAdd);
|
recorder.insertLeft(change.pos, change.toAdd);
|
||||||
} else if (change.type === 'remove') {
|
} else if (change instanceof RemoveChange) {
|
||||||
recorder.remove((<any>change).pos - 1, (<any>change).toRemove.length + 1);
|
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') {
|
} else if (change.type === 'noop') {
|
||||||
// do nothing
|
// 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 {
|
} else {
|
||||||
throw new Error(`Unexpected Change '${change.constructor.name}'`);
|
throw new Error(`Unexpected Change '${change.constructor.name}'`);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user