Skip to content

Commit

Permalink
feat(testing): support root project generation for jest
Browse files Browse the repository at this point in the history
  • Loading branch information
barbados-clemens committed Nov 23, 2022
1 parent 2d758af commit 367f2de
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 36 deletions.
12 changes: 12 additions & 0 deletions docs/generated/packages/jest.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript for config files"
},
"rootProject": {
"description": "initialize Jest for an application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": [],
Expand Down Expand Up @@ -123,6 +129,12 @@
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript for config files"
},
"rootProject": {
"description": "Add Jest to an application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": [],
Expand Down
5 changes: 4 additions & 1 deletion packages/jest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ export {
export { jestConfigObjectAst } from './src/utils/config/functions';
export { jestProjectGenerator } from './src/generators/jest-project/jest-project';
export { jestInitGenerator } from './src/generators/init/init';
export { getJestProjects } from './src/utils/config/get-jest-projects';
export {
getJestProjects,
getNestedJestProjects,
} from './src/utils/config/get-jest-projects';
97 changes: 95 additions & 2 deletions packages/jest/src/generators/init/init.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
addProjectConfiguration,
NxJsonConfiguration,
readJson,
readProjectConfiguration,
stripIndents,
Tree,
updateJson,
Expand Down Expand Up @@ -41,9 +43,16 @@ describe('jest', () => {
});

it('should not override existing files', async () => {
tree.write('jest.config.ts', `test`);
const expected = stripIndents`
import { getJestProjects } from '@nrwl/jest';
export default {
projects: getJestProjects(),
extraThing: "Goes Here"
}
`;
tree.write('jest.config.ts', expected);
jestInitGenerator(tree, {});
expect(tree.read('jest.config.ts', 'utf-8')).toEqual('test');
expect(tree.read('jest.config.ts', 'utf-8')).toEqual(expected);
});

it('should add target defaults for test', async () => {
Expand Down Expand Up @@ -144,6 +153,90 @@ describe('jest', () => {
});
});

describe('root project', () => {
it('should not add a monorepo jest.config.ts to the project', () => {
jestInitGenerator(tree, { rootProject: true });
expect(tree.exists('jest.config.ts')).toBeFalsy();
});

it('should rename the project jest.config.ts to project jest config', () => {
tree.write('nx.json', JSON.stringify({ defaultProject: 'my-project' }));
addProjectConfiguration(tree, 'my-project', {
root: '',
name: 'my-project',
sourceRoot: 'src',
targets: {
test: {
executor: '@nrwl/jest:jest',
options: {
jestConfig: 'jest.config.ts',
},
},
},
});
tree.write(
'jest.config.ts',
`
/* eslint-disable */
export default {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
displayName: 'my-project',
testEnvironment: 'node',
preset: '../../jest.preset.js',
};
`
);
jestInitGenerator(tree, { rootProject: false });
expect(tree.exists('jest.my-project.config.ts')).toBeTruthy();
expect(tree.read('jest.config.ts', 'utf-8'))
.toEqual(`import { getJestProjects } from '@nrwl/jest';
export default {
projects: getJestProjects()
};`);
expect(readProjectConfiguration(tree, 'my-project').targets.test)
.toMatchInlineSnapshot(`
Object {
"executor": "@nrwl/jest:jest",
"options": Object {
"jestConfig": "jest.my-project.config.ts",
},
}
`);
});

it('should work with --js', () => {
tree.write(
'jest.config.js',
`
/* eslint-disable */
module.exports = {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
displayName: 'my-project',
testEnvironment: 'node',
preset: '../../jest.preset.js',
};
`
);
jestInitGenerator(tree, { js: true, rootProject: false });
expect(tree.exists('jest.project-config.js')).toBeTruthy();
expect(tree.read('jest.config.js', 'utf-8'))
.toEqual(`const { getJestProjects } = require('@nrwl/jest');
module.exports = {
projects: getJestProjects()
};`);
});
});

describe('adds jest extension', () => {
beforeEach(async () => {
writeJson(tree, '.vscode/extensions.json', {
Expand Down
78 changes: 61 additions & 17 deletions packages/jest/src/generators/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
Tree,
updateJson,
updateWorkspaceConfiguration,
readProjectConfiguration,
readJson,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { findRootJestConfig } from '../../utils/config/find-root-jest-files';
import {
babelJestVersion,
jestTypesVersion,
Expand All @@ -20,18 +24,71 @@ import {
tsNodeVersion,
} from '../../utils/versions';
import { JestInitSchema } from './schema';
import { extname } from 'path';

interface NormalizedSchema extends ReturnType<typeof normalizeOptions> {}

const schemaDefaults = {
compiler: 'tsc',
js: false,
rootProject: false,
} as const;

function createJestConfig(tree: Tree, js: boolean = false) {
function createJestConfig(tree: Tree, options: NormalizedSchema) {
if (!tree.exists('jest.preset.js')) {
// preset is always js file.
tree.write(
`jest.preset.js`,
`
const nxPreset = require('@nrwl/jest/preset').default;
module.exports = { ...nxPreset }`
);

addTestInputs(tree);
}
const rootJestConfig = findRootJestConfig(tree);

if (options.rootProject && !rootJestConfig) {
// we don't want any config to be made because the jest-project generator
// will make the config when using rootProject
return;
}
const isProjectConfig =
rootJestConfig &&
tree.exists(rootJestConfig) &&
!tree.read(rootJestConfig, 'utf-8').includes('getJestProjects()');
if (!options.rootProject && isProjectConfig) {
// moving from single project to multi project.
// TODO(caleb): this is brittle and needs to be detected better?
const nxJson = readJson(tree, 'nx.json');
const defaultProject =
nxJson.cli?.defaultProjectName || nxJson?.defaultProject;

const ext = extname(rootJestConfig);
const newJestConfigPath = defaultProject
? `jest.${defaultProject}.config${ext}`
: `jest.project-config${ext}`;

tree.rename(rootJestConfig, newJestConfigPath);

if (defaultProject) {
const projectConfig = readProjectConfiguration(tree, defaultProject);

// TODO(caleb): do we care about a custom target name?
if (projectConfig?.targets?.['test']?.options?.jestConfig) {
projectConfig.targets['test'].options.jestConfig = newJestConfigPath;
updateProjectConfiguration(tree, defaultProject, projectConfig);
}
} else {
console.warn(stripIndents`Could not find the default project project.json to update the test target options.
Manually update the 'jestConfig' path to point to ${newJestConfigPath}`);
}
}

// if the root ts config already exists then don't make a js one or vice versa
if (!tree.exists('jest.config.ts') && !tree.exists('jest.config.js')) {
const contents = js
const contents = options.js
? stripIndents`
const { getJestProjects } = require('@nrwl/jest');
Expand All @@ -44,20 +101,7 @@ function createJestConfig(tree: Tree, js: boolean = false) {
export default {
projects: getJestProjects()
};`;
tree.write(`jest.config.${js ? 'js' : 'ts'}`, contents);
}

if (!tree.exists('jest.preset.js')) {
// preset is always js file.
tree.write(
`jest.preset.js`,
`
const nxPreset = require('@nrwl/jest/preset').default;
module.exports = { ...nxPreset }`
);

addTestInputs(tree);
tree.write(`jest.config.${options.js ? 'js' : 'ts'}`, contents);
}
}

Expand Down Expand Up @@ -144,7 +188,7 @@ function updateExtensions(host: Tree) {

export function jestInitGenerator(tree: Tree, schema: JestInitSchema) {
const options = normalizeOptions(schema);
createJestConfig(tree, options.js);
createJestConfig(tree, options);

let installTask: GeneratorCallback = () => {};
if (!options.skipPackageJson) {
Expand Down
1 change: 1 addition & 0 deletions packages/jest/src/generators/init/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface JestInitSchema {
* @deprecated
*/
babelJest?: boolean;
rootProject?: boolean;
}
6 changes: 6 additions & 0 deletions packages/jest/src/generators/init/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
"type": "boolean",
"default": false,
"description": "Use JavaScript instead of TypeScript for config files"
},
"rootProject": {
"description": "initialize Jest for an application at the root of the workspace",
"type": "boolean",
"default": false,
"hidden": true
}
},
"required": []
Expand Down
69 changes: 69 additions & 0 deletions packages/jest/src/generators/jest-project/jest-project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,73 @@ describe('jestProject', () => {
expect(tree.read('libs/lib1/jest.config.ts', 'utf-8')).toMatchSnapshot();
});
});

describe('root project', () => {
it('root jest.config.ts should be project config', async () => {
writeJson(tree, 'tsconfig.json', {
files: [],
include: [],
references: [],
});
addProjectConfiguration(tree, 'my-project', {
root: '',
sourceRoot: 'src',
name: 'my-project',
targets: {},
});
await jestProjectGenerator(tree, {
...defaultOptions,
project: 'my-project',
rootProject: true,
});
expect(tree.read('jest.config.ts', 'utf-8')).toMatchInlineSnapshot(`
"/* eslint-disable */
export default {
displayName: 'my-project',
preset: '../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
}
},
coverageDirectory: '../coverage/'
};
"
`);
});

it('root jest.config.js should be project config', async () => {
writeJson(tree, 'tsconfig.json', {
files: [],
include: [],
references: [],
});
addProjectConfiguration(tree, 'my-project', {
root: '',
sourceRoot: 'src',
name: 'my-project',
targets: {},
});
await jestProjectGenerator(tree, {
...defaultOptions,
project: 'my-project',
rootProject: true,
js: true,
});
expect(tree.read('jest.config.js', 'utf-8')).toMatchInlineSnapshot(`
"/* eslint-disable */
module.exports = {
displayName: 'my-project',
preset: '../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
}
},
coverageDirectory: '../coverage/'
};
"
`);
});
});
});
9 changes: 7 additions & 2 deletions packages/jest/src/generators/jest-project/jest-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const schemaDefaults = {
supportTsx: false,
skipSetupFile: false,
skipSerializers: false,
rootProject: false,
} as const;

function normalizeOptions(options: JestProjectSchema) {
Expand Down Expand Up @@ -42,7 +43,7 @@ function normalizeOptions(options: JestProjectSchema) {

// setupFile is always 'none'
options.setupFile = schemaDefaults.setupFile;

options.rootProject = options.rootProject;
return {
...schemaDefaults,
...options,
Expand All @@ -59,7 +60,11 @@ export async function jestProjectGenerator(
createFiles(tree, options);
updateTsConfig(tree, options);
updateWorkspace(tree, options);
updateJestConfig(tree, options);
// TODO(caleb): is this really needed anymore?
// surely everyone is on the getJestProjects() fn usage already?
// should remove it and provide a migration (just in case) so we can remove it
// which makes the root project work simpler
// updateJestConfig(tree, options);
if (!schema.skipFormat) {
await formatFiles(tree);
}
Expand Down
Loading

0 comments on commit 367f2de

Please sign in to comment.