UNPKG

only-changed-jest-watch-plugin

Version:

Jest watch plugin for running either only the modified test (for TDD), or tests of dependant modules

500 lines (429 loc) 14.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _jestUtil; function _load_jestUtil() { return (_jestUtil = require('jest-util')); } var _istanbulApi; function _load_istanbulApi() { return (_istanbulApi = require('istanbul-api')); } var _chalk; function _load_chalk() { return (_chalk = _interopRequireDefault(require('chalk'))); } var _istanbulLibCoverage; function _load_istanbulLibCoverage() { return (_istanbulLibCoverage = _interopRequireDefault( require('istanbul-lib-coverage') )); } var _istanbulLibSourceMaps; function _load_istanbulLibSourceMaps() { return (_istanbulLibSourceMaps = _interopRequireDefault( require('istanbul-lib-source-maps') )); } var _jestWorker; function _load_jestWorker() { return (_jestWorker = _interopRequireDefault(require('jest-worker'))); } var _base_reporter; function _load_base_reporter() { return (_base_reporter = _interopRequireDefault(require('./base_reporter'))); } var _path; function _load_path() { return (_path = _interopRequireDefault(require('path'))); } var _glob; function _load_glob() { return (_glob = _interopRequireDefault(require('glob'))); } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step('next', value); }, function(err) { step('throw', err); } ); } } return step('next'); }); }; } /** * 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. * * */ const FAIL_COLOR = (_chalk || _load_chalk()).default.bold.red; const RUNNING_TEST_COLOR = (_chalk || _load_chalk()).default.bold.dim; class CoverageReporter extends (_base_reporter || _load_base_reporter()) .default { constructor(globalConfig) { super(); this._coverageMap = ( _istanbulLibCoverage || _load_istanbulLibCoverage() ).default.createCoverageMap({}); this._globalConfig = globalConfig; this._sourceMapStore = ( _istanbulLibSourceMaps || _load_istanbulLibSourceMaps() ).default.createSourceMapStore(); } onTestResult(test, testResult, aggregatedResults) { if (testResult.coverage) { this._coverageMap.merge(testResult.coverage); // Remove coverage data to free up some memory. delete testResult.coverage; Object.keys(testResult.sourceMaps).forEach(sourcePath => { let inputSourceMap; try { const coverage = this._coverageMap.fileCoverageFor(sourcePath); var _coverage$toJSON = coverage.toJSON(); inputSourceMap = _coverage$toJSON.inputSourceMap; } finally { if (inputSourceMap) { this._sourceMapStore.registerMap(sourcePath, inputSourceMap); } else { this._sourceMapStore.registerURL( sourcePath, testResult.sourceMaps[sourcePath] ); } } }); } } onRunComplete(contexts, aggregatedResults) { var _this = this; return _asyncToGenerator(function*() { yield _this._addUntestedFiles(_this._globalConfig, contexts); var _sourceMapStore$trans = _this._sourceMapStore.transformCoverage( _this._coverageMap ); const map = _sourceMapStore$trans.map, sourceFinder = _sourceMapStore$trans.sourceFinder; const reporter = (0, (_istanbulApi || _load_istanbulApi()).createReporter)(); try { if (_this._globalConfig.coverageDirectory) { reporter.dir = _this._globalConfig.coverageDirectory; } let coverageReporters = _this._globalConfig.coverageReporters || []; if ( !_this._globalConfig.useStderr && coverageReporters.length && coverageReporters.indexOf('text') === -1 ) { coverageReporters = coverageReporters.concat(['text-summary']); } reporter.addAll(coverageReporters); reporter.write(map, sourceFinder && {sourceFinder: sourceFinder}); aggregatedResults.coverageMap = map; } catch (e) { console.error( (_chalk || _load_chalk()).default.red(` Failed to write coverage reports: ERROR: ${e.toString()} STACK: ${e.stack} `) ); } _this._checkThreshold(_this._globalConfig, map); })(); } _addUntestedFiles(globalConfig, contexts) { var _this2 = this; return _asyncToGenerator(function*() { const files = []; contexts.forEach(function(context) { const config = context.config; if ( globalConfig.collectCoverageFrom && globalConfig.collectCoverageFrom.length ) { context.hasteFS .matchFilesWithGlob( globalConfig.collectCoverageFrom, config.rootDir ) .forEach(function(filePath) { return files.push({ config: config, path: filePath }); }); } }); if (!files.length) { return; } if ((_jestUtil || _load_jestUtil()).isInteractive) { process.stderr.write( RUNNING_TEST_COLOR('Running coverage on untested files...') ); } let worker; if (_this2._globalConfig.maxWorkers <= 1) { worker = require('./coverage_worker'); } else { // $FlowFixMe: assignment of a worker with custom properties. worker = new (_jestWorker || _load_jestWorker()).default( require.resolve('./coverage_worker'), { exposedMethods: ['worker'], maxRetries: 2, numWorkers: _this2._globalConfig.maxWorkers } ); } const instrumentation = files.map( (() => { var _ref = _asyncToGenerator(function*(fileObj) { const filename = fileObj.path; const config = fileObj.config; if (!_this2._coverageMap.data[filename]) { try { const result = yield worker.worker({ config: config, globalConfig: globalConfig, path: filename }); if (result) { _this2._coverageMap.addFileCoverage(result.coverage); if (result.sourceMapPath) { _this2._sourceMapStore.registerURL( filename, result.sourceMapPath ); } } } catch (error) { console.error( (_chalk || _load_chalk()).default.red( [ `Failed to collect coverage from ${filename}`, `ERROR: ${error.message}`, `STACK: ${error.stack}` ].join('\n') ) ); } } }); return function(_x) { return _ref.apply(this, arguments); }; })() ); try { yield Promise.all(instrumentation); } catch (err) { // Do nothing; errors were reported earlier to the console. } if ((_jestUtil || _load_jestUtil()).isInteractive) { (0, (_jestUtil || _load_jestUtil()).clearLine)(process.stderr); } if (worker && typeof worker.end === 'function') { worker.end(); } })(); } _checkThreshold(globalConfig, map) { if (globalConfig.coverageThreshold) { function check(name, thresholds, actuals) { return ['statements', 'branches', 'lines', 'functions'].reduce( (errors, key) => { const actual = actuals[key].pct; const actualUncovered = actuals[key].total - actuals[key].covered; const threshold = thresholds[key]; if (threshold != null) { if (threshold < 0) { if (threshold * -1 < actualUncovered) { errors.push( `Jest: Uncovered count for ${key} (${actualUncovered})` + `exceeds ${name} threshold (${-1 * threshold})` ); } } else if (actual < threshold) { errors.push( `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%` ); } } return errors; }, [] ); } const THRESHOLD_GROUP_TYPES = { GLOB: 'glob', GLOBAL: 'global', PATH: 'path' }; const coveredFiles = map.files(); const thresholdGroups = Object.keys(globalConfig.coverageThreshold); const groupTypeByThresholdGroup = {}; const filesByGlob = {}; const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce( (files, file) => { const pathOrGlobMatches = thresholdGroups.reduce( (agg, thresholdGroup) => { const absoluteThresholdGroup = ( _path || _load_path() ).default.resolve(thresholdGroup); // The threshold group might be a path: if (file.indexOf(absoluteThresholdGroup) === 0) { groupTypeByThresholdGroup[thresholdGroup] = THRESHOLD_GROUP_TYPES.PATH; return agg.concat([[file, thresholdGroup]]); } // If the threshold group is not a path it might be a glob: // Note: glob.sync is slow. By memoizing the files matching each glob // (rather than recalculating it for each covered file) we save a tonne // of execution time. if (filesByGlob[absoluteThresholdGroup] === undefined) { filesByGlob[absoluteThresholdGroup] = ( _glob || _load_glob() ).default .sync(absoluteThresholdGroup) .map(filePath => (_path || _load_path()).default.resolve(filePath) ); } if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) { groupTypeByThresholdGroup[thresholdGroup] = THRESHOLD_GROUP_TYPES.GLOB; return agg.concat([[file, thresholdGroup]]); } return agg; }, [] ); if (pathOrGlobMatches.length > 0) { return files.concat(pathOrGlobMatches); } // Neither a glob or a path? Toss it in global if there's a global threshold: if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) { groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] = THRESHOLD_GROUP_TYPES.GLOBAL; return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]); } // A covered file that doesn't have a threshold: return files.concat([[file, undefined]]); }, [] ); const getFilesInThresholdGroup = thresholdGroup => coveredFilesSortedIntoThresholdGroup .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup) .map(fileAndGroup => fileAndGroup[0]); function combineCoverage(filePaths) { return filePaths .map(filePath => map.fileCoverageFor(filePath)) .reduce((combinedCoverage, nextFileCoverage) => { if (combinedCoverage === undefined || combinedCoverage === null) { return nextFileCoverage.toSummary(); } return combinedCoverage.merge(nextFileCoverage.toSummary()); }, undefined); } let errors = []; thresholdGroups.forEach(thresholdGroup => { switch (groupTypeByThresholdGroup[thresholdGroup]) { case THRESHOLD_GROUP_TYPES.GLOBAL: { const coverage = combineCoverage( getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL) ); if (coverage) { errors = errors.concat( check( thresholdGroup, globalConfig.coverageThreshold[thresholdGroup], coverage ) ); } break; } case THRESHOLD_GROUP_TYPES.PATH: { const coverage = combineCoverage( getFilesInThresholdGroup(thresholdGroup) ); if (coverage) { errors = errors.concat( check( thresholdGroup, globalConfig.coverageThreshold[thresholdGroup], coverage ) ); } break; } case THRESHOLD_GROUP_TYPES.GLOB: getFilesInThresholdGroup(thresholdGroup).forEach( fileMatchingGlob => { errors = errors.concat( check( fileMatchingGlob, globalConfig.coverageThreshold[thresholdGroup], map.fileCoverageFor(fileMatchingGlob).toSummary() ) ); } ); break; default: // If the file specified by path is not found, error is returned. if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) { errors = errors.concat( `Jest: Coverage data for ${thresholdGroup} was not found.` ); } // Sometimes all files in the coverage data are matched by // PATH and GLOB threshold groups in which case, don't error when // the global threshold group doesn't match any files. } }); errors = errors.filter( err => err !== undefined && err !== null && err.length > 0 ); if (errors.length > 0) { this.log(`${FAIL_COLOR(errors.join('\n'))}`); this._setError(new Error(errors.join('\n'))); } } } // Only exposed for the internal runner. Should not be used getCoverageMap() { return this._coverageMap; } } exports.default = CoverageReporter;