diff --git a/CHANGELOG.md b/CHANGELOG.md index 6572c16d1d59..94e9f2c1fc60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master -None +* `[jest-config]` Add `forceCoverageMatch` to allow collecting coverage from + ignored files. ([#5081](https://github.com/facebook/jest/pull/5081)) ## jest 22.0.0 diff --git a/docs/Configuration.md b/docs/Configuration.md index e3e8037681fc..42eb4e0195df 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -237,6 +237,41 @@ Jest will fail if: * The `./src/api/very-important-module.js` file has less than 100% coverage. * Every remaining file combined has less than 50% coverage (`global`). +### `forceCoverageMatch` [array] + +Default: `['']` + +Test files are normally ignored from collecting code coverage. With this option, +you can overwrite this behavior and include otherwise ignored files in code coverage. + +For example, if you have tests in source files named with `.t.js` extension as +following: + +```javascript +// sum.t.js + +export function sum(a,b) { + return a + b; +} + +if (process.env.NODE_ENV === 'test') { + test('sum', () => { + expect(sum(1, 2)).toBe(3); + }); +} +``` + +You can collect coverage from those files with setting `forceCoverageMatch`. +```json +{ + ... + "jest": { + "forceCoverageMatch": ["**/*.t.js"] + } +} +``` + + ### `globals` [object] Default: `{}` diff --git a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap index ad5f154fb672..f1eba0383106 100644 --- a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap +++ b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap @@ -13,6 +13,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"/node_modules/\\" ], \\"detectLeaks\\": false, + \\"forceCoverageMatch\\": [], \\"globals\\": {}, \\"haste\\": { \\"providesModuleNodeModules\\": [] diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index ce68eab6a649..3f35af5fd105 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -38,6 +38,7 @@ export default ({ coverageReporters: ['json', 'text', 'lcov', 'clover'], detectLeaks: false, expand: false, + forceCoverageMatch: [], globalSetup: null, globalTeardown: null, globals: {}, diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 9f50d4a068e0..7cd8eccbfce5 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -130,6 +130,7 @@ const getConfigs = ( cwd: options.cwd, detectLeaks: options.detectLeaks, displayName: options.displayName, + forceCoverageMatch: options.forceCoverageMatch, globals: options.globals, haste: options.haste, moduleDirectories: options.moduleDirectories, diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 749fb078bf02..e7fba4434f83 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -459,6 +459,7 @@ export default function normalize(options: InitialOptions, argv: Argv) { case 'expand': case 'globals': case 'findRelatedTests': + case 'forceCoverageMatch': case 'forceExit': case 'listTests': case 'logHeapUsage': diff --git a/packages/jest-config/src/valid_config.js b/packages/jest-config/src/valid_config.js index b2ae2930104d..c1559a7b5419 100644 --- a/packages/jest-config/src/valid_config.js +++ b/packages/jest-config/src/valid_config.js @@ -37,6 +37,7 @@ export default ({ }, displayName: 'project-name', expand: false, + forceCoverageMatch: ['**/*.t.js'], forceExit: false, globalSetup: 'setup.js', globalTeardown: 'teardown.js', diff --git a/packages/jest-runtime/src/__tests__/should_instrument.test.js b/packages/jest-runtime/src/__tests__/should_instrument.test.js new file mode 100644 index 000000000000..48e79734227c --- /dev/null +++ b/packages/jest-runtime/src/__tests__/should_instrument.test.js @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import shouldInstrument from '../should_instrument'; + +describe('should_instrument', () => { + const defaultFilename = 'source_file.test.js'; + const defaultOptions = { + collectCoverage: true, + }; + const defaultConfig = { + rootDir: '/', + }; + + describe('should return true', () => { + const testShouldInstrument = ( + filename = defaultFilename, + options = defaultOptions, + config = defaultConfig, + ) => { + const result = shouldInstrument(filename, options, config); + expect(result).toBe(true); + }; + + it('when testRegex provided and file is not a test file', () => { + testShouldInstrument('source_file.js', defaultOptions, { + testRegex: '.*\\.(test)\\.(js)$', + }); + }); + + it('when testMatch is provided and file is not a test file', () => { + testShouldInstrument('source_file.js', defaultOptions, { + testMatch: ['**/?(*.)(test).js'], + }); + }); + + it('should return true when file is in collectCoverageOnlyFrom when provided', () => { + testShouldInstrument( + 'collect/only/from/here.js', + + { + collectCoverage: true, + collectCoverageOnlyFrom: {'collect/only/from/here.js': true}, + }, + defaultConfig, + ); + }); + + it('should return true when filename matches collectCoverageFrom', () => { + testShouldInstrument( + 'do/collect/coverage.js', + { + collectCoverage: true, + collectCoverageFrom: ['!**/dont/**/*.js', '**/do/**/*.js'], + }, + defaultConfig, + ); + }); + + it('should return true if the file is not in coveragePathIgnorePatterns', () => { + testShouldInstrument('do/collect/coverage.js', defaultOptions, { + coveragePathIgnorePatterns: ['dont'], + rootDir: '/', + }); + }); + + it('should return true if file is a testfile but forceCoverageMatch is set', () => { + testShouldInstrument('do/collect/sum.coverage.test.js', defaultOptions, { + forceCoverageMatch: ['**/*.(coverage).(test).js'], + rootDir: '/', + testRegex: '.*\\.(test)\\.(js)$', + }); + }); + }); + + describe('should return false', () => { + const testShouldInstrument = ( + filename = defaultFilename, + options = defaultOptions, + config = defaultConfig, + ) => { + const result = shouldInstrument(filename, options, config); + expect(result).toBe(false); + }; + + it('if collectCoverage is falsy', () => { + testShouldInstrument( + 'source_file.js', + { + collectCoverage: false, + }, + defaultConfig, + ); + }); + + it('when testRegex provided and filename is a test file', () => { + testShouldInstrument(defaultFilename, defaultOptions, { + testRegex: '.*\\.(test)\\.(js)$', + }); + }); + + it('when testMatch is provided and file is a test file', () => { + testShouldInstrument(defaultFilename, defaultOptions, { + testMatch: ['**/?(*.)(test).js'], + }); + }); + + it('when file is not in collectCoverageOnlyFrom when provided', () => { + testShouldInstrument( + 'source_file.js', + { + collectCoverage: true, + collectCoverageOnlyFrom: {'collect/only/from/here.js': true}, + }, + defaultConfig, + ); + }); + + it('when filename does not match collectCoverageFrom', () => { + testShouldInstrument( + 'dont/collect/coverage.js', + { + collectCoverage: true, + collectCoverageFrom: ['!**/dont/**/*.js', '**/do/**/*.js'], + }, + defaultConfig, + ); + }); + + it('if the file is in coveragePathIgnorePatterns', () => { + testShouldInstrument('dont/collect/coverage.js', defaultOptions, { + coveragePathIgnorePatterns: ['dont'], + rootDir: '/', + }); + }); + + it('if file is in mock patterns', () => { + const filename = + process.platform === 'win32' + ? 'dont\\__mocks__\\collect\\coverage.js' + : 'dont/__mocks__/collect/coverage.js'; + + testShouldInstrument(filename, defaultOptions, defaultConfig); + }); + }); +}); diff --git a/packages/jest-runtime/src/should_instrument.js b/packages/jest-runtime/src/should_instrument.js index 48745ddbbea0..0a663ee56378 100644 --- a/packages/jest-runtime/src/should_instrument.js +++ b/packages/jest-runtime/src/should_instrument.js @@ -27,6 +27,14 @@ export default function shouldInstrument( return false; } + if ( + config.forceCoverageMatch && + config.forceCoverageMatch.length && + micromatch.any(filename, config.forceCoverageMatch) + ) { + return true; + } + if (config.testRegex && filename.match(config.testRegex)) { return false; } diff --git a/test_utils.js b/test_utils.js index dbed2652d5b4..9ae8c272dd59 100644 --- a/test_utils.js +++ b/test_utils.js @@ -70,6 +70,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { cwd: '/test_root_dir/', detectLeaks: false, displayName: undefined, + forceCoverageMatch: [], globals: {}, haste: { providesModuleNodeModules: [], diff --git a/types/Config.js b/types/Config.js index e02b2f5fdc80..b9ee6cd422fa 100644 --- a/types/Config.js +++ b/types/Config.js @@ -31,6 +31,7 @@ export type DefaultOptions = {| coveragePathIgnorePatterns: Array, coverageReporters: Array, expand: boolean, + forceCoverageMatch: Array, globals: ConfigGlobals, globalSetup: ?string, globalTeardown: ?string, @@ -86,6 +87,7 @@ export type InitialOptions = { displayName?: string, expand?: boolean, findRelatedTests?: boolean, + forceCoverageMatch?: Array, forceExit?: boolean, json?: boolean, globals?: ConfigGlobals, @@ -212,6 +214,7 @@ export type ProjectConfig = {| cwd: Path, detectLeaks: boolean, displayName: ?string, + forceCoverageMatch: Array, globals: ConfigGlobals, haste: HasteConfig, moduleDirectories: Array,