479 lines
13 KiB
JavaScript
479 lines
13 KiB
JavaScript
'use strict'; /**
|
|
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
const crypto = require('crypto');
|
|
const path = require('path');var _require =
|
|
require('jest-validate');const ValidationError = _require.ValidationError,validate = _require.validate;
|
|
const chalk = require('chalk');
|
|
const glob = require('glob');
|
|
const Resolver = require('jest-resolve');
|
|
const utils = require('jest-regex-util');var _require2 =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
require('./utils');const BULLET = _require2.BULLET,DOCUMENTATION_NOTE = _require2.DOCUMENTATION_NOTE,_replaceRootDirInPath = _require2._replaceRootDirInPath,_replaceRootDirTags = _require2._replaceRootDirTags,getTestEnvironment = _require2.getTestEnvironment,resolve = _require2.resolve;var _require3 =
|
|
|
|
|
|
|
|
|
|
require('./constants');const NODE_MODULES = _require3.NODE_MODULES,DEFAULT_JS_PATTERN = _require3.DEFAULT_JS_PATTERN,DEFAULT_REPORTER_LABEL = _require3.DEFAULT_REPORTER_LABEL;var _require4 =
|
|
require('./reporterValidationErrors');const validateReporters = _require4.validateReporters;
|
|
const DEFAULT_CONFIG = require('./defaults');
|
|
const DEPRECATED_CONFIG = require('./deprecated');
|
|
const setFromArgv = require('./setFromArgv');
|
|
const VALID_CONFIG = require('./validConfig');
|
|
const ERROR = `${BULLET}Validation Error`;
|
|
const JSON_EXTENSION = '.json';
|
|
const PRESET_NAME = 'jest-preset' + JSON_EXTENSION;
|
|
|
|
const createConfigError = message =>
|
|
new ValidationError(ERROR, message, DOCUMENTATION_NOTE);
|
|
|
|
const setupPreset = (
|
|
options,
|
|
optionsPreset) =>
|
|
{
|
|
let preset;
|
|
const presetPath = _replaceRootDirInPath(options.rootDir, optionsPreset);
|
|
const presetModule = Resolver.findNodeModule(
|
|
presetPath.endsWith(JSON_EXTENSION) ?
|
|
presetPath :
|
|
path.join(presetPath, PRESET_NAME),
|
|
{
|
|
basedir: options.rootDir });
|
|
|
|
|
|
|
|
try {
|
|
// $FlowFixMe
|
|
preset = require(presetModule);
|
|
} catch (error) {
|
|
throw createConfigError(` Preset ${chalk.bold(presetPath)} not found.`);
|
|
}
|
|
|
|
if (options.setupFiles) {
|
|
options.setupFiles = (preset.setupFiles || []).concat(options.setupFiles);
|
|
}
|
|
if (options.modulePathIgnorePatterns && preset.modulePathIgnorePatterns) {
|
|
options.modulePathIgnorePatterns = preset.modulePathIgnorePatterns.concat(
|
|
options.modulePathIgnorePatterns);
|
|
|
|
}
|
|
if (options.moduleNameMapper && preset.moduleNameMapper) {
|
|
options.moduleNameMapper = Object.assign(
|
|
{},
|
|
preset.moduleNameMapper,
|
|
options.moduleNameMapper);
|
|
|
|
}
|
|
|
|
// $FlowFixMe
|
|
return Object.assign({}, preset, options);
|
|
};
|
|
|
|
const setupBabelJest = options => {
|
|
const basedir = options.rootDir;
|
|
const transform = options.transform;
|
|
let babelJest;
|
|
if (transform) {
|
|
const customJSPattern = Object.keys(transform).find(pattern => {
|
|
const regex = new RegExp(pattern);
|
|
return regex.test('a.js') || regex.test('a.jsx');
|
|
});
|
|
|
|
if (customJSPattern) {
|
|
const jsTransformer = Resolver.findNodeModule(
|
|
transform[customJSPattern],
|
|
{ basedir });
|
|
|
|
if (
|
|
jsTransformer &&
|
|
jsTransformer.includes(NODE_MODULES + 'babel-jest'))
|
|
{
|
|
babelJest = jsTransformer;
|
|
}
|
|
}
|
|
} else {
|
|
babelJest = Resolver.findNodeModule('babel-jest', { basedir });
|
|
if (babelJest) {
|
|
options.transform = {
|
|
[DEFAULT_JS_PATTERN]: 'babel-jest' };
|
|
|
|
}
|
|
}
|
|
|
|
return babelJest;
|
|
};
|
|
|
|
const normalizeCollectCoverageOnlyFrom = (
|
|
options,
|
|
key) =>
|
|
{
|
|
const collectCoverageOnlyFrom = Array.isArray(options[key]) ?
|
|
options[key] // passed from argv
|
|
: Object.keys(options[key]); // passed from options
|
|
return collectCoverageOnlyFrom.reduce((map, filePath) => {
|
|
filePath = path.resolve(
|
|
options.rootDir,
|
|
_replaceRootDirInPath(options.rootDir, filePath));
|
|
|
|
map[filePath] = true;
|
|
return map;
|
|
}, Object.create(null));
|
|
};
|
|
|
|
const normalizeCollectCoverageFrom = (options, key) => {
|
|
let value;
|
|
if (!options[key]) {
|
|
value = [];
|
|
}
|
|
|
|
if (!Array.isArray(options[key])) {
|
|
try {
|
|
value = JSON.parse(options[key]);
|
|
} catch (e) {}
|
|
|
|
Array.isArray(value) || (value = [options[key]]);
|
|
} else {
|
|
value = options[key];
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
const normalizeUnmockedModulePathPatterns = (
|
|
options,
|
|
key) =>
|
|
{
|
|
// _replaceRootDirTags is specifically well-suited for substituting
|
|
// <rootDir> in paths (it deals with properly interpreting relative path
|
|
// separators, etc).
|
|
//
|
|
// For patterns, direct global substitution is far more ideal, so we
|
|
// special case substitutions for patterns here.
|
|
return options[key].map(pattern =>
|
|
utils.replacePathSepForRegex(
|
|
pattern.replace(/<rootDir>/g, options.rootDir)));
|
|
|
|
|
|
};
|
|
|
|
const normalizePreprocessor = options => {
|
|
if (options.scriptPreprocessor && options.transform) {
|
|
throw createConfigError(
|
|
` Options: ${chalk.bold('scriptPreprocessor')} and ${chalk.bold('transform')} cannot be used together.
|
|
Please change your configuration to only use ${chalk.bold('transform')}.`);
|
|
|
|
}
|
|
|
|
if (options.preprocessorIgnorePatterns && options.transformIgnorePatterns) {
|
|
throw createConfigError(
|
|
` Options ${chalk.bold('preprocessorIgnorePatterns')} and ${chalk.bold('transformIgnorePatterns')} cannot be used together.
|
|
Please change your configuration to only use ${chalk.bold('transformIgnorePatterns')}.`);
|
|
|
|
}
|
|
|
|
if (options.scriptPreprocessor) {
|
|
options.transform = {
|
|
'.*': options.scriptPreprocessor };
|
|
|
|
}
|
|
|
|
if (options.preprocessorIgnorePatterns) {
|
|
options.transformIgnorePatterns = options.preprocessorIgnorePatterns;
|
|
}
|
|
|
|
delete options.scriptPreprocessor;
|
|
delete options.preprocessorIgnorePatterns;
|
|
return options;
|
|
};
|
|
|
|
const normalizeMissingOptions = options => {
|
|
if (!options.name) {
|
|
options.name = crypto.
|
|
createHash('md5').
|
|
update(options.rootDir).
|
|
digest('hex');
|
|
}
|
|
|
|
if (!options.setupFiles) {
|
|
options.setupFiles = [];
|
|
}
|
|
|
|
return options;
|
|
};
|
|
|
|
const normalizeRootDir = options => {
|
|
// Assert that there *is* a rootDir
|
|
if (!options.hasOwnProperty('rootDir')) {
|
|
throw createConfigError(
|
|
` Configuration option ${chalk.bold('rootDir')} must be specified.`);
|
|
|
|
}
|
|
options.rootDir = path.normalize(options.rootDir);
|
|
return options;
|
|
};
|
|
|
|
const normalizeReporters = (options, basedir) => {
|
|
const reporters = options.reporters;
|
|
if (!reporters || !Array.isArray(reporters)) {
|
|
return options;
|
|
}
|
|
|
|
validateReporters(reporters);
|
|
options.reporters = reporters.map(reporterConfig => {
|
|
const normalizedReporterConfig = typeof reporterConfig ===
|
|
'string' ?
|
|
// if reporter config is a string, we wrap it in an array
|
|
// and pass an empty object for options argument, to normalize
|
|
// the shape.
|
|
[reporterConfig, {}] :
|
|
reporterConfig;
|
|
|
|
const reporterPath = _replaceRootDirInPath(
|
|
options.rootDir,
|
|
normalizedReporterConfig[0]);
|
|
|
|
|
|
if (reporterPath !== DEFAULT_REPORTER_LABEL) {
|
|
const reporter = Resolver.findNodeModule(reporterPath, {
|
|
basedir: options.rootDir });
|
|
|
|
if (!reporter) {
|
|
throw new Error(
|
|
`Could not resolve a module for a custom reporter.\n` +
|
|
` Module name: ${reporterPath}`);
|
|
|
|
}
|
|
normalizedReporterConfig[0] = reporter;
|
|
}
|
|
return normalizedReporterConfig;
|
|
});
|
|
|
|
return options;
|
|
};
|
|
|
|
function normalize(options, argv) {var _validate =
|
|
validate(options, {
|
|
comment: DOCUMENTATION_NOTE,
|
|
deprecatedConfig: DEPRECATED_CONFIG,
|
|
exampleConfig: VALID_CONFIG });const hasDeprecationWarnings = _validate.hasDeprecationWarnings;
|
|
|
|
|
|
options = normalizePreprocessor(
|
|
normalizeReporters(
|
|
normalizeMissingOptions(normalizeRootDir(setFromArgv(options, argv)))));
|
|
|
|
|
|
|
|
if (options.preset) {
|
|
options = setupPreset(options, options.preset);
|
|
}
|
|
|
|
if (options.testEnvironment) {
|
|
options.testEnvironment = getTestEnvironment(options);
|
|
}
|
|
|
|
if (!options.roots && options.testPathDirs) {
|
|
options.roots = options.testPathDirs;
|
|
delete options.testPathDirs;
|
|
}
|
|
if (!options.roots) {
|
|
options.roots = [options.rootDir];
|
|
}
|
|
|
|
if (!options.testRunner || options.testRunner === 'jasmine2') {
|
|
options.testRunner = require.resolve('jest-jasmine2');
|
|
}
|
|
|
|
const babelJest = setupBabelJest(options);
|
|
const newOptions = Object.assign({}, DEFAULT_CONFIG);
|
|
// Cast back to exact type
|
|
options = options;
|
|
Object.keys(options).reduce((newOptions, key) => {
|
|
let value;
|
|
switch (key) {
|
|
case 'collectCoverageOnlyFrom':
|
|
value = normalizeCollectCoverageOnlyFrom(options, key);
|
|
break;
|
|
case 'setupFiles':
|
|
case 'snapshotSerializers':
|
|
value =
|
|
options[key] &&
|
|
options[key].map(resolve.bind(null, options.rootDir, key));
|
|
break;
|
|
case 'modulePaths':
|
|
case 'roots':
|
|
value =
|
|
options[key] &&
|
|
options[key].map(filePath =>
|
|
path.resolve(
|
|
options.rootDir,
|
|
_replaceRootDirInPath(options.rootDir, filePath)));
|
|
|
|
|
|
break;
|
|
case 'collectCoverageFrom':
|
|
value = normalizeCollectCoverageFrom(options, key);
|
|
break;
|
|
case 'cacheDirectory':
|
|
case 'coverageDirectory':
|
|
value =
|
|
options[key] &&
|
|
path.resolve(
|
|
options.rootDir,
|
|
_replaceRootDirInPath(options.rootDir, options[key]));
|
|
|
|
break;
|
|
case 'moduleLoader':
|
|
case 'resolver':
|
|
case 'setupTestFrameworkScriptFile':
|
|
case 'testResultsProcessor':
|
|
case 'testRunner':
|
|
value = options[key] && resolve(options.rootDir, key, options[key]);
|
|
break;
|
|
case 'moduleNameMapper':
|
|
const moduleNameMapper = options[key];
|
|
value =
|
|
moduleNameMapper &&
|
|
Object.keys(moduleNameMapper).map(regex => {
|
|
const item = moduleNameMapper && moduleNameMapper[regex];
|
|
return item && [regex, _replaceRootDirTags(options.rootDir, item)];
|
|
});
|
|
break;
|
|
case 'transform':
|
|
const transform = options[key];
|
|
value =
|
|
transform &&
|
|
Object.keys(transform).map(regex => [
|
|
regex,
|
|
resolve(options.rootDir, key, transform[regex])]);
|
|
|
|
break;
|
|
case 'coveragePathIgnorePatterns':
|
|
case 'modulePathIgnorePatterns':
|
|
case 'testPathIgnorePatterns':
|
|
case 'transformIgnorePatterns':
|
|
case 'unmockedModulePathPatterns':
|
|
value = normalizeUnmockedModulePathPatterns(options, key);
|
|
break;
|
|
case 'haste':
|
|
value = Object.assign({}, options[key]);
|
|
if (value.hasteImplModulePath != null) {
|
|
value.hasteImplModulePath = resolve(
|
|
options.rootDir,
|
|
'haste.hasteImplModulePath',
|
|
_replaceRootDirInPath(options.rootDir, value.hasteImplModulePath));
|
|
|
|
}
|
|
break;
|
|
case 'projects':
|
|
const projects = options[key];
|
|
let list = [];
|
|
projects &&
|
|
projects.forEach(
|
|
filePath =>
|
|
list = list.concat(
|
|
glob.sync(_replaceRootDirInPath(options.rootDir, filePath))));
|
|
|
|
|
|
value = list;
|
|
break;
|
|
case 'moduleDirectories':
|
|
case 'testMatch':
|
|
value = _replaceRootDirTags(options.rootDir, options[key]);
|
|
break;
|
|
case 'automock':
|
|
case 'bail':
|
|
case 'browser':
|
|
case 'cache':
|
|
case 'clearMocks':
|
|
case 'collectCoverage':
|
|
case 'coverageReporters':
|
|
case 'coverageThreshold':
|
|
case 'expand':
|
|
case 'globals':
|
|
case 'logHeapUsage':
|
|
case 'mapCoverage':
|
|
case 'moduleFileExtensions':
|
|
case 'name':
|
|
case 'noStackTrace':
|
|
case 'notify':
|
|
case 'replname':
|
|
case 'reporters':
|
|
case 'resetMocks':
|
|
case 'resetModules':
|
|
case 'rootDir':
|
|
case 'silent':
|
|
case 'testEnvironment':
|
|
case 'testNamePattern':
|
|
case 'testRegex':
|
|
case 'testURL':
|
|
case 'timers':
|
|
case 'useStderr':
|
|
case 'verbose':
|
|
case 'watch':
|
|
case 'watchman':
|
|
value = options[key];
|
|
break;}
|
|
|
|
newOptions[key] = value;
|
|
return newOptions;
|
|
}, newOptions);
|
|
|
|
newOptions.updateSnapshot = argv.ci && !argv.updateSnapshot ?
|
|
'none' :
|
|
argv.updateSnapshot ? 'all' : 'new';
|
|
|
|
if (babelJest) {
|
|
const regeneratorRuntimePath = Resolver.findNodeModule(
|
|
'regenerator-runtime/runtime',
|
|
{ basedir: options.rootDir });
|
|
|
|
|
|
if (regeneratorRuntimePath) {
|
|
newOptions.setupFiles.unshift(regeneratorRuntimePath);
|
|
}
|
|
}
|
|
|
|
if (options.testRegex && options.testMatch) {
|
|
throw createConfigError(
|
|
` Configuration options ${chalk.bold('testMatch')} and` +
|
|
` ${chalk.bold('testRegex')} cannot be used together.`);
|
|
|
|
}
|
|
|
|
if (options.testRegex && !options.testMatch) {
|
|
// Prevent the default testMatch conflicting with any explicitly
|
|
// configured `testRegex` value
|
|
newOptions.testMatch = [];
|
|
}
|
|
|
|
// If argv.json is set, coverageReporters shouldn't print a text report.
|
|
if (argv.json) {
|
|
newOptions.coverageReporters = (newOptions.coverageReporters || []).
|
|
filter(reporter => reporter !== 'text');
|
|
}
|
|
|
|
return {
|
|
hasDeprecationWarnings,
|
|
options: newOptions };
|
|
|
|
}
|
|
|
|
module.exports = normalize; |