@nx/jest
Version:
188 lines (186 loc) • 8.51 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.jestExecutor = jestExecutor;
exports.parseJestConfig = parseJestConfig;
exports.batchJest = batchJest;
const jest_1 = require("jest");
const jest_config_1 = require("jest-config");
const reporters_1 = require("@jest/reporters");
const test_result_1 = require("@jest/test-result");
const path = require("path");
const path_1 = require("path");
const devkit_1 = require("@nx/devkit");
const summary_1 = require("./summary");
const fs_1 = require("fs");
process.env.NODE_ENV ??= 'test';
async function jestExecutor(options, context) {
// Jest registers ts-node with module CJS https://github.com/SimenB/jest/blob/v29.6.4/packages/jest-config/src/readConfigFileAndSetRootDir.ts#L117-L119
// We want to support of ESM via 'module':'nodenext', we need to override the resolution until Jest supports it.
const existingValue = process.env['TS_NODE_COMPILER_OPTIONS'];
process.env['TS_NODE_COMPILER_OPTIONS'] = JSON.stringify({
...(existingValue ? JSON.parse(existingValue) : {}),
moduleResolution: 'Node10',
customConditions: null,
});
const config = await parseJestConfig(options, context);
const { results } = await (0, jest_1.runCLI)(config, [options.jestConfig]);
return { success: results.success };
}
function getExtraArgs(options, schema) {
const extraArgs = {};
for (const key of Object.keys(options)) {
if (!schema.properties[key]) {
extraArgs[key] = options[key];
process.argv.push(`--${key}=${options[key]}`);
}
}
return extraArgs;
}
async function parseJestConfig(options, context, multiProjects = false) {
let jestConfig;
// support passing extra args to jest cli supporting 3rd party plugins
// like 'jest-runner-groups' --group arg
const schema = await Promise.resolve().then(() => require('./schema.json'));
const extraArgs = getExtraArgs(options, schema);
const config = {
...extraArgs,
$0: undefined,
_: [],
config: options.config,
coverage: options.codeCoverage,
bail: options.bail,
ci: options.ci,
color: options.color,
detectOpenHandles: options.detectOpenHandles,
forceExit: options.forceExit,
logHeapUsage: options.logHeapUsage,
detectLeaks: options.detectLeaks,
json: options.json,
maxWorkers: options.maxWorkers,
onlyChanged: options.onlyChanged,
changedSince: options.changedSince,
outputFile: options.outputFile,
passWithNoTests: options.passWithNoTests,
runInBand: options.runInBand,
showConfig: options.showConfig,
silent: options.silent,
testLocationInResults: options.testLocationInResults,
testNamePattern: options.testNamePattern,
testPathPattern: options.testPathPattern,
testPathIgnorePatterns: options.testPathIgnorePatterns,
testTimeout: options.testTimeout,
colors: options.colors,
verbose: options.verbose,
testResultsProcessor: options.testResultsProcessor,
updateSnapshot: options.updateSnapshot,
useStderr: options.useStderr,
watch: options.watch,
watchAll: options.watchAll,
randomize: options.randomize,
};
if (!multiProjects) {
options.jestConfig = path.resolve(context.root, options.jestConfig);
jestConfig = (await (0, jest_config_1.readConfig)(config, options.jestConfig)).projectConfig;
}
// for backwards compatibility
if (options.setupFile && !multiProjects) {
const setupFilesAfterEnvSet = new Set([
...(jestConfig.setupFilesAfterEnv ?? []),
path.resolve(context.root, options.setupFile),
]);
config.setupFilesAfterEnv = Array.from(setupFilesAfterEnvSet);
}
if (options.testFile) {
config._.push(options.testFile);
}
if (options.findRelatedTests) {
const parsedTests = options.findRelatedTests
.split(',')
.map((s) => s.trim());
config._.push(...parsedTests);
config.findRelatedTests = true;
}
if (options.coverageDirectory) {
config.coverageDirectory = path.join(context.root, options.coverageDirectory);
}
if (options.clearCache) {
config.clearCache = true;
}
if (options.reporters && options.reporters.length > 0) {
config.reporters = options.reporters;
}
if (Array.isArray(options.coverageReporters) &&
options.coverageReporters.length > 0) {
config.coverageReporters = options.coverageReporters;
}
return config;
}
exports.default = jestExecutor;
async function batchJest(taskGraph, inputs, overrides, context) {
let configPaths = [];
let selectedProjects = [];
let projectsWithNoName = [];
for (const task of taskGraph.roots) {
let configPath = path.resolve(context.root, inputs[task].jestConfig);
configPaths.push(configPath);
/* The display name in the jest.config.js is the correct project name jest
* uses to determine projects. It is usually the same as the Nx projectName
* but it can be changed. The safest method is to extract the displayName
* from the config file, but skip the project if it does not exist. */
const displayNameValueRegex = new RegExp(/(['"]+.*['"])(?<=displayName+.*)/, 'g');
const fileContents = (0, fs_1.readFileSync)(configPath, { encoding: 'utf-8' });
if (!displayNameValueRegex.test(fileContents)) {
projectsWithNoName.push([task.split(':')[0], configPath]);
continue;
}
const displayName = fileContents
.match(displayNameValueRegex)
.map((value) => value.substring(1, value.length - 1))[0];
selectedProjects.push(displayName);
}
if (projectsWithNoName.length > 0) {
throw new Error((0, devkit_1.stripIndents) `Some projects do not have a "displayName" property. Jest Batch Mode requires this to be set. Please ensure this value is set.
Projects missing "displayName":
${projectsWithNoName.map(([project, configPath]) => ` - ${project} - ${configPath}\r\n`)}
You can learn more about this requirement from Jest here: https://jestjs.io/docs/cli#--selectprojects-project1--projectn`);
}
const parsedConfigs = await parseJestConfig(overrides, context, true);
const { globalConfig, results } = await (0, jest_1.runCLI)({
...parsedConfigs,
selectProjects: selectedProjects,
}, [devkit_1.workspaceRoot]);
const { configs } = await (0, jest_config_1.readConfigs)({ $0: undefined, _: [] }, configPaths);
const jestTaskExecutionResults = {};
for (let i = 0; i < taskGraph.roots.length; i++) {
let root = taskGraph.roots[i];
const aggregatedResults = (0, test_result_1.makeEmptyAggregatedTestResult)();
aggregatedResults.startTime = results.startTime;
let endTime;
const projectRoot = (0, path_1.join)(context.root, taskGraph.tasks[root].projectRoot);
let resultOutput = '';
for (const testResult of results.testResults) {
if (testResult.testFilePath.startsWith(projectRoot)) {
aggregatedResults.startTime = aggregatedResults.startTime
? Math.min(aggregatedResults.startTime, testResult.perfStats.start)
: testResult.perfStats.start;
endTime = endTime
? Math.max(testResult.perfStats.end, endTime)
: testResult.perfStats.end;
(0, test_result_1.addResult)(aggregatedResults, testResult);
resultOutput +=
'\n\r' +
reporters_1.utils.getResultHeader(testResult, globalConfig, configs[i]);
}
}
aggregatedResults.numTotalTestSuites = aggregatedResults.testResults.length;
jestTaskExecutionResults[root] = {
startTime: aggregatedResults.startTime,
endTime,
success: aggregatedResults.numFailedTests === 0,
// TODO(caleb): getSummary assumed endTime is Date.now().
// might need to make own method to correctly set the endtime base on tests instead of _now_
terminalOutput: resultOutput + '\n\r\n\r' + (0, summary_1.getSummary)(aggregatedResults),
};
}
return jestTaskExecutionResults;
}
;