diff --git a/CHANGELOG.md b/CHANGELOG.md index 90d3ba8bc6ff..8736f4bb5946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ - `[jest-runner]`: Migrate to TypeScript ([#7968](https://github.com/facebook/jest/pull/7968)) - `[jest-runtime]`: Migrate to TypeScript ([#7964](https://github.com/facebook/jest/pull/7964), [#7988](https://github.com/facebook/jest/pull/7988)) - `[@jest/fake-timers]`: Extract FakeTimers class from `jest-util` into a new separate package ([#7987](https://github.com/facebook/jest/pull/7987)) +- `[@jest/reporters]`: Migrate to TypeScript ([#7994](https://github.com/facebook/jest/pull/7994)) - `[jest-repl]`: Migrate to TypeScript ([#8000](https://github.com/facebook/jest/pull/8000)) - `[jest-validate]`: Migrate to TypeScript ([#7991](https://github.com/facebook/jest/pull/7991)) - `[docs]`: Update CONTRIBUTING.md to add information about running jest with `jest-circus` locally ([#8013](https://github.com/facebook/jest/pull/8013)). diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index fceb1f9e0de0..39ff7a54f21d 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -3,8 +3,11 @@ "description": "Jest's reporters", "version": "24.1.0", "main": "build/index.js", + "types": "build/index.d.ts", "dependencies": { + "@jest/environment": "^24.1.0", "@jest/transform": "^24.1.0", + "@jest/types": "^24.1.0", "chalk": "^2.0.1", "exit": "^0.1.2", "glob": "^7.1.2", @@ -12,19 +15,21 @@ "istanbul-lib-coverage": "^2.0.2", "istanbul-lib-instrument": "^3.0.1", "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.0.0", + "jest-resolve": "^24.1.0", + "jest-runtime": "^24.1.0", "jest-util": "^24.0.0", "jest-worker": "^24.0.0", "node-notifier": "^5.2.1", "slash": "^2.0.0", + "source-map": "^0.6.0", "string-length": "^2.0.0" }, "devDependencies": { "@types/exit": "^0.1.30", "@types/glob": "^7.1.1", - "@types/istanbul-lib-coverage": "^1.1.0", "@types/istanbul-lib-instrument": "^1.7.2", "@types/istanbul-lib-source-maps": "^1.2.1", - "@types/node-notifier": "^0.0.28", "@types/slash": "^2.0.0", "@types/string-length": "^2.0.0", "strip-ansi": "^5.0.0" diff --git a/packages/jest-reporters/src/Status.js b/packages/jest-reporters/src/Status.ts similarity index 76% rename from packages/jest-reporters/src/Status.js rename to packages/jest-reporters/src/Status.ts index 6dbb87f73d6b..099dbe200739 100644 --- a/packages/jest-reporters/src/Status.js +++ b/packages/jest-reporters/src/Status.ts @@ -3,16 +3,13 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {AggregatedResult, TestResult} from 'types/TestResult'; -import type {ProjectConfig, Path} from 'types/Config'; -import type {ReporterOnStartOptions} from 'types/Reporters'; +import {TestResult, Config} from '@jest/types'; import chalk from 'chalk'; import stringLength from 'string-length'; +import {ReporterOnStartOptions} from './types'; import { getSummary, trimAndFormatPath, @@ -29,13 +26,16 @@ const RUNNING = chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' '; * shifting the whole list. */ class CurrentTestList { - _array: Array<{testPath: Path, config: ProjectConfig} | null>; + private _array: Array<{ + testPath: Config.Path; + config: Config.ProjectConfig; + } | null>; constructor() { this._array = []; } - add(testPath, config) { + add(testPath: Config.Path, config: Config.ProjectConfig) { const index = this._array.indexOf(null); const record = {config, testPath}; if (index !== -1) { @@ -45,9 +45,9 @@ class CurrentTestList { } } - delete(testPath) { + delete(testPath: Config.Path) { const record = this._array.find( - record => record && record.testPath === testPath, + record => record !== null && record.testPath === testPath, ); this._array[this._array.indexOf(record || null)] = null; } @@ -63,17 +63,15 @@ class CurrentTestList { * from the terminal. */ export default class Status { - _cache: ?{content: string, clear: string}; - _callback: () => void; - _currentTests: CurrentTestList; - _done: boolean; - _emitScheduled: boolean; - _estimatedTime: number; - _height: number; - _interval: IntervalID; - _aggregatedResults: AggregatedResult; - _lastUpdated: number; - _showStatus: boolean; + private _cache: {content: string; clear: string} | null; + private _callback?: () => void; + private _currentTests: CurrentTestList; + private _done: boolean; + private _emitScheduled: boolean; + private _estimatedTime: number; + private _interval?: NodeJS.Timeout; + private _aggregatedResults?: TestResult.AggregatedResult; + private _showStatus: boolean; constructor() { this._cache = null; @@ -81,7 +79,6 @@ export default class Status { this._done = false; this._emitScheduled = false; this._estimatedTime = 0; - this._height = 0; this._showStatus = false; } @@ -90,7 +87,7 @@ export default class Status { } runStarted( - aggregatedResults: AggregatedResult, + aggregatedResults: TestResult.AggregatedResult, options: ReporterOnStartOptions, ) { this._estimatedTime = (options && options.estimatedTime) || 0; @@ -102,11 +99,11 @@ export default class Status { runFinished() { this._done = true; - clearInterval(this._interval); + if (this._interval) clearInterval(this._interval); this._emit(); } - testStarted(testPath: Path, config: ProjectConfig) { + testStarted(testPath: Config.Path, config: Config.ProjectConfig) { this._currentTests.add(testPath, config); if (!this._showStatus) { this._emit(); @@ -116,9 +113,9 @@ export default class Status { } testFinished( - config: ProjectConfig, - testResult: TestResult, - aggregatedResults: AggregatedResult, + _config: Config.ProjectConfig, + testResult: TestResult.TestResult, + aggregatedResults: TestResult.AggregatedResult, ) { const {testFilePath} = testResult; this._aggregatedResults = aggregatedResults; @@ -135,8 +132,7 @@ export default class Status { return {clear: '', content: ''}; } - // $FlowFixMe - const width: number = process.stdout.columns; + const width: number = process.stdout.columns!; let content = '\n'; this._currentTests.get().forEach(record => { if (record) { @@ -178,13 +174,12 @@ export default class Status { return (this._cache = {clear, content}); } - _emit() { + private _emit() { this._cache = null; - this._lastUpdated = Date.now(); - this._callback(); + if (this._callback) this._callback(); } - _debouncedEmit() { + private _debouncedEmit() { if (!this._emitScheduled) { // Perf optimization to avoid two separate renders When // one test finishes and another test starts executing. @@ -196,7 +191,7 @@ export default class Status { } } - _tick() { + private _tick() { this._debouncedEmit(); } } diff --git a/packages/jest-reporters/src/__tests__/generateEmptyCoverage.test.js b/packages/jest-reporters/src/__tests__/generateEmptyCoverage.test.js index 00ae3f7a7ad8..678913cab672 100644 --- a/packages/jest-reporters/src/__tests__/generateEmptyCoverage.test.js +++ b/packages/jest-reporters/src/__tests__/generateEmptyCoverage.test.js @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import istanbulCoverage from 'istanbul-lib-coverage'; @@ -13,7 +11,6 @@ import generateEmptyCoverage from '../generateEmptyCoverage'; import path from 'path'; import os from 'os'; -//$FlowFixMe: Converted to TS import {makeGlobalConfig, makeProjectConfig} from '../../../../TestUtils'; jest.mock('@jest/transform', () => ({ @@ -64,6 +61,5 @@ it('generates an empty coverage object for a file without running it', () => { coverage = sourceMapStore.transformCoverage(coverageMap).map; } - // $FlowFixMe: IDK... expect(coverage.data).toMatchSnapshot({path: expect.any(String)}); }); diff --git a/packages/jest-reporters/src/__tests__/get_snapshot_status.test.js b/packages/jest-reporters/src/__tests__/get_snapshot_status.test.js index 6d722e2a7d9b..dbbaee15773b 100644 --- a/packages/jest-reporters/src/__tests__/get_snapshot_status.test.js +++ b/packages/jest-reporters/src/__tests__/get_snapshot_status.test.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ 'use strict'; diff --git a/packages/jest-reporters/src/__tests__/notify_reporter.test.js b/packages/jest-reporters/src/__tests__/notify_reporter.test.js index 274a2ca5d21c..9460c82ef1b7 100644 --- a/packages/jest-reporters/src/__tests__/notify_reporter.test.js +++ b/packages/jest-reporters/src/__tests__/notify_reporter.test.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ import type {TestSchedulerContext} from 'types/TestScheduler'; diff --git a/packages/jest-reporters/src/__tests__/utils.test.js b/packages/jest-reporters/src/__tests__/utils.test.js index 3f45dc600d19..d842b3e5a600 100644 --- a/packages/jest-reporters/src/__tests__/utils.test.js +++ b/packages/jest-reporters/src/__tests__/utils.test.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ import path from 'path'; diff --git a/packages/jest-reporters/src/__tests__/verbose_reporter.test.js b/packages/jest-reporters/src/__tests__/verbose_reporter.test.js index 206ce42ffc41..a13f6bd614bb 100644 --- a/packages/jest-reporters/src/__tests__/verbose_reporter.test.js +++ b/packages/jest-reporters/src/__tests__/verbose_reporter.test.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ 'use strict'; diff --git a/packages/jest-reporters/src/base_reporter.js b/packages/jest-reporters/src/base_reporter.ts similarity index 50% rename from packages/jest-reporters/src/base_reporter.js rename to packages/jest-reporters/src/base_reporter.ts index 9cd86e89fb6a..36c7c57670b9 100644 --- a/packages/jest-reporters/src/base_reporter.js +++ b/packages/jest-reporters/src/base_reporter.ts @@ -3,46 +3,48 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {AggregatedResult, TestResult} from 'types/TestResult'; -import type {Context} from 'types/Context'; -import type {Test} from 'types/TestRunner'; -import type {ReporterOnStartOptions} from 'types/Reporters'; - +import {TestResult} from '@jest/types'; import {preRunMessage} from 'jest-util'; +import {ReporterOnStartOptions, Context, Test, Reporter} from './types'; const {remove: preRunMessageRemove} = preRunMessage; -export default class BaseReporter { - _error: ?Error; +export default class BaseReporter implements Reporter { + private _error?: Error; log(message: string) { process.stderr.write(message + '\n'); } - onRunStart(results: AggregatedResult, options: ReporterOnStartOptions) { + onRunStart( + _results: TestResult.AggregatedResult, + _options: ReporterOnStartOptions, + ) { preRunMessageRemove(process.stderr); } - onTestResult(test: Test, testResult: TestResult, results: AggregatedResult) {} + onTestResult( + _test: Test, + _testResult: TestResult.TestResult, + _results: TestResult.AggregatedResult, + ) {} - onTestStart(test: Test) {} + onTestStart(_test: Test) {} onRunComplete( - contexts: Set, - aggregatedResults: AggregatedResult, - ): ?Promise {} + _contexts: Set, + _aggregatedResults: TestResult.AggregatedResult, + ): Promise | void {} - _setError(error: Error) { + protected _setError(error: Error) { this._error = error; } // Return an error that occurred during reporting. This error will // define whether the test run was successful or failed. - getLastError(): ?Error { + getLastError() { return this._error; } } diff --git a/packages/jest-reporters/src/coverage_reporter.js b/packages/jest-reporters/src/coverage_reporter.ts similarity index 64% rename from packages/jest-reporters/src/coverage_reporter.js rename to packages/jest-reporters/src/coverage_reporter.ts index d8466110f720..c3f2004858a0 100644 --- a/packages/jest-reporters/src/coverage_reporter.js +++ b/packages/jest-reporters/src/coverage_reporter.ts @@ -3,49 +3,44 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type { - AggregatedResult, - CoverageMap, - FileCoverage, - CoverageSummary, - TestResult, -} from 'types/TestResult'; -import typeof {worker} from './coverage_worker'; +// TODO: Remove this +/// +/// -import type {GlobalConfig, Path} from 'types/Config'; -import type {Context} from 'types/Context'; -import type {Test} from 'types/TestRunner'; +import path from 'path'; +import {TestResult, Config} from '@jest/types'; import {clearLine, isInteractive} from 'jest-util'; import {createReporter} from 'istanbul-api'; import chalk from 'chalk'; -import istanbulCoverage from 'istanbul-lib-coverage'; -import libSourceMaps from 'istanbul-lib-source-maps'; +import istanbulCoverage, { + CoverageMap, + FileCoverage, + CoverageSummary, + CoverageSummaryData, +} from 'istanbul-lib-coverage'; +import libSourceMaps, {MapStore} from 'istanbul-lib-source-maps'; import Worker from 'jest-worker'; -import BaseReporter from './base_reporter'; -import path from 'path'; import glob from 'glob'; +import {RawSourceMap} from 'source-map'; +import BaseReporter from './base_reporter'; +import {Context, Test, CoverageWorker, CoverageReporterOptions} from './types'; const FAIL_COLOR = chalk.bold.red; const RUNNING_TEST_COLOR = chalk.bold.dim; -type CoverageWorker = {worker: worker}; - -export type CoverageReporterOptions = { - changedFiles?: Set, -}; - export default class CoverageReporter extends BaseReporter { - _coverageMap: CoverageMap; - _globalConfig: GlobalConfig; - _sourceMapStore: any; - _options: CoverageReporterOptions; - - constructor(globalConfig: GlobalConfig, options?: CoverageReporterOptions) { + private _coverageMap: CoverageMap; + private _globalConfig: Config.GlobalConfig; + private _sourceMapStore: MapStore; + private _options: CoverageReporterOptions; + + constructor( + globalConfig: Config.GlobalConfig, + options?: CoverageReporterOptions, + ) { super(); this._coverageMap = istanbulCoverage.createCoverageMap({}); this._globalConfig = globalConfig; @@ -54,9 +49,9 @@ export default class CoverageReporter extends BaseReporter { } onTestResult( - test: Test, - testResult: TestResult, - aggregatedResults: AggregatedResult, + _test: Test, + testResult: TestResult.TestResult, + _aggregatedResults: TestResult.AggregatedResult, ) { if (testResult.coverage) { this._coverageMap.merge(testResult.coverage); @@ -64,12 +59,12 @@ export default class CoverageReporter extends BaseReporter { delete testResult.coverage; Object.keys(testResult.sourceMaps).forEach(sourcePath => { - let inputSourceMap: ?Object; + let inputSourceMap: RawSourceMap | undefined; try { const coverage: FileCoverage = this._coverageMap.fileCoverageFor( sourcePath, ); - ({inputSourceMap} = coverage.toJSON()); + ({inputSourceMap} = coverage.toJSON() as any); } finally { if (inputSourceMap) { this._sourceMapStore.registerMap(sourcePath, inputSourceMap); @@ -86,7 +81,7 @@ export default class CoverageReporter extends BaseReporter { async onRunComplete( contexts: Set, - aggregatedResults: AggregatedResult, + aggregatedResults: TestResult.AggregatedResult, ) { await this._addUntestedFiles(this._globalConfig, contexts); const {map, sourceFinder} = this._sourceMapStore.transformCoverage( @@ -121,11 +116,11 @@ export default class CoverageReporter extends BaseReporter { this._checkThreshold(this._globalConfig, map); } - async _addUntestedFiles( - globalConfig: GlobalConfig, + private async _addUntestedFiles( + globalConfig: Config.GlobalConfig, contexts: Set, ): Promise { - const files = []; + const files: Array<{config: Config.ProjectConfig; path: string}> = []; contexts.forEach(context => { const config = context.config; @@ -154,7 +149,7 @@ export default class CoverageReporter extends BaseReporter { ); } - let worker: CoverageWorker; + let worker: CoverageWorker | Worker; if (this._globalConfig.maxWorkers <= 1) { worker = require('./coverage_worker'); @@ -170,7 +165,7 @@ export default class CoverageReporter extends BaseReporter { const filename = fileObj.path; const config = fileObj.config; - if (!this._coverageMap.data[filename]) { + if (!this._coverageMap.data[filename] && 'worker' in worker) { try { const result = await worker.worker({ config, @@ -210,38 +205,41 @@ export default class CoverageReporter extends BaseReporter { clearLine(process.stderr); } - if (worker && typeof worker.end === 'function') { + if (worker && 'end' in worker && typeof worker.end === 'function') { worker.end(); } } - _checkThreshold(globalConfig: GlobalConfig, map: CoverageMap) { + private _checkThreshold(globalConfig: Config.GlobalConfig, map: CoverageMap) { 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) { + function check( + name: string, + thresholds: {[index: string]: number}, + actuals: CoverageSummaryData, + ) { + return (['statements', 'branches', 'lines', 'functions'] as Array< + keyof CoverageSummaryData + >).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: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`, + `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; - }, - [], - ); + } + return errors; + }, []); } const THRESHOLD_GROUP_TYPES = { @@ -251,73 +249,71 @@ export default class CoverageReporter extends BaseReporter { }; 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.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]]); - } + const groupTypeByThresholdGroup: {[index: string]: string} = {}; + const filesByGlob: {[index: string]: Array} = {}; + + const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce< + Array<[string, string | undefined]> + >((files, file) => { + const pathOrGlobMatches = thresholdGroups.reduce< + Array<[string, string]> + >((agg, thresholdGroup) => { + const absoluteThresholdGroup = path.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: + // 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 - .sync(absoluteThresholdGroup) - .map(filePath => path.resolve(filePath)); - } + // 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 + .sync(absoluteThresholdGroup) + .map(filePath => path.resolve(filePath)); + } - if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) { - groupTypeByThresholdGroup[thresholdGroup] = - THRESHOLD_GROUP_TYPES.GLOB; - return agg.concat([[file, thresholdGroup]]); - } + if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) { + groupTypeByThresholdGroup[thresholdGroup] = + THRESHOLD_GROUP_TYPES.GLOB; + return agg.concat([[file, thresholdGroup]]); + } - return agg; - }, - [], - ); + return agg; + }, []); - if (pathOrGlobMatches.length > 0) { - return files.concat(pathOrGlobMatches); - } + 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]]); - } + // 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]]); - }, - [], - ); + // A covered file that doesn't have a threshold: + return files.concat([[file, undefined]]); + }, []); - const getFilesInThresholdGroup = thresholdGroup => + const getFilesInThresholdGroup = (thresholdGroup: string) => coveredFilesSortedIntoThresholdGroup .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup) .map(fileAndGroup => fileAndGroup[0]); - function combineCoverage(filePaths) { + function combineCoverage(filePaths: Array) { return filePaths .map(filePath => map.fileCoverageFor(filePath)) .reduce( ( - combinedCoverage: ?CoverageSummary, + combinedCoverage: CoverageSummary | null | undefined, nextFileCoverage: FileCoverage, ) => { if (combinedCoverage === undefined || combinedCoverage === null) { @@ -329,7 +325,7 @@ export default class CoverageReporter extends BaseReporter { ); } - let errors = []; + let errors: Array = []; thresholdGroups.forEach(thresholdGroup => { switch (groupTypeByThresholdGroup[thresholdGroup]) { diff --git a/packages/jest-reporters/src/coverage_worker.js b/packages/jest-reporters/src/coverage_worker.ts similarity index 62% rename from packages/jest-reporters/src/coverage_worker.js rename to packages/jest-reporters/src/coverage_worker.ts index 13c41e4dceea..6461a7973fde 100644 --- a/packages/jest-reporters/src/coverage_worker.js +++ b/packages/jest-reporters/src/coverage_worker.ts @@ -3,27 +3,25 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig, ProjectConfig, Path} from 'types/Config'; -import type {CoverageReporterOptions} from './coverage_reporter'; - -import exit from 'exit'; import fs from 'fs'; +import {Config} from '@jest/types'; +import exit from 'exit'; +import {CoverageReporterOptions} from './types'; + import generateEmptyCoverage, { - type CoverageWorkerResult, + CoverageWorkerResult, } from './generateEmptyCoverage'; -export type CoverageWorkerData = {| - globalConfig: GlobalConfig, - config: ProjectConfig, - path: Path, - options?: CoverageReporterOptions, -|}; +export type CoverageWorkerData = { + globalConfig: Config.GlobalConfig; + config: Config.ProjectConfig; + path: Config.Path; + options?: CoverageReporterOptions; +}; -export type {CoverageWorkerResult}; +export {CoverageWorkerResult}; // Make sure uncaught errors are logged before we exit. process.on('uncaughtException', err => { @@ -36,7 +34,7 @@ export function worker({ globalConfig, path, options, -}: CoverageWorkerData): ?CoverageWorkerResult { +}: CoverageWorkerData): CoverageWorkerResult | null { return generateEmptyCoverage( fs.readFileSync(path, 'utf8'), path, diff --git a/packages/jest-reporters/src/default_reporter.js b/packages/jest-reporters/src/default_reporter.ts similarity index 79% rename from packages/jest-reporters/src/default_reporter.js rename to packages/jest-reporters/src/default_reporter.ts index a842f663997a..686207e1d3ae 100644 --- a/packages/jest-reporters/src/default_reporter.js +++ b/packages/jest-reporters/src/default_reporter.ts @@ -3,19 +3,13 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -/* global stream$Writable, tty$WriteStream */ - -import type {AggregatedResult, TestResult} from 'types/TestResult'; -import type {GlobalConfig, Path, ProjectConfig} from 'types/Config'; -import type {Test} from 'types/TestRunner'; -import type {ReporterOnStartOptions} from 'types/Reporters'; +import {TestResult, Config} from '@jest/types'; import {clearLine, getConsoleOutput, isInteractive} from 'jest-util'; import chalk from 'chalk'; +import {Test, ReporterOnStartOptions} from './types'; import BaseReporter from './base_reporter'; import Status from './Status'; import getResultHeader from './get_result_header'; @@ -27,14 +21,14 @@ type FlushBufferedOutput = () => void; const TITLE_BULLET = chalk.bold('\u25cf '); export default class DefaultReporter extends BaseReporter { - _clear: string; // ANSI clear sequence for the last printed status - _err: write; - _globalConfig: GlobalConfig; - _out: write; - _status: Status; - _bufferedOutput: Set; - - constructor(globalConfig: GlobalConfig) { + private _clear: string; // ANSI clear sequence for the last printed status + private _err: write; + protected _globalConfig: Config.GlobalConfig; + private _out: write; + private _status: Status; + private _bufferedOutput: Set; + + constructor(globalConfig: Config.GlobalConfig) { super(); this._globalConfig = globalConfig; this._clear = ''; @@ -50,11 +44,11 @@ export default class DefaultReporter extends BaseReporter { }); } - _wrapStdio(stream: stream$Writable | tty$WriteStream) { + private _wrapStdio(stream: NodeJS.WritableStream | NodeJS.WriteStream) { const originalWrite = stream.write; - let buffer = []; - let timeout = null; + let buffer: Array = []; + let timeout: NodeJS.Timeout | null = null; const flushBufferedOutput = () => { const string = buffer.join(''); @@ -88,8 +82,7 @@ export default class DefaultReporter extends BaseReporter { } }; - // $FlowFixMe - stream.write = chunk => { + stream.write = (chunk: string) => { buffer.push(chunk); debouncedFlush(); return true; @@ -103,7 +96,7 @@ export default class DefaultReporter extends BaseReporter { } } - _clearStatus() { + private _clearStatus() { if (isInteractive) { if (this._globalConfig.useStderr) { this._err(this._clear); @@ -113,7 +106,7 @@ export default class DefaultReporter extends BaseReporter { } } - _printStatus() { + private _printStatus() { const {content, clear} = this._status.get(); this._clear = clear; if (isInteractive) { @@ -126,7 +119,7 @@ export default class DefaultReporter extends BaseReporter { } onRunStart( - aggregatedResults: AggregatedResult, + aggregatedResults: TestResult.AggregatedResult, options: ReporterOnStartOptions, ) { this._status.runStarted(aggregatedResults, options); @@ -139,17 +132,15 @@ export default class DefaultReporter extends BaseReporter { onRunComplete() { this.forceFlushBufferedOutput(); this._status.runFinished(); - // $FlowFixMe process.stdout.write = this._out; - // $FlowFixMe process.stderr.write = this._err; clearLine(process.stderr); } onTestResult( test: Test, - testResult: TestResult, - aggregatedResults: AggregatedResult, + testResult: TestResult.TestResult, + aggregatedResults: TestResult.AggregatedResult, ) { this.testFinished(test.context.config, testResult, aggregatedResults); if (!testResult.skipped) { @@ -168,17 +159,17 @@ export default class DefaultReporter extends BaseReporter { } testFinished( - config: ProjectConfig, - testResult: TestResult, - aggregatedResults: AggregatedResult, + config: Config.ProjectConfig, + testResult: TestResult.TestResult, + aggregatedResults: TestResult.AggregatedResult, ) { this._status.testFinished(config, testResult, aggregatedResults); } printTestFileHeader( - testPath: Path, - config: ProjectConfig, - result: TestResult, + _testPath: Config.Path, + config: Config.ProjectConfig, + result: TestResult.TestResult, ) { this.log(getResultHeader(result, this._globalConfig, config)); const consoleBuffer = result.console; @@ -197,9 +188,9 @@ export default class DefaultReporter extends BaseReporter { } printTestFileFailureMessage( - testPath: Path, - config: ProjectConfig, - result: TestResult, + _testPath: Config.Path, + _config: Config.ProjectConfig, + result: TestResult.TestResult, ) { if (result.failureMessage) { this.log(result.failureMessage); diff --git a/packages/jest-reporters/src/generateEmptyCoverage.js b/packages/jest-reporters/src/generateEmptyCoverage.ts similarity index 74% rename from packages/jest-reporters/src/generateEmptyCoverage.js rename to packages/jest-reporters/src/generateEmptyCoverage.ts index 493d251646d6..6ea3567566d9 100644 --- a/packages/jest-reporters/src/generateEmptyCoverage.js +++ b/packages/jest-reporters/src/generateEmptyCoverage.ts @@ -3,30 +3,30 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig, ProjectConfig, Path} from 'types/Config'; +// TODO: Remove this +/// +import {Config} from '@jest/types'; import {readInitialCoverage} from 'istanbul-lib-instrument'; import {classes} from 'istanbul-lib-coverage'; import {shouldInstrument, ScriptTransformer} from '@jest/transform'; -export type CoverageWorkerResult = {| - coverage: any, - sourceMapPath: ?string, -|}; +const FileCoverage = classes.FileCoverage; -const {FileCoverage} = classes; +export type CoverageWorkerResult = { + coverage: any; + sourceMapPath?: string | null; +}; export default function( source: string, - filename: Path, - globalConfig: GlobalConfig, - config: ProjectConfig, - changedFiles: ?Set, -): ?CoverageWorkerResult { + filename: Config.Path, + globalConfig: Config.GlobalConfig, + config: Config.ProjectConfig, + changedFiles?: Set, +): CoverageWorkerResult | null { const coverageOptions = { changedFiles, collectCoverage: globalConfig.collectCoverage, diff --git a/packages/jest-reporters/src/get_result_header.js b/packages/jest-reporters/src/get_result_header.ts similarity index 85% rename from packages/jest-reporters/src/get_result_header.js rename to packages/jest-reporters/src/get_result_header.ts index ada0082ce073..43c8b2301d94 100644 --- a/packages/jest-reporters/src/get_result_header.js +++ b/packages/jest-reporters/src/get_result_header.ts @@ -3,12 +3,9 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig, ProjectConfig} from 'types/Config'; -import type {TestResult} from 'types/TestResult'; +import {Config, TestResult} from '@jest/types'; import chalk from 'chalk'; import {formatTestPath, printDisplayName} from './utils'; @@ -28,9 +25,9 @@ const PASS = chalk.supportsColor : PASS_TEXT; export default ( - result: TestResult, - globalConfig: GlobalConfig, - projectConfig?: ProjectConfig, + result: TestResult.TestResult, + globalConfig: Config.GlobalConfig, + projectConfig?: Config.ProjectConfig, ) => { const testPath = result.testFilePath; const status = @@ -46,7 +43,7 @@ export default ( } if (result.memoryUsage) { - const toMB = bytes => Math.floor(bytes / 1024 / 1024); + const toMB = (bytes: number) => Math.floor(bytes / 1024 / 1024); testDetail.push(`${toMB(result.memoryUsage)} MB heap size`); } diff --git a/packages/jest-reporters/src/get_snapshot_status.js b/packages/jest-reporters/src/get_snapshot_status.ts similarity index 93% rename from packages/jest-reporters/src/get_snapshot_status.js rename to packages/jest-reporters/src/get_snapshot_status.ts index 8406fa3fa3a2..91c4e692eb90 100644 --- a/packages/jest-reporters/src/get_snapshot_status.js +++ b/packages/jest-reporters/src/get_snapshot_status.ts @@ -3,11 +3,9 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {TestResult} from 'types/TestResult'; +import {TestResult} from '@jest/types'; import chalk from 'chalk'; @@ -21,7 +19,7 @@ const SNAPSHOT_UPDATED = chalk.bold.green; const SNAPSHOT_OUTDATED = chalk.bold.yellow; export default ( - snapshot: $PropertyType, + snapshot: TestResult.TestResult['snapshot'], afterUpdate: boolean, ): Array => { const statuses = []; diff --git a/packages/jest-reporters/src/get_snapshot_summary.js b/packages/jest-reporters/src/get_snapshot_summary.ts similarity index 95% rename from packages/jest-reporters/src/get_snapshot_summary.js rename to packages/jest-reporters/src/get_snapshot_summary.ts index 60eea8aa0cab..19027f821aca 100644 --- a/packages/jest-reporters/src/get_snapshot_summary.js +++ b/packages/jest-reporters/src/get_snapshot_summary.ts @@ -3,12 +3,9 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {SnapshotSummary} from 'types/TestResult'; -import type {GlobalConfig} from 'types/Config'; +import {Config, TestResult} from '@jest/types'; import chalk from 'chalk'; import {pluralize} from 'jest-util'; @@ -26,8 +23,8 @@ const SNAPSHOT_SUMMARY = chalk.bold; const SNAPSHOT_UPDATED = chalk.bold.green; export default ( - snapshots: SnapshotSummary, - globalConfig: GlobalConfig, + snapshots: TestResult.SnapshotSummary, + globalConfig: Config.GlobalConfig, updateCommand: string, ): Array => { const summary = []; diff --git a/packages/jest-reporters/src/index.js b/packages/jest-reporters/src/index.ts similarity index 97% rename from packages/jest-reporters/src/index.js rename to packages/jest-reporters/src/index.ts index c714b0cce89c..d706797ee0fd 100644 --- a/packages/jest-reporters/src/index.js +++ b/packages/jest-reporters/src/index.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ export {default as BaseReporter} from './base_reporter'; diff --git a/packages/jest-reporters/src/istanbul-api.d.ts b/packages/jest-reporters/src/istanbul-api.d.ts new file mode 100644 index 000000000000..d39fef7f7ee6 --- /dev/null +++ b/packages/jest-reporters/src/istanbul-api.d.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. 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. + */ + +declare module 'istanbul-api' { + class Reporter { + constructor(config?: object, options?: object); + add(format: string): void; + addAll(formats: Array): void; + write(coverageMap: object, options: object): void; + config: object; + dir: string; + reports: object; + summarizer: string; + } + + function createReporter(config?: object, options?: object): Reporter; +} diff --git a/packages/jest-reporters/src/istanbul-lib-coverage.d.ts b/packages/jest-reporters/src/istanbul-lib-coverage.d.ts new file mode 100644 index 000000000000..360455ca42b5 --- /dev/null +++ b/packages/jest-reporters/src/istanbul-lib-coverage.d.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. 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. + */ + +declare module 'istanbul-lib-coverage' { + export interface CoverageSummaryData { + lines: Totals; + statements: Totals; + branches: Totals; + functions: Totals; + } + + export class CoverageSummary { + constructor(data: CoverageSummary | CoverageSummaryData); + merge(obj: CoverageSummary): CoverageSummary; + toJSON(): CoverageSummaryData; + isEmpty(): boolean; + data: CoverageSummaryData; + lines: Totals; + statements: Totals; + branches: Totals; + functions: Totals; + } + + export interface CoverageMapData { + [key: string]: FileCoverage; + } + + export class CoverageMap { + constructor(data: CoverageMapData | CoverageMap); + addFileCoverage( + pathOrObject: string | FileCoverage | FileCoverageData, + ): void; + files(): Array; + fileCoverageFor(filename: string): FileCoverage; + filter(callback: (key: string) => boolean): void; + getCoverageSummary(): CoverageSummary; + merge(data: CoverageMapData | CoverageMap): void; + toJSON(): CoverageMapData; + data: CoverageMapData; + } + + export interface Location { + line: number; + column: number; + } + + export interface Range { + start: Location; + end: Location; + } + + export interface BranchMapping { + loc: Range; + type: string; + locations: Array; + line: number; + } + + export interface FunctionMapping { + name: string; + decl: Range; + loc: Range; + line: number; + } + + export interface FileCoverageData { + path: string; + statementMap: {[key: string]: Range}; + fnMap: {[key: string]: FunctionMapping}; + branchMap: {[key: string]: BranchMapping}; + s: {[key: string]: number}; + f: {[key: string]: number}; + b: {[key: string]: Array}; + } + + export interface Totals { + total: number; + covered: number; + skipped: number; + pct: number; + } + + export interface Coverage { + covered: number; + total: number; + coverage: number; + } + + export class FileCoverage implements FileCoverageData { + constructor(data: string | FileCoverage | FileCoverageData); + merge(other: FileCoverageData): void; + getBranchCoverageByLine(): {[line: number]: Coverage}; + getLineCoverage(): {[line: number]: number}; + getUncoveredLines(): Array; + resetHits(): void; + computeBranchTotals(): Totals; + computeSimpleTotals(): Totals; + toSummary(): CoverageSummary; + toJSON(): object; + + data: FileCoverageData; + path: string; + statementMap: {[key: string]: Range}; + fnMap: {[key: string]: FunctionMapping}; + branchMap: {[key: string]: BranchMapping}; + s: {[key: string]: number}; + f: {[key: string]: number}; + b: {[key: string]: Array}; + } + + export const classes: { + FileCoverage: typeof FileCoverage; + }; + + export function createCoverageMap( + data?: CoverageMap | CoverageMapData, + ): CoverageMap; + export function createCoverageSummary( + obj?: CoverageSummary | CoverageSummaryData, + ): CoverageSummary; + export function createFileCoverage( + pathOrObject: string | FileCoverage | FileCoverageData, + ): FileCoverage; +} diff --git a/packages/jest-reporters/src/node-notifier.d.ts b/packages/jest-reporters/src/node-notifier.d.ts new file mode 100644 index 000000000000..27a27e1211dd --- /dev/null +++ b/packages/jest-reporters/src/node-notifier.d.ts @@ -0,0 +1,255 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. 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. + */ + +/// + +// TODO: Replace with @types/node-notifier +declare module 'node-notifier' { + import NotificationCenter = require('node-notifier/notifiers/notificationcenter'); + import NotifySend = require('node-notifier/notifiers/notifysend'); + import WindowsToaster = require('node-notifier/notifiers/toaster'); + import WindowsBalloon = require('node-notifier/notifiers/balloon'); + import Growl = require('node-notifier/notifiers/growl'); + + namespace nodeNotifier { + interface NodeNotifier extends NodeJS.EventEmitter { + notify( + notification?: NotificationCenter.Notification, + callback?: NotificationCallback, + ): NotificationCenter; + notify( + notification?: WindowsToaster.Notification, + callback?: NotificationCallback, + ): WindowsToaster; + notify( + notification?: WindowsBalloon.Notification, + callback?: NotificationCallback, + ): WindowsBalloon; + notify( + notification?: NotifySend.Notification, + callback?: NotificationCallback, + ): NotifySend; + notify( + notification?: Growl.Notification, + callback?: NotificationCallback, + ): Growl; + notify( + notification?: Notification, + callback?: NotificationCallback, + ): NodeNotifier; + notify( + notification?: string, + callback?: NotificationCallback, + ): NodeNotifier; + NotificationCenter: typeof NotificationCenter; + NotifySend: typeof NotifySend; + WindowsToaster: typeof WindowsToaster; + WindowsBalloon: typeof WindowsBalloon; + Growl: typeof Growl; + } + + interface Notification { + title?: string; + message?: string; + /** Absolute path (not balloons) */ + icon?: string; + /** Wait with callback until user action is taken on notification */ + wait?: boolean; + } + + interface NotificationMetadata { + activationType?: string; + activationAt?: string; + deliveredAt?: string; + activationValue?: string; + activationValueIndex?: string; + } + + interface NotificationCallback { + ( + err: Error | null, + response: string, + metadata?: NotificationMetadata, + ): void; + } + + interface Option { + withFallback?: boolean; + customPath?: string; + } + } + + const nodeNotifier: nodeNotifier.NodeNotifier; + + export = nodeNotifier; +} + +declare module 'node-notifier/notifiers/notificationcenter' { + import notifier = require('node-notifier'); + + class NotificationCenter { + constructor(option?: notifier.Option); + notify( + notification?: NotificationCenter.Notification, + callback?: notifier.NotificationCallback, + ): NotificationCenter; + } + + namespace NotificationCenter { + interface Notification extends notifier.Notification { + /** + * Case Sensitive string for location of sound file, or use one of macOS' native sounds. + */ + sound?: boolean | string; + subtitle?: string; + /** Attach image? (Absolute path) */ + contentImage?: string; + /** URL to open on click */ + open?: string; + /** + * The amount of seconds before the notification closes. + * Takes precedence over wait if both are defined. + */ + timeout?: number; + /** Label for cancel button */ + closeLabel?: string; + /** Action label or list of labels in case of dropdown. */ + actions?: string | Array; + /** Label to be used if there are multiple actions */ + dropdownLabel?: string; + /** + * If notification should take input. + * Value passed as third argument in callback and event emitter. + */ + reply?: boolean; + } + } + + export = NotificationCenter; +} + +declare module 'node-notifier/notifiers/notifysend' { + import notifier = require('node-notifier'); + + class NotifySend { + constructor(option?: notifier.Option); + notify( + notification?: NotifySend.Notification, + callback?: notifier.NotificationCallback, + ): NotifySend; + } + + namespace NotifySend { + interface Notification { + title?: string; + message?: string; + icon?: string; + /** Specifies the urgency level (low, normal, critical). */ + urgency?: string; + /** Specifies the timeout in milliseconds at which to expire the notification */ + time?: number; + /** Specifies the notification category */ + category?: string; + /** Specifies basic extra data to pass. Valid types are int, double, string and byte. */ + hint?: string; + } + } + + export = NotifySend; +} + +declare module 'node-notifier/notifiers/toaster' { + import notifier = require('node-notifier'); + + class WindowsToaster { + constructor(option?: notifier.Option); + notify( + notification?: WindowsToaster.Notification, + callback?: notifier.NotificationCallback, + ): WindowsToaster; + } + + namespace WindowsToaster { + interface Notification extends notifier.Notification { + /** + * Defined by http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx + */ + sound?: boolean | string; + /** ID to use for closing notification. */ + id?: number; + /** App.ID and app Name. Defaults to no value, causing SnoreToast text to be visible. */ + appID?: string; + /** Refer to previously created notification to close. */ + remove?: number; + /** + * Creates a shortcut in the start menu which point to the + * executable , appID used for the notifications. + */ + install?: string; + } + } + + export = WindowsToaster; +} + +declare module 'node-notifier/notifiers/growl' { + import notifier = require('node-notifier'); + + class Growl { + constructor(option?: Growl.Option); + notify( + notification?: Growl.Notification, + callback?: notifier.NotificationCallback, + ): Growl; + } + + namespace Growl { + interface Option { + name?: string; + host?: string; + port?: number; + } + + interface Notification extends notifier.Notification { + /** whether or not to sticky the notification (defaults to false) */ + sticky?: boolean; + /** type of notification to use (defaults to the first registered type) */ + label?: string; + /** the priority of the notification from lowest (-2) to highest (2) */ + priority?: number; + } + } + + export = Growl; +} + +declare module 'node-notifier/notifiers/balloon' { + import notifier = require('node-notifier'); + + class WindowsBalloon { + constructor(option?: notifier.Option); + notify( + notification?: WindowsBalloon.Notification, + callback?: notifier.NotificationCallback, + ): WindowsBalloon; + } + + namespace WindowsBalloon { + interface Notification { + title?: string; + message?: string; + /** How long to show balloons in ms */ + time?: number; + /** Wait with callback until user action is taken on notification */ + wait?: boolean; + /** The notification type */ + type?: 'info' | 'warn' | 'error'; + } + } + + export = WindowsBalloon; +} diff --git a/packages/jest-reporters/src/notify_reporter.js b/packages/jest-reporters/src/notify_reporter.ts similarity index 87% rename from packages/jest-reporters/src/notify_reporter.js rename to packages/jest-reporters/src/notify_reporter.ts index 9a6c9a27f3cb..b6755b85b25c 100644 --- a/packages/jest-reporters/src/notify_reporter.js +++ b/packages/jest-reporters/src/notify_reporter.ts @@ -3,19 +3,17 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig} from 'types/Config'; -import type {AggregatedResult} from 'types/TestResult'; -import type {TestSchedulerContext} from 'types/TestScheduler'; -import type {Context} from 'types/Context'; +// TODO: Remove this +/// -import exit from 'exit'; import path from 'path'; import util from 'util'; +import exit from 'exit'; +import {Config, TestResult} from '@jest/types'; import notifier from 'node-notifier'; +import {TestSchedulerContext, Context} from './types'; import BaseReporter from './base_reporter'; const isDarwin = process.platform === 'darwin'; @@ -23,12 +21,13 @@ const isDarwin = process.platform === 'darwin'; const icon = path.resolve(__dirname, '../assets/jest_logo.png'); export default class NotifyReporter extends BaseReporter { - _startRun: (globalConfig: GlobalConfig) => *; - _globalConfig: GlobalConfig; - _context: TestSchedulerContext; + private _startRun: (globalConfig: Config.GlobalConfig) => any; + private _globalConfig: Config.GlobalConfig; + private _context: TestSchedulerContext; + constructor( - globalConfig: GlobalConfig, - startRun: (globalConfig: GlobalConfig) => *, + globalConfig: Config.GlobalConfig, + startRun: (globalConfig: Config.GlobalConfig) => any, context: TestSchedulerContext, ) { super(); @@ -37,7 +36,10 @@ export default class NotifyReporter extends BaseReporter { this._context = context; } - onRunComplete(contexts: Set, result: AggregatedResult): void { + onRunComplete( + contexts: Set, + result: TestResult.AggregatedResult, + ): void { const success = result.numFailedTests === 0 && result.numRuntimeErrorTestSuites === 0; diff --git a/packages/jest-reporters/src/summary_reporter.js b/packages/jest-reporters/src/summary_reporter.ts similarity index 87% rename from packages/jest-reporters/src/summary_reporter.js rename to packages/jest-reporters/src/summary_reporter.ts index 863ab6e0db41..cf392376c53f 100644 --- a/packages/jest-reporters/src/summary_reporter.js +++ b/packages/jest-reporters/src/summary_reporter.ts @@ -3,17 +3,12 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {AggregatedResult, SnapshotSummary} from 'types/TestResult'; -import type {GlobalConfig} from 'types/Config'; -import type {Context} from 'types/Context'; -import type {ReporterOnStartOptions} from 'types/Reporters'; - +import {TestResult, Config} from '@jest/types'; import chalk from 'chalk'; import {testPathPatternToRegExp} from 'jest-util'; +import {Context, ReporterOnStartOptions} from './types'; import BaseReporter from './base_reporter'; import {getSummary} from './utils'; import getResultHeader from './get_result_header'; @@ -49,10 +44,10 @@ const NPM_EVENTS = new Set([ ]); export default class SummaryReporter extends BaseReporter { - _estimatedTime: number; - _globalConfig: GlobalConfig; + private _estimatedTime: number; + private _globalConfig: Config.GlobalConfig; - constructor(globalConfig: GlobalConfig) { + constructor(globalConfig: Config.GlobalConfig) { super(); this._globalConfig = globalConfig; this._estimatedTime = 0; @@ -63,21 +58,24 @@ export default class SummaryReporter extends BaseReporter { // in Node.js 0.10 and still persists in Node.js 6.7+. // Let's print the test failure summary character by character which is safer // when hundreds of tests are failing. - _write(string: string) { + private _write(string: string) { for (let i = 0; i < string.length; i++) { process.stderr.write(string.charAt(i)); } } onRunStart( - aggregatedResults: AggregatedResult, + aggregatedResults: TestResult.AggregatedResult, options: ReporterOnStartOptions, ) { super.onRunStart(aggregatedResults, options); this._estimatedTime = options.estimatedTime; } - onRunComplete(contexts: Set, aggregatedResults: AggregatedResult) { + onRunComplete( + contexts: Set, + aggregatedResults: TestResult.AggregatedResult, + ) { const {numTotalTestSuites, testResults, wasInterrupted} = aggregatedResults; if (numTotalTestSuites) { const lastResult = testResults[testResults.length - 1]; @@ -115,9 +113,9 @@ export default class SummaryReporter extends BaseReporter { } } - _printSnapshotSummary( - snapshots: SnapshotSummary, - globalConfig: GlobalConfig, + private _printSnapshotSummary( + snapshots: TestResult.SnapshotSummary, + globalConfig: Config.GlobalConfig, ) { if ( snapshots.added || @@ -127,7 +125,7 @@ export default class SummaryReporter extends BaseReporter { snapshots.updated ) { let updateCommand; - const event = process.env.npm_lifecycle_event; + const event = process.env.npm_lifecycle_event || ''; const prefix = NPM_EVENTS.has(event) ? '' : 'run '; const isYarn = typeof process.env.npm_config_user_agent === 'string' && @@ -160,9 +158,9 @@ export default class SummaryReporter extends BaseReporter { } } - _printSummary( - aggregatedResults: AggregatedResult, - globalConfig: GlobalConfig, + private _printSummary( + aggregatedResults: TestResult.AggregatedResult, + globalConfig: Config.GlobalConfig, ) { // If there were any failing tests and there was a large number of tests // executed, re-print the failing results at the end of execution output. @@ -188,7 +186,10 @@ export default class SummaryReporter extends BaseReporter { } } - _getTestSummary(contexts: Set, globalConfig: GlobalConfig) { + private _getTestSummary( + contexts: Set, + globalConfig: Config.GlobalConfig, + ) { const getMatchingTestsInfo = () => { const prefix = globalConfig.findRelatedTests ? ' related to files matching ' diff --git a/packages/jest-reporters/src/types.ts b/packages/jest-reporters/src/types.ts new file mode 100644 index 000000000000..cfdde6598abc --- /dev/null +++ b/packages/jest-reporters/src/types.ts @@ -0,0 +1,98 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. 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 {Config, TestResult} from '@jest/types'; +import {JestEnvironment as Environment} from '@jest/environment'; +import {ModuleMap, FS as HasteFS} from 'jest-haste-map'; +import HasteResolver from 'jest-resolve'; +import Runtime from 'jest-runtime'; +import {worker} from './coverage_worker'; + +export type ReporterOnStartOptions = { + estimatedTime: number; + showStatus: boolean; +}; + +export type Context = { + config: Config.ProjectConfig; + hasteFS: HasteFS; + moduleMap: ModuleMap; + resolver: HasteResolver; +}; + +export type Test = { + context: Context; + duration?: number; + path: Config.Path; +}; + +export type CoverageWorker = {worker: typeof worker}; + +export type CoverageReporterOptions = { + changedFiles?: Set; +}; + +export type OnTestStart = (test: Test) => Promise; +export type OnTestFailure = ( + test: Test, + error: TestResult.SerializableError, +) => Promise; +export type OnTestSuccess = ( + test: Test, + result: TestResult.TestResult, +) => Promise; + +export interface Reporter { + readonly onTestResult: ( + test: Test, + testResult: TestResult.TestResult, + aggregatedResult: TestResult.AggregatedResult, + ) => Promise | void; + readonly onRunStart: ( + results: TestResult.AggregatedResult, + options: ReporterOnStartOptions, + ) => Promise | void; + readonly onTestStart: (test: Test) => Promise | void; + readonly onRunComplete: ( + contexts: Set, + results: TestResult.AggregatedResult, + ) => Promise | void; + readonly getLastError: () => Error | void; +} + +export type SummaryOptions = { + estimatedTime?: number; + roundTime?: boolean; + width?: number; +}; + +export type TestFramework = ( + globalConfig: Config.GlobalConfig, + config: Config.ProjectConfig, + environment: Environment, + runtime: Runtime, + testPath: string, +) => Promise; + +export type TestRunnerOptions = { + serial: boolean; +}; + +export type TestRunnerContext = { + changedFiles?: Set; +}; + +export type TestRunData = Array<{ + context: Context; + matches: {allTests: number; tests: Array; total: number}; +}>; + +export type TestSchedulerContext = { + firstRun: boolean; + previousSuccess: boolean; + changedFiles?: Set; +}; diff --git a/packages/jest-reporters/src/utils.js b/packages/jest-reporters/src/utils.ts similarity index 92% rename from packages/jest-reporters/src/utils.js rename to packages/jest-reporters/src/utils.ts index f2f17e52f0a2..d078b584fbf0 100644 --- a/packages/jest-reporters/src/utils.js +++ b/packages/jest-reporters/src/utils.ts @@ -3,27 +3,18 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Path, ProjectConfig, GlobalConfig} from 'types/Config'; -import type {AggregatedResult} from 'types/TestResult'; - import path from 'path'; +import {Config, TestResult} from '@jest/types'; import chalk from 'chalk'; import slash from 'slash'; import {pluralize} from 'jest-util'; - -type SummaryOptions = {| - estimatedTime?: number, - roundTime?: boolean, - width?: number, -|}; +import {SummaryOptions} from './types'; const PROGRESS_BAR_WIDTH = 40; -export const printDisplayName = (config: ProjectConfig) => { +export const printDisplayName = (config: Config.ProjectConfig) => { const {displayName} = config; if (displayName) { @@ -37,8 +28,8 @@ export const printDisplayName = (config: ProjectConfig) => { export const trimAndFormatPath = ( pad: number, - config: ProjectConfig | GlobalConfig, - testPath: Path, + config: Config.ProjectConfig | Config.GlobalConfig, + testPath: Config.Path, columns: number, ): string => { const maxLength = columns - pad; @@ -73,28 +64,31 @@ export const trimAndFormatPath = ( }; export const formatTestPath = ( - config: GlobalConfig | ProjectConfig, - testPath: Path, + config: Config.GlobalConfig | Config.ProjectConfig, + testPath: Config.Path, ) => { const {dirname, basename} = relativePath(config, testPath); return slash(chalk.dim(dirname + path.sep) + chalk.bold(basename)); }; export const relativePath = ( - config: GlobalConfig | ProjectConfig, - testPath: Path, + config: Config.GlobalConfig | Config.ProjectConfig, + testPath: Config.Path, ) => { // this function can be called with ProjectConfigs or GlobalConfigs. GlobalConfigs // do not have config.cwd, only config.rootDir. Try using config.cwd, fallback // to config.rootDir. (Also, some unit just use config.rootDir, which is ok) - testPath = path.relative(config.cwd || config.rootDir, testPath); + testPath = path.relative( + (config as Config.ProjectConfig).cwd || config.rootDir, + testPath, + ); const dirname = path.dirname(testPath); const basename = path.basename(testPath); return {basename, dirname}; }; export const getSummary = ( - aggregatedResults: AggregatedResult, + aggregatedResults: TestResult.AggregatedResult, options?: SummaryOptions, ) => { let runTime = (Date.now() - aggregatedResults.startTime) / 1000; @@ -180,7 +174,7 @@ export const getSummary = ( return [suites, tests, snapshots, time].join('\n'); }; -const renderTime = (runTime, estimatedTime, width) => { +const renderTime = (runTime: number, estimatedTime: number, width: number) => { // If we are more than one second over the estimated time, highlight it. const renderedTime = estimatedTime && runTime >= estimatedTime + 1 diff --git a/packages/jest-reporters/src/verbose_reporter.js b/packages/jest-reporters/src/verbose_reporter.ts similarity index 79% rename from packages/jest-reporters/src/verbose_reporter.js rename to packages/jest-reporters/src/verbose_reporter.ts index 4a1ecc646e08..74ac6794f27c 100644 --- a/packages/jest-reporters/src/verbose_reporter.js +++ b/packages/jest-reporters/src/verbose_reporter.ts @@ -3,41 +3,33 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig} from 'types/Config'; -import type { - AggregatedResult, - AssertionResult, - Suite, - TestResult, -} from 'types/TestResult'; -import type {Test} from 'types/TestRunner'; +import {Config, TestResult} from '@jest/types'; import chalk from 'chalk'; import {specialChars} from 'jest-util'; +import {Test} from './types'; import DefaultReporter from './default_reporter'; const {ICONS} = specialChars; export default class VerboseReporter extends DefaultReporter { - _globalConfig: GlobalConfig; + protected _globalConfig: Config.GlobalConfig; - constructor(globalConfig: GlobalConfig) { + constructor(globalConfig: Config.GlobalConfig) { super(globalConfig); this._globalConfig = globalConfig; } static filterTestResults( - testResults: Array, - ): Array { + testResults: Array, + ): Array { return testResults.filter(({status}) => status !== 'pending'); } - static groupTestsBySuites(testResults: Array) { - const root = {suites: [], tests: [], title: ''}; + static groupTestsBySuites(testResults: Array) { + const root: TestResult.Suite = {suites: [], tests: [], title: ''}; testResults.forEach(testResult => { let targetSuite = root; @@ -59,8 +51,8 @@ export default class VerboseReporter extends DefaultReporter { onTestResult( test: Test, - result: TestResult, - aggregatedResults: AggregatedResult, + result: TestResult.TestResult, + aggregatedResults: TestResult.AggregatedResult, ) { super.testFinished(test.context.config, result, aggregatedResults); if (!result.skipped) { @@ -81,12 +73,12 @@ export default class VerboseReporter extends DefaultReporter { super.forceFlushBufferedOutput(); } - _logTestResults(testResults: Array) { + private _logTestResults(testResults: Array) { this._logSuite(VerboseReporter.groupTestsBySuites(testResults), 0); this._logLine(); } - _logSuite(suite: Suite, indentLevel: number) { + private _logSuite(suite: TestResult.Suite, indentLevel: number) { if (suite.title) { this._logLine(suite.title, indentLevel); } @@ -96,7 +88,7 @@ export default class VerboseReporter extends DefaultReporter { suite.suites.forEach(suite => this._logSuite(suite, indentLevel + 1)); } - _getIcon(status: string) { + private _getIcon(status: string) { if (status === 'failed') { return chalk.red(ICONS.failed); } else if (status === 'pending') { @@ -108,13 +100,16 @@ export default class VerboseReporter extends DefaultReporter { } } - _logTest(test: AssertionResult, indentLevel: number) { + private _logTest(test: TestResult.AssertionResult, indentLevel: number) { const status = this._getIcon(test.status); const time = test.duration ? ` (${test.duration.toFixed(0)}ms)` : ''; this._logLine(status + ' ' + chalk.dim(test.title + time), indentLevel); } - _logTests(tests: Array, indentLevel: number) { + private _logTests( + tests: Array, + indentLevel: number, + ) { if (this._globalConfig.expand) { tests.forEach(test => this._logTest(test, indentLevel)); } else { @@ -153,7 +148,7 @@ export default class VerboseReporter extends DefaultReporter { } } - _logSummedTests( + private _logSummedTests( prefix: string, icon: string, count: number, @@ -163,7 +158,7 @@ export default class VerboseReporter extends DefaultReporter { this._logLine(`${icon} ${text}`, indentLevel); } - _logLine(str?: string, indentLevel?: number) { + private _logLine(str?: string, indentLevel?: number) { const indentation = ' '.repeat(indentLevel || 0); this.log(indentation + (str || '')); } diff --git a/packages/jest-reporters/tsconfig.json b/packages/jest-reporters/tsconfig.json new file mode 100644 index 000000000000..5f38de20815b --- /dev/null +++ b/packages/jest-reporters/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "references": [ + {"path": "../jest-environment"}, + {"path": "../jest-haste-map"}, + {"path": "../jest-resolve"}, + {"path": "../jest-runtime"}, + {"path": "../jest-types"}, + {"path": "../jest-util"}, + {"path": "../jest-worker"} + ] +} diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index c2c70c829be9..cffb4ba74e32 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -223,6 +223,15 @@ type NotifyMode = | 'success-change' | 'failure-change'; +type CoverageThreshold = { + [path: string]: { + [key: string]: number; + }; + global: { + [key: string]: number; + }; +}; + export type GlobalConfig = { bail: number; changedSince: string; @@ -238,11 +247,7 @@ export type GlobalConfig = { coverageDirectory: string; coveragePathIgnorePatterns?: Array; coverageReporters: Array; - coverageThreshold: { - global: { - [key: string]: number; - }; - }; + coverageThreshold: CoverageThreshold; detectLeaks: boolean; detectOpenHandles: boolean; enabledTestsMap: diff --git a/yarn.lock b/yarn.lock index 2a165b6d6a2f..35dfdc279ddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1732,13 +1732,6 @@ resolved "https://registry.yarnpkg.com/@types/natural-compare/-/natural-compare-1.4.0.tgz#b3c54f7edc339758d573c5dcac7808c58cf8c31e" integrity sha512-bNtBj6AF1F90jp54KRPOrYfilGNfPr2kpaUN7rMJjauAtfGBXzT/T/REZN6jb4qUs9FTxU37kir3Nrn5WsTUDw== -"@types/node-notifier@^0.0.28": - version "0.0.28" - resolved "https://registry.yarnpkg.com/@types/node-notifier/-/node-notifier-0.0.28.tgz#86ba3d3aa8d918352cc3191d88de328b20dc93c1" - integrity sha1-hro9OqjZGDUswxkdiN4yiyDck8E= - dependencies: - "@types/node" "*" - "@types/node@*", "@types/node@^11.9.6": version "11.9.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.6.tgz#c632bbcc780a1349673a6e2e9b9dfa8c369d3c74"