diff --git a/integration_tests/__tests__/__snapshots__/showConfig-test.js.snap b/integration_tests/__tests__/__snapshots__/showConfig-test.js.snap index 8c3fcce1da6d..0d10b92058ad 100644 --- a/integration_tests/__tests__/__snapshots__/showConfig-test.js.snap +++ b/integration_tests/__tests__/__snapshots__/showConfig-test.js.snap @@ -73,6 +73,9 @@ exports[`jest --showConfig outputs config info and exits 1`] = ` "mapCoverage": false, "noStackTrace": false, "notify": false, + "projects": [ + "/mocked/root/path/jest/integration_tests/verbose_reporter" + ], "rootDir": "/mocked/root/path/jest/integration_tests/verbose_reporter", "testPathPattern": "", "testResultsProcessor": null, diff --git a/integration_tests/__tests__/config-test.js b/integration_tests/__tests__/config-test.js index 2d966bfab458..1d7ca3580e39 100644 --- a/integration_tests/__tests__/config-test.js +++ b/integration_tests/__tests__/config-test.js @@ -39,6 +39,16 @@ test('works with sane config JSON', () => { }); test('watchman config option is respected over default argv', () => { + const {stdout} = runJest('verbose_reporter', [ + '--env=node', + '--watchman=false', + '--debug', + ]); + + expect(stdout).toMatch('"watchman": false'); +}); + +test('config from argv is respected with sane config JSON', () => { const {stdout} = runJest('verbose_reporter', [ '--config=' + JSON.stringify({ diff --git a/integration_tests/__tests__/coverage_report-test.js b/integration_tests/__tests__/coverage_report-test.js index 479ce71ce489..dcad6aa00f40 100644 --- a/integration_tests/__tests__/coverage_report-test.js +++ b/integration_tests/__tests__/coverage_report-test.js @@ -78,6 +78,8 @@ test('outputs coverage report as json', () => { try { JSON.parse(stdout); } catch (err) { - throw new Error("Can't parse the JSON result from stdout" + err.toString()); + throw new Error( + "Can't parse the JSON result from stdout. " + err.toString(), + ); } }); diff --git a/integration_tests/runJest.js b/integration_tests/runJest.js index 55e3578721d7..16b07c4340ce 100644 --- a/integration_tests/runJest.js +++ b/integration_tests/runJest.js @@ -34,9 +34,7 @@ function runJest(dir, args) { ); } - const result = spawnSync(JEST_PATH, args || [], { - cwd: dir, - }); + const result = spawnSync(JEST_PATH, args || [], {cwd: dir}); result.stdout = result.stdout && result.stdout.toString(); result.stderr = result.stderr && result.stderr.toString(); diff --git a/packages/jest-cli/src/cli/args.js b/packages/jest-cli/src/cli/args.js index c7ea934f5457..2b57b7aa1282 100644 --- a/packages/jest-cli/src/cli/args.js +++ b/packages/jest-cli/src/cli/args.js @@ -49,17 +49,46 @@ const usage = 'Usage: $0 [--config=] [TestPathPattern]'; const docs = 'Documentation: https://facebook.github.io/jest/'; const options = { + automock: { + default: undefined, + description: 'Automock all files by default.', + type: 'boolean', + }, bail: { alias: 'b', + default: undefined, description: 'Exit the test suite immediately upon the first failing test.', type: 'boolean', }, + browser: { + default: undefined, + description: 'Respect Browserify\'s "browser" field in package.json ' + + 'when resolving modules. Some modules export different versions ' + + 'based on whether they are operating in Node or a browser.', + type: 'boolean', + }, cache: { - default: true, + default: undefined, description: 'Whether to use the transform cache. Disable the cache ' + 'using --no-cache.', type: 'boolean', }, + cacheDirectory: { + description: 'The directory where Jest should store its cached ' + + ' dependency information.', + type: 'string', + }, + clearMocks: { + default: undefined, + description: 'Automatically clear mock calls and instances between every ' + + 'test. Equivalent to calling jest.clearAllMocks() between each test.', + type: 'boolean', + }, + collectCoverage: { + default: undefined, + description: 'Alias for --coverage.', + type: 'boolean', + }, collectCoverageFrom: { description: 'relative to glob pattern matching the files ' + 'that coverage info needs to be collected from.', @@ -70,11 +99,13 @@ const options = { type: 'array', }, color: { + default: undefined, description: 'Forces test results output color highlighting (even if ' + 'stdout is not a TTY). Set to false if you would like to have no colors.', type: 'boolean', }, colors: { + default: undefined, description: 'Alias for `--color`.', type: 'boolean', }, @@ -87,21 +118,37 @@ const options = { type: 'string', }, coverage: { + default: undefined, description: 'Indicates that test coverage information should be ' + 'collected and reported in the output.', type: 'boolean', }, coverageDirectory: { - default: undefined, description: 'The directory where Jest should output its coverage files.', type: 'string', }, + coveragePathIgnorePatterns: { + description: 'An array of regexp pattern strings that are matched ' + + 'against all file paths before executing the test. If the file path' + + 'matches any of the patterns, coverage information will be skipped.', + type: 'array', + }, + coverageReporters: { + description: 'A list of reporter names that Jest uses when writing ' + + 'coverage reports. Any istanbul reporter can be used.', + type: 'array', + }, + coverageThreshold: { + description: 'A JSON string with which will be used to configure ' + + 'minimum threshold enforcement for coverage results', + type: 'string', + }, debug: { + default: undefined, description: 'Print debugging info about your jest config.', type: 'boolean', }, env: { - default: undefined, description: 'The test environment used for all tests. This can point to ' + 'any file or node module. Examples: `jsdom`, `node` or ' + '`path/to/my-environment.js`', @@ -109,41 +156,55 @@ const options = { }, expand: { alias: 'e', - default: false, + default: undefined, description: 'Use this flag to show full diffs instead of a patch.', type: 'boolean', }, findRelatedTests: { + default: undefined, description: 'Find related tests for a list of source files that were ' + 'passed in as arguments. Useful for pre-commit hook integration to run ' + 'the minimal amount of tests necessary.', type: 'boolean', }, forceExit: { - default: false, + default: undefined, description: 'Force Jest to exit after all tests have completed running. ' + 'This is useful when resources set up by test code cannot be ' + 'adequately cleaned up.', type: 'boolean', }, + globals: { + description: 'A JSON string with map of global variables that need ' + + 'to be available in all test environments.', + type: 'string', + }, + haste: { + description: "A JSON string with map of variables for Facebook's " + + '@providesModule module system', + type: 'string', + }, json: { + default: undefined, description: 'Prints the test results in JSON. This mode will send all ' + 'other test output and user messages to stderr.', type: 'boolean', }, lastCommit: { - default: false, + default: undefined, description: 'Will run all tests affected by file changes in the last ' + 'commit made.', type: 'boolean', }, logHeapUsage: { + default: undefined, description: 'Logs the heap usage after every test. Useful to debug ' + 'memory leaks. Use together with `--runInBand` and `--expose-gc` in ' + 'node.', type: 'boolean', }, mapCoverage: { + default: undefined, description: 'Maps code coverage reports against original source code ' + 'when transformers supply source maps.', type: 'boolean', @@ -154,18 +215,50 @@ const options = { 'will spawn for running tests. This defaults to the number of the ' + 'cores available on your machine. (its usually best not to override ' + 'this default)', - type: 'string', // no, yargs -- its a number.. :( + type: 'number', + }, + moduleDirectories: { + description: 'An array of directory names to be searched recursively ' + + "up from the requiring module's location.", + type: 'array', + }, + moduleFileExtensions: { + description: 'An array of file extensions your modules use. If you ' + + 'require modules without specifying a file extension, these are the ' + + 'extensions Jest will look for. ', + type: 'array', + }, + moduleNameMapper: { + description: 'A JSON string with a map from regular expressions to ' + + 'module names that allow to stub out resources, like images or ' + + 'styles with a single module', + type: 'string', + }, + modulePathIgnorePatterns: { + description: 'An array of regexp pattern strings that are matched ' + + 'against all module paths before those paths are to be considered ' + + '"visible" to the module loader.', + type: 'array', + }, + modulePaths: { + description: 'An alternative API to setting the NODE_PATH env variable, ' + + 'modulePaths is an array of absolute paths to additional locations to ' + + 'search when resolving modules.', + type: 'array', }, noStackTrace: { + default: undefined, description: 'Disables stack trace in test results output', type: 'boolean', }, notify: { + default: undefined, description: 'Activates notifications for test results.', type: 'boolean', }, onlyChanged: { alias: 'o', + default: undefined, description: 'Attempts to identify which tests to run based on which ' + "files have changed in the current repository. Only works if you're " + 'running tests in a git repository at the moment.', @@ -176,45 +269,108 @@ const options = { 'also specified.', type: 'string', }, + preset: { + description: "A preset that is used as a base for Jest's configuration.", + type: 'string', + }, projects: { description: 'A list of projects that use Jest to run all tests of all ' + 'projects in a single instance of Jest.', type: 'array', }, + reporters: { + description: 'A list of custom reporters for the test suite.', + type: 'array', + }, + resetMocks: { + default: undefined, + description: 'Automatically reset mock state between every test. ' + + 'Equivalent to calling jest.resetAllMocks() between each test.', + type: 'boolean', + }, + resetModules: { + default: undefined, + description: 'If enabled, the module registry for every test file will ' + + 'be reset before running each individual test.', + type: 'boolean', + }, + resolver: { + description: 'A JSON string which allows the use of a custom resolver.', + type: 'string', + }, + rootDir: { + description: 'The root directory that Jest should scan for tests and ' + + 'modules within.', + type: 'string', + }, + roots: { + description: 'A list of paths to directories that Jest should use to ' + + 'search for files in.', + type: 'array', + }, runInBand: { alias: 'i', + default: undefined, description: 'Run all tests serially in the current process (rather than ' + 'creating a worker pool of child processes that run tests). This ' + 'is sometimes useful for debugging, but such use cases are pretty ' + 'rare.', type: 'boolean', }, + setupFiles: { + description: 'The paths to modules that run some code to configure or ' + + 'set up the testing environment before each test. ', + type: 'array', + }, setupTestFrameworkScriptFile: { description: 'The path to a module that runs some code to configure or ' + 'set up the testing framework before each test.', type: 'string', }, showConfig: { + default: undefined, description: 'Print your jest config and then exits.', type: 'boolean', }, silent: { - default: false, + default: undefined, description: 'Prevent tests from printing messages through the console.', type: 'boolean', }, + snapshotSerializers: { + description: 'A list of paths to snapshot serializer modules Jest should ' + + 'use for snapshot testing.', + type: 'array', + }, + testEnvironment: { + description: 'Alias for --env', + type: 'string', + }, + testMatch: { + description: 'The glob patterns Jest uses to detect test files.', + type: 'array', + }, testNamePattern: { alias: 't', description: 'Run only tests with a name that matches the regex pattern.', type: 'string', }, + testPathIgnorePatterns: { + description: 'An array of regexp pattern strings that are matched ' + + 'against all test paths before executing the test. If the test path ' + + 'matches any of the patterns, it will be skipped.', + type: 'array', + }, testPathPattern: { description: 'A regexp pattern string that is matched against all tests ' + 'paths before executing the test.', type: 'string', }, + testRegex: { + description: 'The regexp pattern Jest uses to detect test files.', + type: 'string', + }, testResultsProcessor: { - default: undefined, description: 'Allows the use of a custom results processor. ' + 'This processor must be a node module that exports ' + 'a function expecting as the first argument the result object', @@ -226,9 +382,34 @@ const options = { '`/path/to/testRunner.js`.', type: 'string', }, + testURL: { + description: 'This option sets the URL for the jsdom environment.', + type: 'string', + }, + timers: { + description: 'Setting this value to fake allows the use of fake timers ' + + 'for functions such as setTimeout.', + type: 'string', + }, + transform: { + description: 'A JSON string which maps from regular expressions to paths ' + + 'to transformers.', + type: 'string', + }, + transformIgnorePatterns: { + description: 'An array of regexp pattern strings that are matched ' + + 'against all source file paths before transformation.', + type: 'array', + }, + unmockedModulePathPatterns: { + description: 'An array of regexp pattern strings that are matched ' + + 'against all modules before the module loader will automatically ' + + 'return a mock for them.', + type: 'array', + }, updateSnapshot: { alias: 'u', - default: false, + default: undefined, description: 'Use this flag to re-record snapshots. ' + 'Can be used together with a test suite pattern or with ' + '`--testNamePattern` to re-record snapshot for test matching ' + @@ -236,33 +417,38 @@ const options = { type: 'boolean', }, useStderr: { + default: undefined, description: 'Divert all output to stderr.', type: 'boolean', }, verbose: { + default: undefined, description: 'Display individual test results with the test suite ' + 'hierarchy.', type: 'boolean', }, version: { alias: 'v', + default: undefined, description: 'Print the version and exit', type: 'boolean', }, watch: { + default: undefined, description: 'Watch files for changes and rerun tests related to ' + 'changed files. If you want to re-run all tests when a file has ' + 'changed, use the `--watchAll` option.', type: 'boolean', }, watchAll: { + default: undefined, description: 'Watch files for changes and rerun all tests. If you want ' + 'to re-run only the tests related to the changed files, use the ' + '`--watch` option.', type: 'boolean', }, watchman: { - default: true, + default: undefined, description: 'Whether to use watchman for file crawling. Disable using ' + '--no-watchman.', type: 'boolean', diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index 1d98674fffcc..5b6dbc2c5108 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -11,6 +11,7 @@ 'use strict'; import type {Path} from 'types/Config'; +import type {Argv} from 'types/Argv'; const args = require('./args'); const getJest = require('./getJest'); @@ -19,7 +20,7 @@ const runCLI = require('./runCLI'); const validateCLIOptions = require('jest-util').validateCLIOptions; const yargs = require('yargs'); -function run(argv?: Object, project?: Path) { +function run(argv?: Argv, project?: Path) { argv = yargs(argv || process.argv.slice(2)) .usage(args.usage) .help() @@ -30,12 +31,6 @@ function run(argv?: Object, project?: Path) { validateCLIOptions(argv, args.options); - if (argv.help) { - yargs.showHelp(); - process.on('exit', () => process.exit(1)); - return; - } - if (!project) { project = pkgDir.sync(); } diff --git a/packages/jest-cli/src/lib/setState.js b/packages/jest-cli/src/lib/updateArgv.js similarity index 83% rename from packages/jest-cli/src/lib/setState.js rename to packages/jest-cli/src/lib/updateArgv.js index b338afe80914..d34904604928 100644 --- a/packages/jest-cli/src/lib/setState.js +++ b/packages/jest-cli/src/lib/updateArgv.js @@ -9,11 +9,17 @@ */ 'use strict'; +import type {Argv} from 'types/Argv'; + const getTestPathPattern = require('./getTestPathPattern'); -module.exports = (argv: Object, mode: 'watch' | 'watchAll', options?: {}) => { - options = options || {}; +type Options = {| + testNamePattern?: string, + testPathPattern?: string, + noSCM?: boolean, +|}; +module.exports = (argv: Argv, mode: 'watch' | 'watchAll', options: Options) => { if (mode === 'watch') { argv.watch = true; argv.watchAll = false; diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index 0d8d1ecf08ea..dc532cced3a8 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -20,7 +20,7 @@ const getMaxWorkers = require('./lib/getMaxWorkers'); const getTestPathPattern = require('./lib/getTestPathPattern'); const path = require('path'); const SearchSource = require('./SearchSource'); -const setState = require('./lib/setState'); +const updateArgv = require('./lib/updateArgv'); const TestRunner = require('./TestRunner'); const TestSequencer = require('./TestSequencer'); @@ -103,9 +103,7 @@ const getTestPaths = async (globalConfig, context, pattern, argv, pipe) => { if (pattern.onlyChanged && data.noSCM) { if (globalConfig.watch) { // Run all the tests - setState(argv, 'watchAll', { - noSCM: true, - }); + updateArgv(argv, 'watchAll', {noSCM: true}); pattern = getTestPathPattern(argv); data = await source.getTestPaths(pattern); } else { diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index 45c85c93409e..e8625aa6bb1c 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -21,7 +21,7 @@ const isInteractive = process.stdout.isTTY && !isCI; const isValidPath = require('./lib/isValidPath'); const preRunMessage = require('./preRunMessage'); const runJest = require('./runJest'); -const setState = require('./lib/setState'); +const updateArgv = require('./lib/updateArgv'); const SearchSource = require('./SearchSource'); const TestWatcher = require('./TestWatcher'); const Prompt = require('./lib/Prompt'); @@ -39,10 +39,9 @@ const watch = ( hasteMapInstances: Array, stdin?: stream$Readable | tty$ReadStream = process.stdin, ) => { - setState(argv, argv.watch ? 'watch' : 'watchAll', { + updateArgv(argv, argv.watch ? 'watch' : 'watchAll', { testNamePattern: argv.testNamePattern, - testPathPattern: argv.testPathPattern || - (Array.isArray(argv._) ? argv._.join('|') : ''), + testPathPattern: argv.testPathPattern || (argv._ || []).join('|'), }); const prompt = new Prompt(); @@ -177,21 +176,21 @@ const watch = ( startRun({updateSnapshot: true}); break; case KEYS.A: - setState(argv, 'watchAll', { + updateArgv(argv, 'watchAll', { testNamePattern: '', testPathPattern: '', }); startRun(); break; case KEYS.C: - setState(argv, 'watch', { + updateArgv(argv, 'watch', { testNamePattern: '', testPathPattern: '', }); startRun(); break; case KEYS.O: - setState(argv, 'watch', { + updateArgv(argv, 'watch', { testNamePattern: '', testPathPattern: '', }); @@ -200,7 +199,7 @@ const watch = ( case KEYS.P: testPathPatternPrompt.run( testPathPattern => { - setState(argv, 'watch', { + updateArgv(argv, 'watch', { testNamePattern: '', testPathPattern, }); @@ -214,7 +213,7 @@ const watch = ( case KEYS.T: testNamePatternPrompt.run( testNamePattern => { - setState(argv, 'watch', { + updateArgv(argv, 'watch', { testNamePattern, testPathPattern: argv.testPathPattern, }); diff --git a/packages/jest-config/src/__tests__/setFromArgv-test.js b/packages/jest-config/src/__tests__/setFromArgv-test.js new file mode 100644 index 000000000000..7f118d5f41e7 --- /dev/null +++ b/packages/jest-config/src/__tests__/setFromArgv-test.js @@ -0,0 +1,68 @@ +/** + * 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. + * + */ +'use strict'; + +const setFromArgv = require('../setFromArgv'); + +test('maps special values to valid options', () => { + const options = {}; + const argv = { + coverage: true, + env: 'node', + json: true, + watchAll: true, + }; + + expect(setFromArgv(options, argv)).toMatchObject({ + collectCoverage: true, + testEnvironment: 'node', + useStderr: true, + watch: true, + }); +}); + +test('maps regular values to themselves', () => { + const options = {}; + const argv = { + collectCoverageOnlyFrom: ['a', 'b'], + coverageDirectory: 'covDir', + watchman: true, + }; + + expect(setFromArgv(options, argv)).toMatchObject({ + collectCoverageOnlyFrom: ['a', 'b'], + coverageDirectory: 'covDir', + watchman: true, + }); +}); + +test('works with string objects', () => { + const options = {}; + const argv = { + moduleNameMapper: '{"types/(.*)": "/src/types/$1"}', + transform: '{"*.js": "/transformer"}', + }; + expect(setFromArgv(options, argv)).toMatchObject({ + moduleNameMapper: { + 'types/(.*)': '/src/types/$1', + }, + transform: { + '*.js': '/transformer', + }, + }); +}); + +test('explicit flags override those from --config', () => { + const options = {}; + const argv = { + config: '{"watch": false}', + watch: true, + }; + expect(setFromArgv(options, argv)).toMatchObject({watch: true}); +}); diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index a6ec0c8b1e8e..38bdb3ad867b 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -34,6 +34,7 @@ module.exports = ({ automock: false, bail: false, browser: false, + cache: true, cacheDirectory, clearMocks: false, coveragePathIgnorePatterns: [NODE_MODULES_REGEXP], @@ -66,4 +67,5 @@ module.exports = ({ useStderr: false, verbose: null, watch: false, + watchman: true, }: DefaultOptions); diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 4cd060d0907a..3a1aca9a1cd3 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -16,8 +16,7 @@ const path = require('path'); const loadFromFile = require('./loadFromFile'); const loadFromPackage = require('./loadFromPackage'); const normalize = require('./normalize'); -const setFromArgv = require('./setFromArgv'); -const {getTestEnvironment} = require('./utils'); +const {getTestEnvironment, isJSONString} = require('./utils'); async function readConfig( argv: Object, @@ -29,7 +28,7 @@ async function readConfig( }> { const rawConfig = await readRawConfig(argv, packageRoot); const {options, hasDeprecationWarnings} = normalize(rawConfig, argv); - const {globalConfig, projectConfig} = getConfigs(setFromArgv(options, argv)); + const {globalConfig, projectConfig} = getConfigs(options); return { config: projectConfig, globalConfig, @@ -37,15 +36,8 @@ async function readConfig( }; } -const parseConfig = argv => { - if (argv.config && typeof argv.config === 'string') { - // If the passed in value looks like JSON, treat it as an object. - if (argv.config[0] === '{' && argv.config[argv.config.length - 1] === '}') { - return JSON.parse(argv.config); - } - } - return argv.config; -}; +const parseConfig = argv => + (isJSONString(argv.config) ? JSON.parse(argv.config) : argv.config); const readRawConfig = (argv, root) => { const rawConfig = parseConfig(argv); @@ -64,7 +56,7 @@ const readRawConfig = (argv, root) => { }; const getConfigs = ( - options, + options: Object, ): {globalConfig: GlobalConfig, projectConfig: ProjectConfig} => { return { globalConfig: Object.freeze({ diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 12b983396f43..7bd88b94aec7 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -15,6 +15,7 @@ import type {InitialOptions, ReporterConfig} from 'types/Config'; const { BULLET, DOCUMENTATION_NOTE, + _replaceRootDirInObject, _replaceRootDirInPath, _replaceRootDirTags, getTestEnvironment, @@ -37,13 +38,14 @@ const JSON_EXTENSION = '.json'; const path = require('path'); const PRESET_NAME = 'jest-preset' + JSON_EXTENSION; const Resolver = require('jest-resolve'); +const setFromArgv = require('./setFromArgv'); const utils = require('jest-regex-util'); const VALID_CONFIG = require('./validConfig'); const createConfigError = message => new ValidationError(ERROR, message, DOCUMENTATION_NOTE); -const setupPreset = (options: InitialOptions, optionsPreset: string) => { +const setupPreset = (options: Object, optionsPreset: string) => { let preset; const presetPath = _replaceRootDirInPath(options.rootDir, optionsPreset); const presetModule = Resolver.findNodeModule( @@ -80,7 +82,7 @@ const setupPreset = (options: InitialOptions, optionsPreset: string) => { return Object.assign({}, preset, options); }; -const setupBabelJest = (options: InitialOptions) => { +const setupBabelJest = (options: Object) => { let babelJest; const basedir = options.rootDir; @@ -92,7 +94,6 @@ const setupBabelJest = (options: InitialOptions) => { if (customJSPattern) { const jsTransformer = Resolver.findNodeModule( - //$FlowFixMe options.transform[customJSPattern], {basedir}, ); @@ -119,7 +120,10 @@ const normalizeCollectCoverageOnlyFrom = ( options: InitialOptions, key: string, ) => { - return Object.keys(options[key]).reduce((map, filePath) => { + 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), @@ -165,7 +169,7 @@ const normalizeUnmockedModulePathPatterns = ( ); }; -const normalizePreprocessor = (options: InitialOptions) => { +const normalizePreprocessor = (options: Object) => { /* eslint-disable max-len */ if (options.scriptPreprocessor && options.transform) { throw createConfigError( @@ -196,7 +200,7 @@ const normalizePreprocessor = (options: InitialOptions) => { delete options.preprocessorIgnorePatterns; }; -const normalizeMissingOptions = (options: InitialOptions) => { +const normalizeMissingOptions = (options: Object) => { if (!options.name) { options.name = crypto .createHash('md5') @@ -221,7 +225,7 @@ const normalizeMissingOptions = (options: InitialOptions) => { return options; }; -const normalizeRootDir = (options: InitialOptions) => { +const normalizeRootDir = (options: Object) => { // Assert that there *is* a rootDir if (!options.hasOwnProperty('rootDir')) { throw createConfigError( @@ -231,28 +235,6 @@ const normalizeRootDir = (options: InitialOptions) => { options.rootDir = path.normalize(options.rootDir); }; -const normalizeArgv = (options: InitialOptions, argv: Object) => { - if (argv.testRunner) { - options.testRunner = argv.testRunner; - } - - if (argv.collectCoverageFrom) { - options.collectCoverageFrom = argv.collectCoverageFrom; - } - - if (argv.collectCoverageOnlyFrom) { - const collectCoverageOnlyFrom = Object.create(null); - argv.collectCoverageOnlyFrom.forEach( - path => (collectCoverageOnlyFrom[path] = true), - ); - options.collectCoverageOnlyFrom = collectCoverageOnlyFrom; - } - - if (argv.env) { - options.testEnvironment = argv.env; - } -}; - const normalizeReporters = (options: InitialOptions, basedir) => { const reporters = options.reporters; if (!reporters || !Array.isArray(reporters)) { @@ -299,11 +281,11 @@ function normalize(options: InitialOptions, argv: Object = {}) { exampleConfig: VALID_CONFIG, }); + options = setFromArgv(options, argv); normalizeReporters(options); normalizePreprocessor(options); normalizeRootDir(options); normalizeMissingOptions(options); - normalizeArgv(options, argv); if (options.preset) { options = setupPreset(options, options.preset); @@ -326,11 +308,9 @@ function normalize(options: InitialOptions, argv: Object = {}) { break; case 'setupFiles': case 'snapshotSerializers': - //$FlowFixMe value = options[key].map(resolve.bind(null, options.rootDir, key)); break; case 'roots': - //$FlowFixMe value = options[key].map(filePath => path.resolve( options.rootDir, @@ -345,29 +325,23 @@ function normalize(options: InitialOptions, argv: Object = {}) { case 'coverageDirectory': value = path.resolve( options.rootDir, - //$FlowFixMe _replaceRootDirInPath(options.rootDir, options[key]), ); break; case 'setupTestFrameworkScriptFile': case 'testResultsProcessor': case 'resolver': - //$FlowFixMe value = resolve(options.rootDir, key, options[key]); break; case 'moduleNameMapper': - //$FlowFixMe value = Object.keys(options[key]).map(regex => [ regex, - //$FlowFixMe _replaceRootDirTags(options.rootDir, options[key][regex]), ]); break; case 'transform': - //$FlowFixMe value = Object.keys(options[key]).map(regex => [ regex, - //$FlowFixMe resolve(options.rootDir, key, options[key][regex]), ]); break; @@ -390,7 +364,6 @@ function normalize(options: InitialOptions, argv: Object = {}) { break; case 'projects': let list = []; - //$FlowFixMe options[key].forEach( filePath => (list = list.concat( @@ -407,6 +380,7 @@ function normalize(options: InitialOptions, argv: Object = {}) { case 'collectCoverage': case 'coverageReporters': case 'coverageThreshold': + case 'expand': case 'globals': case 'logHeapUsage': case 'mapCoverage': @@ -423,13 +397,16 @@ function normalize(options: InitialOptions, argv: Object = {}) { case 'resetMocks': case 'resetModules': case 'rootDir': + case 'silent': case 'testMatch': case 'testEnvironment': + case 'testNamePattern': case 'testRegex': case 'testRunner': case 'testURL': case 'timers': case 'updateSnapshot': + case 'useStderr': case 'verbose': case 'watchman': value = options[key]; @@ -471,7 +448,7 @@ function normalize(options: InitialOptions, argv: Object = {}) { return { hasDeprecationWarnings, - options: _replaceRootDirTags(newOptions.rootDir, newOptions), + options: _replaceRootDirInObject(newOptions.rootDir, newOptions), }; } diff --git a/packages/jest-config/src/setFromArgv.js b/packages/jest-config/src/setFromArgv.js index 27399f036a70..74ba5dd58126 100644 --- a/packages/jest-config/src/setFromArgv.js +++ b/packages/jest-config/src/setFromArgv.js @@ -4,90 +4,58 @@ * 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. + * + * @flow */ 'use strict'; -function setFromArgv(config, argv) { - if (argv.coverage) { - config.collectCoverage = true; - } - - if (argv.coverageDirectory) { - config.coverageDirectory = argv.coverageDirectory; - } - - if (argv.mapCoverage) { - config.mapCoverage = true; - } - - if (argv.verbose) { - config.verbose = argv.verbose; - } - - if (argv.notify !== null) { - config.notify = argv.notify; - } - - if (argv.bail) { - config.bail = argv.bail; - } - - if (argv.cache !== null) { - config.cache = argv.cache; - } - - if (config.watchman === undefined && argv.watchman !== null) { - config.watchman = argv.watchman; - } - - if (argv.useStderr) { - config.useStderr = argv.useStderr; - } - - if (argv.json) { - config.useStderr = true; - } - - if (argv.logHeapUsage) { - config.logHeapUsage = argv.logHeapUsage; - } - - if (argv.replname) { - config.replname = argv.replname; - } - - if (argv.silent) { - config.silent = true; - } - - if (argv.setupTestFrameworkScriptFile) { - config.setupTestFrameworkScriptFile = argv.setupTestFrameworkScriptFile; - } - - if (argv.testNamePattern) { - config.testNamePattern = argv.testNamePattern; - } - - if (argv.updateSnapshot) { - config.updateSnapshot = argv.updateSnapshot; - } - - if (argv.watch || argv.watchAll) { - config.watch = true; - } - - if (argv.expand) { - config.expand = argv.expand; - } - - if (argv.testResultsProcessor) { - config.testResultsProcessor = argv.testResultsProcessor; - } - - config.noStackTrace = argv.noStackTrace; - - return config; +import type {InitialOptions} from 'types/Config'; +import type {Argv} from 'types/Argv'; + +const specialArgs = ['_', '$0', 'h', 'help', 'config']; +const {isJSONString} = require('./utils'); + +function setFromArgv(options: InitialOptions, argv: Argv) { + let configFromArgv; + const argvToOptions = Object.keys(argv) + .filter(key => argv[key] !== undefined && specialArgs.indexOf(key) === -1) + .reduce((options: Object, key) => { + switch (key) { + case 'coverage': + options.collectCoverage = argv[key]; + break; + case 'json': + options.useStderr = argv[key]; + break; + case 'watchAll': + options.watch = argv[key]; + break; + case 'env': + options.testEnvironment = argv[key]; + break; + case 'config': + break; + case 'coverageThreshold': + case 'globals': + case 'moduleNameMapper': + case 'transform': + case 'haste': + if (isJSONString(argv[key])) { + options[key] = JSON.parse(argv[key]); + } + break; + default: + options[key] = argv[key]; + } + return options; + }, {}); + + if (isJSONString(argv.config)) { + configFromArgv = JSON.parse(argv.config); + } + + return Object.assign({}, options, configFromArgv, argvToOptions); } module.exports = setFromArgv; diff --git a/packages/jest-config/src/utils.js b/packages/jest-config/src/utils.js index e2f53ccdcca3..cd90883017f6 100644 --- a/packages/jest-config/src/utils.js +++ b/packages/jest-config/src/utils.js @@ -59,27 +59,29 @@ const _replaceRootDirInPath = (rootDir: string, filePath: Path): string => { ); }; +const _replaceRootDirInObject = (rootDir: string, config: any): Object => { + if (config !== null) { + const newConfig = {}; + for (const configKey in config) { + newConfig[configKey] = configKey === 'rootDir' + ? config[configKey] + : _replaceRootDirTags(rootDir, config[configKey]); + } + return newConfig; + } + return config; +}; + const _replaceRootDirTags = (rootDir: string, config: any) => { switch (typeof config) { case 'object': - if (config instanceof RegExp) { - return config; - } - if (Array.isArray(config)) { return config.map(item => _replaceRootDirTags(rootDir, item)); } - - if (config !== null) { - const newConfig = {}; - for (const configKey in config) { - newConfig[configKey] = configKey === 'rootDir' - ? config[configKey] - : _replaceRootDirTags(rootDir, config[configKey]); - } - return newConfig; + if (config instanceof RegExp) { + return config; } - break; + return _replaceRootDirInObject(rootDir, config); case 'string': return _replaceRootDirInPath(rootDir, config); } @@ -123,11 +125,19 @@ const getTestEnvironment = (config: Object) => { /* eslint-disable max-len */ }; +const isJSONString = (text: ?string) => + text && + typeof text === 'string' && + text.startsWith('{') && + text.endsWith('}'); + module.exports = { BULLET, DOCUMENTATION_NOTE, + _replaceRootDirInObject, _replaceRootDirInPath, _replaceRootDirTags, getTestEnvironment, + isJSONString, resolve, }; diff --git a/packages/jest-util/package.json b/packages/jest-util/package.json index 1a27a0162f7a..c5824e59958d 100644 --- a/packages/jest-util/package.json +++ b/packages/jest-util/package.json @@ -10,9 +10,9 @@ "dependencies": { "chalk": "^1.1.3", "graceful-fs": "^4.1.11", + "jest-message-util": "^19.0.0", "jest-mock": "^19.0.0", "jest-validate": "^19.0.2", - "jest-message-util": "^19.0.0", "leven": "^2.1.0", "mkdirp": "^0.5.1" } diff --git a/packages/jest-util/src/validateCLIOptions.js b/packages/jest-util/src/validateCLIOptions.js index 17c059de9ce2..8bef446690d7 100644 --- a/packages/jest-util/src/validateCLIOptions.js +++ b/packages/jest-util/src/validateCLIOptions.js @@ -10,6 +10,8 @@ 'use strict'; +import type {Argv} from 'types/Argv'; + const chalk = require('chalk'); const { ValidationError, @@ -48,7 +50,7 @@ const createCLIValidationError = ( return new ValidationError(title, message, comment); }; -const validateCLIOptions = (argv: Object, options: Object) => { +const validateCLIOptions = (argv: Argv, options: Object) => { const yargsSpecialOptions = ['$0', '_', 'help', 'h']; const allowedOptions = Object.keys(options).reduce( (acc, option) => acc.add(option).add(options[option].alias || option), diff --git a/types/Argv.js b/types/Argv.js new file mode 100644 index 000000000000..d7963a582ed0 --- /dev/null +++ b/types/Argv.js @@ -0,0 +1,81 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +export type Argv = { + _: Array, + $0: string, + automock: boolean, + bail: boolean, + browser: boolean, + cache: boolean, + cacheDirectory: string, + clearMocks: boolean, + collectCoverage: boolean, + collectCoverageFrom: Array, + collectCoverageOnlyFrom: Array, + config: string, + coverage: boolean, + coverageDirectory: string, + coveragePathIgnorePatterns: Array, + coverageReporters: Array, + coverageThreshold: string, + env: string, + expand: boolean, + forceExit: boolean, + globals: string, + h: boolean, + haste: string, + help: boolean, + json: boolean, + logHeapUsage: boolean, + mapCoverage: boolean, + moduleDirectories: Array, + moduleFileExtensions: Array, + moduleLoader: string, + moduleNameMapper: string, + modulePathIgnorePatterns: Array, + modulePaths: Array, + name: string, + noSCM: boolean, + noStackTrace: boolean, + notify: boolean, + onlyChanged: boolean, + preset: ?string, + replname: ?string, + resetMocks: boolean, + resetModules: boolean, + resolver: ?string, + rootDir: string, + roots: Array, + setupFiles: Array, + setupTestFrameworkScriptFile: string, + silent: boolean, + snapshotSerializers: Array, + testEnvironment: string, + testMatch: Array, + testNamePattern: string, + testPathIgnorePatterns: Array, + testPathPattern: string, + testRegex: string, + testResultsProcessor: ?string, + testRunner: string, + testURL: string, + timers: 'real' | 'fake', + transform: string, + transformIgnorePatterns: Array, + unmockedModulePathPatterns: ?Array, + updateSnapshot: boolean, + useStderr: boolean, + verbose: ?boolean, + watch: boolean, + watchAll: boolean, + watchman: boolean, +}; diff --git a/types/Config.js b/types/Config.js index 5d75502e73dc..24df602ee164 100644 --- a/types/Config.js +++ b/types/Config.js @@ -27,6 +27,7 @@ export type DefaultOptions = {| automock: boolean, bail: boolean, browser: boolean, + cache: boolean, cacheDirectory: Path, clearMocks: boolean, coveragePathIgnorePatterns: Array, @@ -57,6 +58,7 @@ export type DefaultOptions = {| useStderr: boolean, verbose: ?boolean, watch: boolean, + watchman: boolean, |}; export type InitialOptions = {|