diff --git a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap index c4f023851bac..f312b5fdcd49 100644 --- a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap +++ b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap @@ -73,6 +73,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"nonFlagArgs\\": [], \\"notify\\": false, \\"rootDir\\": \\"<>\\", + \\"runTestsByPath\\": false, \\"testFailureExitCode\\": 1, \\"testPathPattern\\": \\"\\", \\"testResultsProcessor\\": null, diff --git a/integration_tests/__tests__/run_tests_by_path.test.js b/integration_tests/__tests__/run_tests_by_path.test.js new file mode 100644 index 000000000000..c54e21e1183a --- /dev/null +++ b/integration_tests/__tests__/run_tests_by_path.test.js @@ -0,0 +1,47 @@ +/** + * 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'; + +import runJest from '../runJest'; +import {cleanup, writeFiles} from '../utils'; +import os from 'os'; +import path from 'path'; + +const skipOnWindows = require('../../scripts/skip_on_windows'); +const DIR = path.resolve(os.tmpdir(), 'run_tests_by_path_test'); + +skipOnWindows.suite(); + +beforeEach(() => cleanup(DIR)); +afterEach(() => cleanup(DIR)); + +test('runs tests by exact path', () => { + writeFiles(DIR, { + '.watchmanconfig': '', + '__tests__/t1.test.js': 'it("foo", () => {})', + '__tests__/t2.test.js': 'it("bar", () => {})', + 'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}), + }); + + // Passing an exact path executes only the given test. + const run1 = runJest(DIR, ['--runTestsByPath', '__tests__/t1.test.js']); + expect(run1.stderr).toMatch('PASS __tests__/t1.test.js'); + expect(run1.stderr).not.toMatch('PASS __tests__/t2.test.js'); + + // When running with thte flag and a pattern, no test is found. + const run2 = runJest(DIR, ['--runTestsByPath', '__tests__/t']); + expect(run2.stdout).toMatch(/no tests found/i); + + // When ran without the flag and a pattern, both tests are found. + const run3 = runJest(DIR, ['__tests__/t']); + expect(run3.stderr).toMatch('PASS __tests__/t1.test.js'); + expect(run3.stderr).toMatch('PASS __tests__/t2.test.js'); +}); diff --git a/packages/jest-cli/src/cli/args.js b/packages/jest-cli/src/cli/args.js index 2bf27d2a39a9..b3c629bc188e 100644 --- a/packages/jest-cli/src/cli/args.js +++ b/packages/jest-cli/src/cli/args.js @@ -394,6 +394,14 @@ const options = { 'rare.', type: 'boolean', }, + runTestsByPath: { + default: false, + description: + 'Used when provided patterns are exact file paths. This avoids ' + + 'converting them into a regular expression and matching it against ' + + 'every single file.', + type: 'boolean', + }, setupFiles: { description: 'The paths to modules that run some code to configure or ' + diff --git a/packages/jest-cli/src/get_no_test_found.js b/packages/jest-cli/src/get_no_test_found.js index 16d051a2d28f..5447068bbc03 100644 --- a/packages/jest-cli/src/get_no_test_found.js +++ b/packages/jest-cli/src/get_no_test_found.js @@ -6,6 +6,18 @@ const getNoTestFound = (testRunData, globalConfig): string => { (current, testRun) => (current += testRun.matches.total), 0, ); + let dataMessage; + + if (globalConfig.runTestsByPath) { + dataMessage = `Files: ${globalConfig.nonFlagArgs + .map(p => `"${p}"`) + .join(', ')}`; + } else { + dataMessage = `Pattern: ${chalk.yellow( + globalConfig.testPathPattern, + )} - 0 matches`; + } + return ( chalk.bold('No tests found') + '\n' + @@ -17,7 +29,7 @@ const getNoTestFound = (testRunData, globalConfig): string => { 's', )}. for more details run with \`--verbose\`` + '\n' + - `Pattern: ${chalk.yellow(globalConfig.testPathPattern)} - 0 matches` + dataMessage ); }; diff --git a/packages/jest-cli/src/get_no_test_found_verbose.js b/packages/jest-cli/src/get_no_test_found_verbose.js index 095cccac6956..49d7ed058437 100644 --- a/packages/jest-cli/src/get_no_test_found_verbose.js +++ b/packages/jest-cli/src/get_no_test_found_verbose.js @@ -30,12 +30,24 @@ const getNoTestFoundVerbose = (testRunData, globalConfig): string => { `Jest Documentation: ` + `facebook.github.io/jest/docs/configuration.html`; }); + let dataMessage; + + if (globalConfig.runTestsByPath) { + dataMessage = `Files: ${globalConfig.nonFlagArgs + .map(p => `"${p}"`) + .join(', ')}`; + } else { + dataMessage = `Pattern: ${chalk.yellow( + globalConfig.testPathPattern, + )} - 0 matches`; + } + return ( chalk.bold('No tests found') + '\n' + individualResults.join('\n') + '\n' + - `Pattern: ${chalk.yellow(globalConfig.testPathPattern)} - 0 matches` + dataMessage ); }; diff --git a/packages/jest-cli/src/reporters/summary_reporter.js b/packages/jest-cli/src/reporters/summary_reporter.js index 6d8e54e9c74d..9d9b02ce0a51 100644 --- a/packages/jest-cli/src/reporters/summary_reporter.js +++ b/packages/jest-cli/src/reporters/summary_reporter.js @@ -190,13 +190,25 @@ class SummaryReporter extends BaseReporter { ); }; - const testInfo = globalConfig.onlyChanged - ? chalk.dim(' related to changed files') - : globalConfig.testPathPattern ? getMatchingTestsInfo() : ''; + let testInfo = ''; + + if (globalConfig.runTestsByPath) { + testInfo = chalk.dim(' within paths'); + } else if (globalConfig.onlyChanged) { + testInfo = chalk.dim(' related to changed files'); + } else if (globalConfig.testPathPattern) { + testInfo = getMatchingTestsInfo(); + } + + let nameInfo = ''; - const nameInfo = globalConfig.testNamePattern - ? chalk.dim(' with tests matching ') + `"${globalConfig.testNamePattern}"` - : ''; + if (globalConfig.runTestsByPath) { + nameInfo = ' ' + globalConfig.nonFlagArgs.map(p => `"${p}"`).join(', '); + } else if (globalConfig.testNamePattern) { + nameInfo = + chalk.dim(' with tests matching ') + + `"${globalConfig.testNamePattern}"`; + } const contextInfo = contexts.size > 1 diff --git a/packages/jest-cli/src/run_jest.js b/packages/jest-cli/src/run_jest.js index f3160a6ca6df..3afdf315b89f 100644 --- a/packages/jest-cli/src/run_jest.js +++ b/packages/jest-cli/src/run_jest.js @@ -39,19 +39,18 @@ const getTestPaths = async ( ) => { const source = new SearchSource(context); let data = await source.getTestPaths(globalConfig, changedFilesPromise); - if (!data.tests.length) { - if (globalConfig.onlyChanged && data.noSCM) { - if (globalConfig.watch) { - data = await source.getTestPaths(globalConfig); - } else { - new Console(outputStream, outputStream).log( - 'Jest can only find uncommitted changed files in a git or hg ' + - 'repository. If you make your project a git or hg ' + - 'repository (`git init` or `hg init`), Jest will be able ' + - 'to only run tests related to files changed since the last ' + - 'commit.', - ); - } + + if (!data.tests.length && globalConfig.onlyChanged && data.noSCM) { + if (globalConfig.watch) { + data = await source.getTestPaths(globalConfig); + } else { + new Console(outputStream, outputStream).log( + 'Jest can only find uncommitted changed files in a git or hg ' + + 'repository. If you make your project a git or hg ' + + 'repository (`git init` or `hg init`), Jest will be able ' + + 'to only run tests related to files changed since the last ' + + 'commit.', + ); } } return data; diff --git a/packages/jest-cli/src/search_source.js b/packages/jest-cli/src/search_source.js index 8c81c6dc50e2..47d8a29c324f 100644 --- a/packages/jest-cli/src/search_source.js +++ b/packages/jest-cli/src/search_source.js @@ -162,6 +162,17 @@ class SearchSource { }; } + findTestsByPaths(paths: Array): SearchResult { + return { + tests: toTests( + this._context, + paths + .map(p => path.resolve(process.cwd(), p)) + .filter(this.isTestFilePath.bind(this)), + ), + }; + } + findRelatedTestsFromPattern(paths: Array): SearchResult { if (Array.isArray(paths) && paths.length) { const resolvedPaths = paths.map(p => path.resolve(process.cwd(), p)); @@ -193,6 +204,8 @@ class SearchSource { } return this.findTestRelatedToChangedFiles(changedFilesPromise); + } else if (globalConfig.runTestsByPath && paths && paths.length) { + return Promise.resolve(this.findTestsByPaths(paths)); } else if (globalConfig.findRelatedTests && paths && paths.length) { return Promise.resolve(this.findRelatedTestsFromPattern(paths)); } else if (globalConfig.testPathPattern != null) { diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index 5053a35f4bb2..df42c47356d4 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -52,6 +52,7 @@ module.exports = ({ preset: null, resetMocks: false, resetModules: false, + runTestsByPath: false, runner: 'jest-runner', snapshotSerializers: [], testEnvironment: 'jest-environment-jsdom', diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index e5a5b698c113..a6ec27e77f56 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -101,6 +101,7 @@ const getConfigs = ( replname: options.replname, reporters: options.reporters, rootDir: options.rootDir, + runTestsByPath: options.runTestsByPath, silent: options.silent, testFailureExitCode: options.testFailureExitCode, testNamePattern: options.testNamePattern, diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 1f6e24290a7e..fa359ac8fb93 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -472,6 +472,7 @@ function normalize(options: InitialOptions, argv: Argv) { case 'resetMocks': case 'resetModules': case 'rootDir': + case 'runTestsByPath': case 'silent': case 'skipNodeResolution': case 'testEnvironment': diff --git a/packages/jest-config/src/valid_config.js b/packages/jest-config/src/valid_config.js index 66139c1ad5ea..8a902261d950 100644 --- a/packages/jest-config/src/valid_config.js +++ b/packages/jest-config/src/valid_config.js @@ -70,6 +70,7 @@ module.exports = ({ resolver: '/resolver.js', rootDir: '/', roots: [''], + runTestsByPath: false, runner: 'jest-runner', setupFiles: ['/setup.js'], setupTestFrameworkScriptFile: '/test_setup_file.js', diff --git a/test_utils.js b/test_utils.js index 280b758c9db0..0c47e53693ad 100644 --- a/test_utils.js +++ b/test_utils.js @@ -40,6 +40,7 @@ const DEFAULT_GLOBAL_CONFIG: GlobalConfig = { replname: null, reporters: [], rootDir: '/test_root_dir/', + runTestsByPath: false, silent: false, testFailureExitCode: 1, testNamePattern: '', diff --git a/types/Config.js b/types/Config.js index 4c5e831f3d20..ce81bc25cf7b 100644 --- a/types/Config.js +++ b/types/Config.js @@ -45,6 +45,7 @@ export type DefaultOptions = {| resetMocks: boolean, resetModules: boolean, runner: string, + runTestsByPath: boolean, snapshotSerializers: Array, testEnvironment: string, testFailureExitCode: string | number, @@ -109,6 +110,7 @@ export type InitialOptions = { rootDir: Path, roots?: Array, runner?: string, + runTestsByPath?: boolean, scriptPreprocessor?: string, setupFiles?: Array, setupTestFrameworkScriptFile?: Path, @@ -167,6 +169,7 @@ export type GlobalConfig = {| projects: Array, replname: ?string, reporters: Array, + runTestsByPath: boolean, rootDir: Path, silent: boolean, testFailureExitCode: number,