From 07a9d43c009f30d5af609827a70b0a2115e7823d Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 9 Apr 2022 14:04:38 +0300 Subject: [PATCH 01/14] feat: pass reporterContext to Reporter constructor --- .eslintrc.cjs | 2 - docs/Configuration.md | 57 +++++-------- .../customReporters.test.ts.snap | 24 +++++- .../reporters/IncompleteReporter.js | 2 +- .../reporters/TestReporter.js | 14 ++-- packages/jest-core/src/ReporterDispatcher.ts | 13 ++- packages/jest-core/src/TestScheduler.ts | 84 ++++++++----------- packages/jest-core/src/runJest.ts | 12 ++- packages/jest-reporters/package.json | 3 +- packages/jest-reporters/src/BaseReporter.ts | 6 +- .../jest-reporters/src/CoverageReporter.ts | 41 ++++----- packages/jest-reporters/src/CoverageWorker.ts | 16 ++-- .../jest-reporters/src/DefaultReporter.ts | 3 +- .../src/GitHubActionsReporter.ts | 9 +- packages/jest-reporters/src/NotifyReporter.ts | 25 +++--- packages/jest-reporters/src/Status.ts | 3 +- .../jest-reporters/src/SummaryReporter.ts | 14 ++-- .../jest-reporters/src/VerboseReporter.ts | 2 +- .../src/__tests__/CoverageWorker.test.js | 3 +- .../src/__tests__/NotifyReporter.test.ts | 24 +++--- packages/jest-reporters/src/index.ts | 9 +- packages/jest-reporters/src/types.ts | 68 ++++----------- packages/jest-reporters/src/utils.ts | 4 +- packages/jest-reporters/tsconfig.json | 1 - yarn.lock | 1 - 25 files changed, 192 insertions(+), 248 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8ee5a68dfe67..6c97d9abc82f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -101,8 +101,6 @@ module.exports = { 'packages/expect/src/print.ts', 'packages/expect/src/toThrowMatchers.ts', 'packages/expect-utils/src/utils.ts', - 'packages/jest-core/src/ReporterDispatcher.ts', - 'packages/jest-core/src/TestScheduler.ts', 'packages/jest-core/src/collectHandles.ts', 'packages/jest-core/src/plugins/UpdateSnapshotsInteractive.ts', 'packages/jest-haste-map/src/index.ts', diff --git a/docs/Configuration.md b/docs/Configuration.md index ab5dc416f632..b7459afc498b 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -822,73 +822,62 @@ When using multi-project runner, it's recommended to add a `displayName` for eac Default: `undefined` -Use this configuration option to add custom reporters to Jest. A custom reporter is a class that implements `onRunStart`, `onTestStart`, `onTestResult`, `onRunComplete` methods that will be called when any of those events occurs. - -If custom reporters are specified, the default Jest reporters will be overridden. To keep default reporters, `default` can be passed as a module name. - -This will override default reporters: +Use this configuration option to add reporters to Jest. Optionally, a reporter can be configured by passing options object as a second argument of a tuple: ```json { - "reporters": ["/my-custom-reporter.js"] + "reporters": [ + "default", + ["/custom-reporter.js", {"banana": "yes", "pineapple": "no"}] + ] } ``` -This will use custom reporter in addition to default reporters that Jest provides: +:::tip -```json -{ - "reporters": ["default", "/my-custom-reporter.js"] -} -``` +Take a look at a list of [awesome reporters](https://github.com/jest-community/awesome-jest#reporters) from Awesome Jest. -Additionally, custom reporters can be configured by passing an `options` object as a second argument: +If custom reporters are specified, the default Jest reporters will be overridden. If you wish to keep them, `'default'` can be passed as a module name: ```json { "reporters": [ "default", - ["/my-custom-reporter.js", {"banana": "yes", "pineapple": "no"}] + ["jest-junit", {"outputName": "junit-report.xml", "suiteName": "some-name"}] ] } ``` -Custom reporter modules must define a class that takes a `GlobalConfig` and reporter options as constructor arguments: +::: -Example reporter: +Custom reporter module must export a class that takes `globalConfig`, `reporterConfig` and `reporterContext` as constructor arguments and implements at least `onRunComplete()` method (for the full list of methods and argument types see `Reporter` interface in [packages/jest-reporters/src/types.ts](https://github.com/facebook/jest/blob/main/packages/jest-reporters/src/types.ts)): -```js title="my-custom-reporter.js" -class MyCustomReporter { - constructor(globalConfig, options) { +```js title="custom-reporter.js" +class CustomReporter { + constructor(globalConfig, reporterOptions, reporterContext) { this._globalConfig = globalConfig; - this._options = options; + this._options = reporterOptions; + this._context = reporterContext; } - onRunComplete(contexts, results) { + onRunComplete(testContexts, results) { console.log('Custom reporter output:'); console.log('GlobalConfig: ', this._globalConfig); console.log('Options: ', this._options); + console.log('Context: ', this._context); } -} - -module.exports = MyCustomReporter; -// or export default MyCustomReporter; -``` - -Custom reporters can also force Jest to exit with non-0 code by returning an Error from `getLastError()` methods -```js -class MyCustomReporter { - // ... + // Optionally, reporters can force Jest to exit with non zero code by returning + // an `Error` from `getLastError()` method. getLastError() { if (this._shouldFail) { - return new Error('my-custom-reporter.js reported an error'); + return new Error('Custom error reported!'); } } } -``` -For the full list of methods and argument types see `Reporter` interface in [packages/jest-reporters/src/types.ts](https://github.com/facebook/jest/blob/main/packages/jest-reporters/src/types.ts) +module.exports = CustomReporter; +``` ### `resetMocks` \[boolean] diff --git a/e2e/__tests__/__snapshots__/customReporters.test.ts.snap b/e2e/__tests__/__snapshots__/customReporters.test.ts.snap index 43299913cba8..7506630b07d2 100644 --- a/e2e/__tests__/__snapshots__/customReporters.test.ts.snap +++ b/e2e/__tests__/__snapshots__/customReporters.test.ts.snap @@ -27,7 +27,11 @@ Object { "called": true, "path": false, }, - "options": Object { + "reporterContext": Object { + "firstRun": true, + "previousSuccess": true, + }, + "reporterOptions": Object { "christoph": "pojer", "dmitrii": "abramov", "hello": "world", @@ -55,7 +59,11 @@ Object { "called": true, "path": false, }, - "options": Object { + "reporterContext": Object { + "firstRun": true, + "previousSuccess": true, + }, + "reporterOptions": Object { "christoph": "pojer", "dmitrii": "abramov", "hello": "world", @@ -97,7 +105,11 @@ Object { "called": true, "path": false, }, - "options": Object {}, + "reporterContext": Object { + "firstRun": true, + "previousSuccess": true, + }, + "reporterOptions": Object {}, } `; @@ -146,7 +158,11 @@ exports[`Custom Reporters Integration valid array format for adding reporters 1` "called": true, "path": false }, - "options": { + "reporterContext": { + "firstRun": true, + "previousSuccess": true + }, + "reporterOptions": { "Aaron Abramov": "Awesome" } }" diff --git a/e2e/custom-reporters/reporters/IncompleteReporter.js b/e2e/custom-reporters/reporters/IncompleteReporter.js index 760e101ac74c..0cfba83192a1 100644 --- a/e2e/custom-reporters/reporters/IncompleteReporter.js +++ b/e2e/custom-reporters/reporters/IncompleteReporter.js @@ -15,7 +15,7 @@ * This only implements one method onRunComplete which should be called */ class IncompleteReporter { - onRunComplete(contexts, results) { + onRunComplete(testContexts, results) { console.log('onRunComplete is called'); console.log(`Passed Tests: ${results.numPassedTests}`); console.log(`Failed Tests: ${results.numFailedTests}`); diff --git a/e2e/custom-reporters/reporters/TestReporter.js b/e2e/custom-reporters/reporters/TestReporter.js index 5cd404b766b2..300fb5478bd3 100644 --- a/e2e/custom-reporters/reporters/TestReporter.js +++ b/e2e/custom-reporters/reporters/TestReporter.js @@ -15,8 +15,9 @@ * to get the output. */ class TestReporter { - constructor(globalConfig, options) { - this._options = options; + constructor(globalConfig, reporterOptions, reporterContext) { + this._context = reporterContext; + this._options = reporterOptions; /** * statsCollected property @@ -30,7 +31,8 @@ class TestReporter { onRunStart: {}, onTestResult: {times: 0}, onTestStart: {}, - options, + reporterContext, + reporterOptions, }; } @@ -66,7 +68,7 @@ class TestReporter { onRunStart.options = typeof options; } - onRunComplete(contexts, results) { + onRunComplete(testContexts, results) { const onRunComplete = this._statsCollected.onRunComplete; onRunComplete.called = true; @@ -75,9 +77,9 @@ class TestReporter { onRunComplete.numFailedTests = results.numFailedTests; onRunComplete.numTotalTests = results.numTotalTests; - if (this._statsCollected.options.maxWorkers) { + if (this._statsCollected.reporterOptions.maxWorkers) { // Since it's a different number on different machines. - this._statsCollected.options.maxWorkers = '<>'; + this._statsCollected.reporterOptions.maxWorkers = '<>'; } // The Final Call process.stdout.write(JSON.stringify(this._statsCollected, null, 4)); diff --git a/packages/jest-core/src/ReporterDispatcher.ts b/packages/jest-core/src/ReporterDispatcher.ts index 992925cdd65b..a70691f5ddcf 100644 --- a/packages/jest-core/src/ReporterDispatcher.ts +++ b/packages/jest-core/src/ReporterDispatcher.ts @@ -5,16 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable local/ban-types-eventually */ - import type {Reporter, ReporterOnStartOptions} from '@jest/reporters'; import type { AggregatedResult, Test, TestCaseResult, + TestContext, TestResult, } from '@jest/test-result'; -import type {Context} from 'jest-runtime'; +import type {ReporterConstructor} from './TestScheduler'; export default class ReporterDispatcher { private _reporters: Array; @@ -27,9 +26,9 @@ export default class ReporterDispatcher { this._reporters.push(reporter); } - unregister(ReporterClass: Function): void { + unregister(reporterConstructor: ReporterConstructor): void { this._reporters = this._reporters.filter( - reporter => !(reporter instanceof ReporterClass), + reporter => !(reporter instanceof reporterConstructor), ); } @@ -82,12 +81,12 @@ export default class ReporterDispatcher { } async onRunComplete( - contexts: Set, + testContexts: Set, results: AggregatedResult, ): Promise { for (const reporter of this._reporters) { if (reporter.onRunComplete) { - await reporter.onRunComplete(contexts, results); + await reporter.onRunComplete(testContexts, results); } } } diff --git a/packages/jest-core/src/TestScheduler.ts b/packages/jest-core/src/TestScheduler.ts index 9cceb5fb1bdd..b6e552caed9e 100644 --- a/packages/jest-core/src/TestScheduler.ts +++ b/packages/jest-core/src/TestScheduler.ts @@ -5,15 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable local/ban-types-eventually */ - import chalk = require('chalk'); import exit = require('exit'); import { CoverageReporter, DefaultReporter, + BaseReporter as JestReporter, NotifyReporter, Reporter, + ReporterContext, SummaryReporter, VerboseReporter, } from '@jest/reporters'; @@ -21,6 +21,7 @@ import { AggregatedResult, SerializableError, Test, + TestContext, TestResult, addResult, buildFailureTestResult, @@ -30,7 +31,6 @@ import {createScriptTransformer} from '@jest/transform'; import type {Config} from '@jest/types'; import {formatExecError} from 'jest-message-util'; import type {JestTestRunner, TestRunnerContext} from 'jest-runner'; -import type {Context} from 'jest-runtime'; import { buildSnapshotResolver, cleanup as cleanupSnapshots, @@ -40,27 +40,30 @@ import type {TestWatcher} from 'jest-watcher'; import ReporterDispatcher from './ReporterDispatcher'; import {shouldRunInBand} from './testSchedulerHelper'; +export type ReporterConstructor = new ( + globalConfig: Config.GlobalConfig, + reporterConfig: Record, + reporterContext: ReporterContext, +) => JestReporter; + type TestRunnerConstructor = new ( globalConfig: Config.GlobalConfig, - context: TestRunnerContext, + testRunnerContext: TestRunnerContext, ) => JestTestRunner; -export type TestSchedulerOptions = { - startRun: (globalConfig: Config.GlobalConfig) => void; -}; export type TestSchedulerContext = { firstRun: boolean; previousSuccess: boolean; changedFiles?: Set; sourcesRelatedToTestsInChangedFiles?: Set; + startRun?: (globalConfig: Config.GlobalConfig) => void; }; export async function createTestScheduler( globalConfig: Config.GlobalConfig, - options: TestSchedulerOptions, context: TestSchedulerContext, ): Promise { - const scheduler = new TestScheduler(globalConfig, options, context); + const scheduler = new TestScheduler(globalConfig, context); await scheduler._setupReporters(); @@ -68,28 +71,25 @@ export async function createTestScheduler( } class TestScheduler { + private readonly _context: TestSchedulerContext; private readonly _dispatcher: ReporterDispatcher; private readonly _globalConfig: Config.GlobalConfig; - private readonly _options: TestSchedulerOptions; - private readonly _context: TestSchedulerContext; constructor( globalConfig: Config.GlobalConfig, - options: TestSchedulerOptions, context: TestSchedulerContext, ) { + this._context = context; this._dispatcher = new ReporterDispatcher(); this._globalConfig = globalConfig; - this._options = options; - this._context = context; } addReporter(reporter: Reporter): void { this._dispatcher.register(reporter); } - removeReporter(ReporterClass: Function): void { - this._dispatcher.unregister(ReporterClass); + removeReporter(reporter: ReporterConstructor): void { + this._dispatcher.unregister(reporter); } async scheduleTests( @@ -100,9 +100,9 @@ class TestScheduler { this._dispatcher, ); const timings: Array = []; - const contexts = new Set(); + const testContexts = new Set(); tests.forEach(test => { - contexts.add(test.context); + testContexts.add(test.context); if (test.duration) { timings.push(test.duration); } @@ -153,7 +153,7 @@ class TestScheduler { testResult, aggregatedResults, ); - return this._bailIfNeeded(contexts, aggregatedResults, watcher); + return this._bailIfNeeded(testContexts, aggregatedResults, watcher); }; const onFailure = async (test: Test, error: SerializableError) => { @@ -177,7 +177,7 @@ class TestScheduler { const updateSnapshotState = async () => { const contextsWithSnapshotResolvers = await Promise.all( - Array.from(contexts).map( + Array.from(testContexts).map( async context => [context, await buildSnapshotResolver(context.config)] as const, ), @@ -212,9 +212,9 @@ class TestScheduler { }); const testRunners: Record = Object.create(null); - const contextsByTestRunner = new WeakMap(); + const contextsByTestRunner = new WeakMap(); await Promise.all( - Array.from(contexts).map(async context => { + Array.from(testContexts).map(async context => { const {config} = context; if (!testRunners[config.runner]) { const transformer = await createScriptTransformer(config); @@ -290,7 +290,7 @@ class TestScheduler { await updateSnapshotState(); aggregatedResults.wasInterrupted = watcher.isInterrupted(); - await this._dispatcher.onRunComplete(contexts, aggregatedResults); + await this._dispatcher.onRunComplete(testContexts, aggregatedResults); const anyTestFailures = !( aggregatedResults.numFailedTests === 0 && @@ -350,23 +350,11 @@ class TestScheduler { } if (!isDefault && collectCoverage) { - this.addReporter( - new CoverageReporter(this._globalConfig, { - changedFiles: this._context?.changedFiles, - sourcesRelatedToTestsInChangedFiles: - this._context?.sourcesRelatedToTestsInChangedFiles, - }), - ); + this.addReporter(new CoverageReporter(this._globalConfig, this._context)); } if (notify) { - this.addReporter( - new NotifyReporter( - this._globalConfig, - this._options.startRun, - this._context, - ), - ); + this.addReporter(new NotifyReporter(this._globalConfig, this._context)); } if (reporters && Array.isArray(reporters)) { @@ -382,13 +370,7 @@ class TestScheduler { ); if (collectCoverage) { - this.addReporter( - new CoverageReporter(this._globalConfig, { - changedFiles: this._context?.changedFiles, - sourcesRelatedToTestsInChangedFiles: - this._context?.sourcesRelatedToTestsInChangedFiles, - }), - ); + this.addReporter(new CoverageReporter(this._globalConfig, this._context)); } this.addReporter(new SummaryReporter(this._globalConfig)); @@ -403,8 +385,10 @@ class TestScheduler { if (path === 'default') continue; try { - const Reporter = await requireOrImportModule(path, true); - this.addReporter(new Reporter(this._globalConfig, options)); + const Reporter: ReporterConstructor = await requireOrImportModule(path); + this.addReporter( + new Reporter(this._globalConfig, options, this._context), + ); } catch (error: any) { error.message = `An error occurred while adding the reporter at path "${chalk.bold( path, @@ -419,11 +403,11 @@ class TestScheduler { * to make dealing with them less painful. */ private _getReporterProps(reporter: string | Config.ReporterConfig): { - path: string; options: Record; + path: string; } { if (typeof reporter === 'string') { - return {options: this._options, path: reporter}; + return {options: {}, path: reporter}; } else if (Array.isArray(reporter)) { const [path, options] = reporter; return {options, path}; @@ -433,7 +417,7 @@ class TestScheduler { } private async _bailIfNeeded( - contexts: Set, + testContexts: Set, aggregatedResults: AggregatedResult, watcher: TestWatcher, ): Promise { @@ -447,7 +431,7 @@ class TestScheduler { } try { - await this._dispatcher.onRunComplete(contexts, aggregatedResults); + await this._dispatcher.onRunComplete(testContexts, aggregatedResults); } finally { const exitCode = this._globalConfig.testFailureExitCode; exit(exitCode); diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index 977cd07addfa..0ebc5e1a996b 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -23,8 +23,7 @@ import type {ChangedFiles, ChangedFilesPromise} from 'jest-changed-files'; import Resolver from 'jest-resolve'; import type {Context} from 'jest-runtime'; import {requireOrImportModule, tryRealpath} from 'jest-util'; -import {JestHook, JestHookEmitter} from 'jest-watcher'; -import type {TestWatcher} from 'jest-watcher'; +import {JestHook, JestHookEmitter, TestWatcher} from 'jest-watcher'; import type FailedTestsCache from './FailedTestsCache'; import SearchSource from './SearchSource'; import {TestSchedulerContext, createTestScheduler} from './TestScheduler'; @@ -280,11 +279,10 @@ export default async function runJest({ } } - const scheduler = await createTestScheduler( - globalConfig, - {startRun}, - testSchedulerContext, - ); + const scheduler = await createTestScheduler(globalConfig, { + startRun, + ...testSchedulerContext, + }); const results = await scheduler.scheduleTests(allTests, testWatcher); diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index 6f1d4006813b..7a84a37f6312 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -28,8 +28,6 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^28.0.0-alpha.8", - "jest-resolve": "^28.0.0-alpha.8", "jest-util": "^28.0.0-alpha.8", "jest-worker": "^28.0.0-alpha.8", "slash": "^3.0.0", @@ -49,6 +47,7 @@ "@types/istanbul-lib-source-maps": "^4.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node-notifier": "^8.0.0", + "jest-resolve": "^28.0.0-alpha.8", "mock-fs": "^5.1.2", "strip-ansi": "^6.0.0" }, diff --git a/packages/jest-reporters/src/BaseReporter.ts b/packages/jest-reporters/src/BaseReporter.ts index 4520c3e7146c..8f526555b12d 100644 --- a/packages/jest-reporters/src/BaseReporter.ts +++ b/packages/jest-reporters/src/BaseReporter.ts @@ -7,11 +7,13 @@ import type { AggregatedResult, + Test, TestCaseResult, + TestContext, TestResult, } from '@jest/test-result'; import {preRunMessage} from 'jest-util'; -import type {Context, Reporter, ReporterOnStartOptions, Test} from './types'; +import type {Reporter, ReporterOnStartOptions} from './types'; const {remove: preRunMessageRemove} = preRunMessage; @@ -40,7 +42,7 @@ export default class BaseReporter implements Reporter { onTestStart(_test?: Test): void {} onRunComplete( - _contexts?: Set, + _testContexts?: Set, _aggregatedResults?: AggregatedResult, ): Promise | void {} diff --git a/packages/jest-reporters/src/CoverageReporter.ts b/packages/jest-reporters/src/CoverageReporter.ts index 2390029f90b6..2aa808d97e75 100644 --- a/packages/jest-reporters/src/CoverageReporter.ts +++ b/packages/jest-reporters/src/CoverageReporter.ts @@ -19,6 +19,8 @@ import v8toIstanbul = require('v8-to-istanbul'); import type { AggregatedResult, RuntimeTransformResult, + Test, + TestContext, TestResult, V8CoverageResult, } from '@jest/test-result'; @@ -27,12 +29,7 @@ import {clearLine, isInteractive} from 'jest-util'; import {Worker} from 'jest-worker'; import BaseReporter from './BaseReporter'; import getWatermarks from './getWatermarks'; -import type { - Context, - CoverageReporterOptions, - CoverageWorker, - Test, -} from './types'; +import type {CoverageWorker, ReporterContext} from './types'; // This is fixed in a newer versions of source-map, but our dependencies are still stuck on old versions interface FixedRawSourceMap extends Omit { @@ -44,24 +41,21 @@ const FAIL_COLOR = chalk.bold.red; const RUNNING_TEST_COLOR = chalk.bold.dim; export default class CoverageReporter extends BaseReporter { + private _context: ReporterContext; private _coverageMap: istanbulCoverage.CoverageMap; private _globalConfig: Config.GlobalConfig; private _sourceMapStore: libSourceMaps.MapStore; - private _options: CoverageReporterOptions; private _v8CoverageResults: Array; static readonly filename = __filename; - constructor( - globalConfig: Config.GlobalConfig, - options?: CoverageReporterOptions, - ) { + constructor(globalConfig: Config.GlobalConfig, context: ReporterContext) { super(); + this._context = context; this._coverageMap = istanbulCoverage.createCoverageMap({}); this._globalConfig = globalConfig; this._sourceMapStore = libSourceMaps.createSourceMapStore(); this._v8CoverageResults = []; - this._options = options || {}; } override onTestResult(_test: Test, testResult: TestResult): void { @@ -76,10 +70,10 @@ export default class CoverageReporter extends BaseReporter { } override async onRunComplete( - contexts: Set, + testContexts: Set, aggregatedResults: AggregatedResult, ): Promise { - await this._addUntestedFiles(contexts); + await this._addUntestedFiles(testContexts); const {map, reportContext} = await this._getCoverageResult(); try { @@ -114,10 +108,12 @@ export default class CoverageReporter extends BaseReporter { this._checkThreshold(map); } - private async _addUntestedFiles(contexts: Set): Promise { + private async _addUntestedFiles( + testContexts: Set, + ): Promise { const files: Array<{config: Config.ProjectConfig; path: string}> = []; - contexts.forEach(context => { + testContexts.forEach(context => { const config = context.config; if ( this._globalConfig.collectCoverageFrom && @@ -175,16 +171,15 @@ export default class CoverageReporter extends BaseReporter { try { const result = await worker.worker({ config, - globalConfig: this._globalConfig, - options: { - ...this._options, + context: { changedFiles: - this._options.changedFiles && - Array.from(this._options.changedFiles), + this._context.changedFiles && + Array.from(this._context.changedFiles), sourcesRelatedToTestsInChangedFiles: - this._options.sourcesRelatedToTestsInChangedFiles && - Array.from(this._options.sourcesRelatedToTestsInChangedFiles), + this._context.sourcesRelatedToTestsInChangedFiles && + Array.from(this._context.sourcesRelatedToTestsInChangedFiles), }, + globalConfig: this._globalConfig, path: filename, }); diff --git a/packages/jest-reporters/src/CoverageWorker.ts b/packages/jest-reporters/src/CoverageWorker.ts index b7f9741c0320..e369636041cc 100644 --- a/packages/jest-reporters/src/CoverageWorker.ts +++ b/packages/jest-reporters/src/CoverageWorker.ts @@ -11,17 +11,15 @@ import type {Config} from '@jest/types'; import generateEmptyCoverage, { CoverageWorkerResult, } from './generateEmptyCoverage'; -import type {CoverageReporterSerializedOptions} from './types'; +import type {ReporterContextSerialized} from './types'; export type CoverageWorkerData = { - globalConfig: Config.GlobalConfig; config: Config.ProjectConfig; + context: ReporterContextSerialized; + globalConfig: Config.GlobalConfig; path: string; - options?: CoverageReporterSerializedOptions; }; -export type {CoverageWorkerResult}; - // Make sure uncaught errors are logged before we exit. process.on('uncaughtException', err => { console.error(err.stack); @@ -32,15 +30,15 @@ export function worker({ config, globalConfig, path, - options, + context, }: CoverageWorkerData): Promise { return generateEmptyCoverage( fs.readFileSync(path, 'utf8'), path, globalConfig, config, - options?.changedFiles && new Set(options.changedFiles), - options?.sourcesRelatedToTestsInChangedFiles && - new Set(options.sourcesRelatedToTestsInChangedFiles), + context.changedFiles && new Set(context.changedFiles), + context.sourcesRelatedToTestsInChangedFiles && + new Set(context.sourcesRelatedToTestsInChangedFiles), ); } diff --git a/packages/jest-reporters/src/DefaultReporter.ts b/packages/jest-reporters/src/DefaultReporter.ts index 3af42784283e..69d8c867e0ce 100644 --- a/packages/jest-reporters/src/DefaultReporter.ts +++ b/packages/jest-reporters/src/DefaultReporter.ts @@ -9,6 +9,7 @@ import chalk = require('chalk'); import {getConsoleOutput} from '@jest/console'; import type { AggregatedResult, + Test, TestCaseResult, TestResult, } from '@jest/test-result'; @@ -18,7 +19,7 @@ import BaseReporter from './BaseReporter'; import Status from './Status'; import getResultHeader from './getResultHeader'; import getSnapshotStatus from './getSnapshotStatus'; -import type {ReporterOnStartOptions, Test} from './types'; +import type {ReporterOnStartOptions} from './types'; type write = NodeJS.WriteStream['write']; type FlushBufferedOutput = () => void; diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index b9188e701b7b..7a4deffb6044 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -6,9 +6,12 @@ */ import stripAnsi = require('strip-ansi'); -import type {AggregatedResult, TestResult} from '@jest/test-result'; +import type { + AggregatedResult, + TestContext, + TestResult, +} from '@jest/test-result'; import BaseReporter from './BaseReporter'; -import type {Context} from './types'; const lineAndColumnInStackTrace = /^.*?:([0-9]+):([0-9]+).*$/; @@ -24,7 +27,7 @@ function replaceEntities(s: string): string { export default class GitHubActionsReporter extends BaseReporter { override onRunComplete( - _contexts?: Set, + _testContexts?: Set, aggregatedResults?: AggregatedResult, ): void { const messages = getMessages(aggregatedResults?.testResults); diff --git a/packages/jest-reporters/src/NotifyReporter.ts b/packages/jest-reporters/src/NotifyReporter.ts index 63d8a218ea2b..337022af8916 100644 --- a/packages/jest-reporters/src/NotifyReporter.ts +++ b/packages/jest-reporters/src/NotifyReporter.ts @@ -8,11 +8,11 @@ import * as path from 'path'; import * as util from 'util'; import exit = require('exit'); -import type {AggregatedResult} from '@jest/test-result'; +import type {AggregatedResult, TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; import {pluralize} from 'jest-util'; import BaseReporter from './BaseReporter'; -import type {Context, TestSchedulerContext} from './types'; +import type {ReporterContext} from './types'; const isDarwin = process.platform === 'darwin'; @@ -20,31 +20,25 @@ const icon = path.resolve(__dirname, '../assets/jest_logo.png'); export default class NotifyReporter extends BaseReporter { private _notifier = loadNotifier(); - private _startRun: (globalConfig: Config.GlobalConfig) => unknown; private _globalConfig: Config.GlobalConfig; - private _context: TestSchedulerContext; + private _context: ReporterContext; static readonly filename = __filename; - constructor( - globalConfig: Config.GlobalConfig, - startRun: (globalConfig: Config.GlobalConfig) => unknown, - context: TestSchedulerContext, - ) { + constructor(globalConfig: Config.GlobalConfig, context: ReporterContext) { super(); this._globalConfig = globalConfig; - this._startRun = startRun; this._context = context; } override onRunComplete( - contexts: Set, + testContexts: Set, result: AggregatedResult, ): void { const success = result.numFailedTests === 0 && result.numRuntimeErrorTestSuites === 0; - const firstContext = contexts.values().next(); + const firstContext = testContexts.values().next(); const hasteFS = firstContext && firstContext.value && firstContext.value.hasteFS; @@ -145,8 +139,11 @@ export default class NotifyReporter extends BaseReporter { exit(0); return; } - if (metadata.activationValue === restartAnswer) { - this._startRun(this._globalConfig); + if ( + metadata.activationValue === restartAnswer && + this._context.startRun + ) { + this._context.startRun(this._globalConfig); } }, ); diff --git a/packages/jest-reporters/src/Status.ts b/packages/jest-reporters/src/Status.ts index ba57f2883418..e357b0d9ddc0 100644 --- a/packages/jest-reporters/src/Status.ts +++ b/packages/jest-reporters/src/Status.ts @@ -9,11 +9,12 @@ import chalk = require('chalk'); import stringLength = require('string-length'); import type { AggregatedResult, + Test, TestCaseResult, TestResult, } from '@jest/test-result'; import type {Config} from '@jest/types'; -import type {ReporterOnStartOptions, Test} from './types'; +import type {ReporterOnStartOptions} from './types'; import { getSummary, printDisplayName, diff --git a/packages/jest-reporters/src/SummaryReporter.ts b/packages/jest-reporters/src/SummaryReporter.ts index 36e59aae3bb3..4c7ba39d965e 100644 --- a/packages/jest-reporters/src/SummaryReporter.ts +++ b/packages/jest-reporters/src/SummaryReporter.ts @@ -6,13 +6,17 @@ */ import chalk = require('chalk'); -import type {AggregatedResult, SnapshotSummary} from '@jest/test-result'; +import type { + AggregatedResult, + SnapshotSummary, + TestContext, +} from '@jest/test-result'; import type {Config} from '@jest/types'; import {testPathPatternToRegExp} from 'jest-util'; import BaseReporter from './BaseReporter'; import getResultHeader from './getResultHeader'; import getSnapshotSummary from './getSnapshotSummary'; -import type {Context, ReporterOnStartOptions} from './types'; +import type {ReporterOnStartOptions} from './types'; import {getSummary} from './utils'; const TEST_SUMMARY_THRESHOLD = 20; @@ -79,7 +83,7 @@ export default class SummaryReporter extends BaseReporter { } override onRunComplete( - contexts: Set, + testContexts: Set, aggregatedResults: AggregatedResult, ): void { const {numTotalTestSuites, testResults, wasInterrupted} = aggregatedResults; @@ -111,7 +115,7 @@ export default class SummaryReporter extends BaseReporter { message += `\n${ wasInterrupted ? chalk.bold.red('Test run was interrupted.') - : this._getTestSummary(contexts, this._globalConfig) + : this._getTestSummary(testContexts, this._globalConfig) }`; } this.log(message); @@ -188,7 +192,7 @@ export default class SummaryReporter extends BaseReporter { } private _getTestSummary( - contexts: Set, + contexts: Set, globalConfig: Config.GlobalConfig, ) { const getMatchingTestsInfo = () => { diff --git a/packages/jest-reporters/src/VerboseReporter.ts b/packages/jest-reporters/src/VerboseReporter.ts index 919767dbdf9b..f7675355fa45 100644 --- a/packages/jest-reporters/src/VerboseReporter.ts +++ b/packages/jest-reporters/src/VerboseReporter.ts @@ -10,12 +10,12 @@ import type { AggregatedResult, AssertionResult, Suite, + Test, TestResult, } from '@jest/test-result'; import type {Config} from '@jest/types'; import {formatTime, specialChars} from 'jest-util'; import DefaultReporter from './DefaultReporter'; -import type {Test} from './types'; const {ICONS} = specialChars; diff --git a/packages/jest-reporters/src/__tests__/CoverageWorker.test.js b/packages/jest-reporters/src/__tests__/CoverageWorker.test.js index d8b83408f52e..e83b1f13287c 100644 --- a/packages/jest-reporters/src/__tests__/CoverageWorker.test.js +++ b/packages/jest-reporters/src/__tests__/CoverageWorker.test.js @@ -11,7 +11,8 @@ jest.mock('graceful-fs').mock('../generateEmptyCoverage'); const globalConfig = {collectCoverage: true}; const config = {}; -const workerOptions = {config, globalConfig, path: 'banana.js'}; +const context = {}; +const workerOptions = {config, context, globalConfig, path: 'banana.js'}; let fs; let generateEmptyCoverage; diff --git a/packages/jest-reporters/src/__tests__/NotifyReporter.test.ts b/packages/jest-reporters/src/__tests__/NotifyReporter.test.ts index 3e9a22ea03fa..bc59000d41f8 100644 --- a/packages/jest-reporters/src/__tests__/NotifyReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/NotifyReporter.test.ts @@ -5,20 +5,22 @@ * LICENSE file in the root directory of this source tree. */ -import type {AggregatedResult} from '@jest/test-result'; +import type {AggregatedResult, TestContext} from '@jest/test-result'; import {makeGlobalConfig} from '@jest/test-utils'; import type {Config} from '@jest/types'; import Resolver from 'jest-resolve'; import NotifyReporter from '../NotifyReporter'; +import type {ReporterContext} from '../types'; jest.mock('../DefaultReporter'); jest.mock('node-notifier', () => ({ notify: jest.fn(), })); -const initialContext = { +const initialContext: ReporterContext = { firstRun: true, previousSuccess: false, + startRun: () => {}, }; const aggregatedResultsSuccess = { @@ -78,20 +80,20 @@ const testModes = ({ Partial>) => { const notify = require('node-notifier'); - const config = makeGlobalConfig({notify: true, notifyMode, rootDir}); + const globalConfig = makeGlobalConfig({notify: true, notifyMode, rootDir}); let previousContext = initialContext; arl.forEach((ar, i) => { - const newContext = Object.assign(previousContext, { + const newContext: ReporterContext = Object.assign(previousContext, { firstRun: i === 0, previousSuccess: previousContext.previousSuccess, }); - const reporter = new NotifyReporter(config, () => {}, newContext); + const reporter = new NotifyReporter(globalConfig, newContext); previousContext = newContext; - const contexts = new Set(); + const testContexts = new Set(); if (moduleName != null) { - contexts.add({ + testContexts.add({ hasteFS: { getModuleName() { return moduleName; @@ -101,10 +103,10 @@ const testModes = ({ return ['package.json']; }, }, - }); + } as unknown as TestContext); } - reporter.onRunComplete(contexts, ar); + reporter.onRunComplete(testContexts, ar); if (ar.numTotalTests === 0) { expect(notify.notify).not.toHaveBeenCalled(); @@ -214,12 +216,12 @@ describe('node-notifier is an optional dependency', () => { }); const ctor = () => { - const config = makeGlobalConfig({ + const globalConfig = makeGlobalConfig({ notify: true, notifyMode: 'success', rootDir: 'some-test', }); - return new NotifyReporter(config, () => {}, initialContext); + return new NotifyReporter(globalConfig, initialContext); }; test('without node-notifier uses mock function that throws an error', () => { diff --git a/packages/jest-reporters/src/index.ts b/packages/jest-reporters/src/index.ts index 3f8089c3b689..7831946d7a51 100644 --- a/packages/jest-reporters/src/index.ts +++ b/packages/jest-reporters/src/index.ts @@ -14,12 +14,6 @@ import { trimAndFormatPath, } from './utils'; -export type {Config} from '@jest/types'; -export type { - AggregatedResult, - SnapshotSummary, - TestResult, -} from '@jest/test-result'; export {default as BaseReporter} from './BaseReporter'; export {default as CoverageReporter} from './CoverageReporter'; export {default as DefaultReporter} from './DefaultReporter'; @@ -28,11 +22,10 @@ export {default as SummaryReporter} from './SummaryReporter'; export {default as VerboseReporter} from './VerboseReporter'; export {default as GitHubActionsReporter} from './GitHubActionsReporter'; export type { - Context, Reporter, ReporterOnStartOptions, + ReporterContext, SummaryOptions, - Test, } from './types'; export const utils = { formatTestPath, diff --git a/packages/jest-reporters/src/types.ts b/packages/jest-reporters/src/types.ts index 693454d007af..453f151881e5 100644 --- a/packages/jest-reporters/src/types.ts +++ b/packages/jest-reporters/src/types.ts @@ -7,13 +7,12 @@ import type { AggregatedResult, - SerializableError, + Test, TestCaseResult, + TestContext, TestResult, } from '@jest/test-result'; import type {Config} from '@jest/types'; -import type {FS as HasteFS, ModuleMap} from 'jest-haste-map'; -import type Resolver from 'jest-resolve'; import type {worker} from './CoverageWorker'; export type ReporterOnStartOptions = { @@ -21,41 +20,8 @@ export type ReporterOnStartOptions = { showStatus: boolean; }; -export type Context = { - config: Config.ProjectConfig; - hasteFS: HasteFS; - moduleMap: ModuleMap; - resolver: Resolver; -}; - -export type Test = { - context: Context; - duration?: number; - path: string; -}; - export type CoverageWorker = {worker: typeof worker}; -export type CoverageReporterOptions = { - changedFiles?: Set; - sourcesRelatedToTestsInChangedFiles?: Set; -}; - -export type CoverageReporterSerializedOptions = { - changedFiles?: Array; - sourcesRelatedToTestsInChangedFiles?: Array; -}; - -export type OnTestStart = (test: Test) => Promise; -export type OnTestFailure = ( - test: Test, - error: SerializableError, -) => Promise; -export type OnTestSuccess = ( - test: Test, - result: TestResult, -) => Promise; - export interface Reporter { readonly onTestResult?: ( test: Test, @@ -78,30 +44,28 @@ export interface Reporter { readonly onTestStart?: (test: Test) => Promise | void; readonly onTestFileStart?: (test: Test) => Promise | void; readonly onRunComplete: ( - contexts: Set, + testContexts: Set, results: AggregatedResult, ) => Promise | void; readonly getLastError: () => Error | void; } +export type ReporterContext = { + firstRun: boolean; + previousSuccess: boolean; + changedFiles?: Set; + sourcesRelatedToTestsInChangedFiles?: Set; + startRun?: (globalConfig: Config.GlobalConfig) => unknown; +}; + +export type ReporterContextSerialized = { + changedFiles?: Array; + sourcesRelatedToTestsInChangedFiles?: Array; +}; + export type SummaryOptions = { currentTestCases?: Array<{test: Test; testCaseResult: TestCaseResult}>; estimatedTime?: number; roundTime?: boolean; width?: number; }; - -export type TestRunnerOptions = { - serial: boolean; -}; - -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.ts b/packages/jest-reporters/src/utils.ts index 7701a3283a1a..158baaf9e6cd 100644 --- a/packages/jest-reporters/src/utils.ts +++ b/packages/jest-reporters/src/utils.ts @@ -8,10 +8,10 @@ import * as path from 'path'; import chalk = require('chalk'); import slash = require('slash'); -import type {AggregatedResult, TestCaseResult} from '@jest/test-result'; +import type {AggregatedResult, Test, TestCaseResult} from '@jest/test-result'; import type {Config} from '@jest/types'; import {formatTime, pluralize} from 'jest-util'; -import type {SummaryOptions, Test} from './types'; +import type {SummaryOptions} from './types'; const PROGRESS_BAR_WIDTH = 40; diff --git a/packages/jest-reporters/tsconfig.json b/packages/jest-reporters/tsconfig.json index d5c6ecc17d98..406051cc2489 100644 --- a/packages/jest-reporters/tsconfig.json +++ b/packages/jest-reporters/tsconfig.json @@ -8,7 +8,6 @@ "exclude": ["./**/__tests__/**/*"], "references": [ {"path": "../jest-console"}, - {"path": "../jest-haste-map"}, {"path": "../jest-resolve"}, {"path": "../jest-test-result"}, {"path": "../jest-transform"}, diff --git a/yarn.lock b/yarn.lock index a9276f7a5f4a..c7723be53e9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2777,7 +2777,6 @@ __metadata: istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.0 istanbul-reports: ^3.1.3 - jest-haste-map: ^28.0.0-alpha.8 jest-resolve: ^28.0.0-alpha.8 jest-util: ^28.0.0-alpha.8 jest-worker: ^28.0.0-alpha.8 From 238be9260157428b67aee1e7fef15217884e1743 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 9 Apr 2022 15:23:44 +0300 Subject: [PATCH 02/14] reporterConstructor --- packages/jest-core/src/TestScheduler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-core/src/TestScheduler.ts b/packages/jest-core/src/TestScheduler.ts index b6e552caed9e..c921244e4cd3 100644 --- a/packages/jest-core/src/TestScheduler.ts +++ b/packages/jest-core/src/TestScheduler.ts @@ -88,8 +88,8 @@ class TestScheduler { this._dispatcher.register(reporter); } - removeReporter(reporter: ReporterConstructor): void { - this._dispatcher.unregister(reporter); + removeReporter(reporterConstructor: ReporterConstructor): void { + this._dispatcher.unregister(reporterConstructor); } async scheduleTests( From 1a53908cb7391b5d3c1f00bf6e5b31a055709dae Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 9 Apr 2022 15:40:00 +0300 Subject: [PATCH 03/14] add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be38c0984c0..1fdfa101d39c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ - `[jest-mock]` Add support for auto-mocking async generator functions ([#11080](https://github.com/facebook/jest/pull/11080)) - `[jest-mock]` Add `contexts` member to mock functions ([#12601](https://github.com/facebook/jest/pull/12601)) - `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320)) +- `[jest-reporters]` Pass `reporterContext` to custom reporter constructors as third argument ([#12657](https://github.com/facebook/jest/pull/12657)) +- `[jest-reporters]` [**BREAKING**] Remove type reexport, they must be imported from `@jest/test-result` and `@jest/types` ([#12657](https://github.com/facebook/jest/pull/12657)) - `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373)) - `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392)) - `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540)) @@ -459,6 +461,7 @@ - `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751)) - `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232)) - `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) +- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) - `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728)) - `[jest-runner]` [**BREAKING**] Run transforms over `runner` ([#8823](https://github.com/facebook/jest/pull/8823)) - `[jest-runner]` [**BREAKING**] Run transforms over `testRunner` ([#8823](https://github.com/facebook/jest/pull/8823)) From 50a8241c90e67dde07f4497a340688c5a348c76c Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 9 Apr 2022 15:47:16 +0300 Subject: [PATCH 04/14] fix changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fdfa101d39c..640a7abf75bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -461,7 +461,6 @@ - `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751)) - `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232)) - `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) -- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015)) - `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728)) - `[jest-runner]` [**BREAKING**] Run transforms over `runner` ([#8823](https://github.com/facebook/jest/pull/8823)) - `[jest-runner]` [**BREAKING**] Run transforms over `testRunner` ([#8823](https://github.com/facebook/jest/pull/8823)) From deb6074db00030db6bd9d10879e225e05d98f5cb Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 10 Apr 2022 10:45:49 +0300 Subject: [PATCH 05/14] keep type reexports --- CHANGELOG.md | 1 - packages/jest-reporters/src/index.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9139ba1cfaf4..29b70dfadf5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,6 @@ - `[jest-mock]` Add `contexts` member to mock functions ([#12601](https://github.com/facebook/jest/pull/12601)) - `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320), [#12658](https://github.com/facebook/jest/pull/12658) - `[jest-reporters]` Pass `reporterContext` to custom reporter constructors as third argument ([#12657](https://github.com/facebook/jest/pull/12657)) -- `[jest-reporters]` [**BREAKING**] Remove type reexport, they must be imported from `@jest/test-result` and `@jest/types` ([#12657](https://github.com/facebook/jest/pull/12657)) - `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373)) - `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392)) - `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540)) diff --git a/packages/jest-reporters/src/index.ts b/packages/jest-reporters/src/index.ts index 7831946d7a51..b64b93649794 100644 --- a/packages/jest-reporters/src/index.ts +++ b/packages/jest-reporters/src/index.ts @@ -14,6 +14,14 @@ import { trimAndFormatPath, } from './utils'; +export type {Config} from '@jest/types'; +export type { + AggregatedResult, + Test, + TestCaseResult, + TestContext, + TestResult, +} from '@jest/test-result'; export {default as BaseReporter} from './BaseReporter'; export {default as CoverageReporter} from './CoverageReporter'; export {default as DefaultReporter} from './DefaultReporter'; From e13963cb426868f38dbe138985377261cd4c57d9 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 10 Apr 2022 10:50:32 +0300 Subject: [PATCH 06/14] sort exports --- packages/jest-reporters/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-reporters/src/index.ts b/packages/jest-reporters/src/index.ts index b64b93649794..12431b49ef0b 100644 --- a/packages/jest-reporters/src/index.ts +++ b/packages/jest-reporters/src/index.ts @@ -14,7 +14,6 @@ import { trimAndFormatPath, } from './utils'; -export type {Config} from '@jest/types'; export type { AggregatedResult, Test, @@ -22,6 +21,7 @@ export type { TestContext, TestResult, } from '@jest/test-result'; +export type {Config} from '@jest/types'; export {default as BaseReporter} from './BaseReporter'; export {default as CoverageReporter} from './CoverageReporter'; export {default as DefaultReporter} from './DefaultReporter'; From 4afff9f65f465a1a8eb5350ac9ed30fba79e090f Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 10 Apr 2022 11:02:12 +0300 Subject: [PATCH 07/14] expose getSnapshotStatus and getSnapshotSummary --- packages/jest-reporters/src/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/jest-reporters/src/index.ts b/packages/jest-reporters/src/index.ts index 12431b49ef0b..f536c362a7f8 100644 --- a/packages/jest-reporters/src/index.ts +++ b/packages/jest-reporters/src/index.ts @@ -6,6 +6,8 @@ */ import getResultHeader from './getResultHeader'; +import getSnapshotStatus from './getSnapshotStatus'; +import getSnapshotSummary from './getSnapshotSummary'; import { formatTestPath, getSummary, @@ -16,6 +18,7 @@ import { export type { AggregatedResult, + SnapshotSummary, Test, TestCaseResult, TestContext, @@ -38,6 +41,8 @@ export type { export const utils = { formatTestPath, getResultHeader, + getSnapshotStatus, + getSnapshotSummary, getSummary, printDisplayName, relativePath, From 3a80da7f5dd938e546ec81bafd535db3b08388d9 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 10 Apr 2022 18:55:15 +0300 Subject: [PATCH 08/14] better TestSchedulerContext type? --- packages/jest-core/src/TestScheduler.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/jest-core/src/TestScheduler.ts b/packages/jest-core/src/TestScheduler.ts index b32f2bf6d4a6..3ba591f27ffb 100644 --- a/packages/jest-core/src/TestScheduler.ts +++ b/packages/jest-core/src/TestScheduler.ts @@ -53,13 +53,7 @@ type TestRunnerConstructor = new ( testRunnerContext: TestRunnerContext, ) => JestTestRunner; -export type TestSchedulerContext = { - firstRun: boolean; - previousSuccess: boolean; - changedFiles?: Set; - sourcesRelatedToTestsInChangedFiles?: Set; - startRun?: (globalConfig: Config.GlobalConfig) => void; -}; +export type TestSchedulerContext = ReporterContext & TestRunnerContext; export async function createTestScheduler( globalConfig: Config.GlobalConfig, From aed27728ae3e9448a5f4ae1af64cc0ab2bf453b8 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 11 Apr 2022 08:56:24 +0300 Subject: [PATCH 09/14] tweak docs --- docs/Configuration.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index b7459afc498b..9eb4915808c5 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -822,7 +822,7 @@ When using multi-project runner, it's recommended to add a `displayName` for eac Default: `undefined` -Use this configuration option to add reporters to Jest. Optionally, a reporter can be configured by passing options object as a second argument of a tuple: +Use this configuration option to add reporters to Jest. It must be a list of reporter names, additional options can be passed to a reporter using the tuple form: ```json { @@ -833,24 +833,38 @@ Use this configuration option to add reporters to Jest. Optionally, a reporter c } ``` -:::tip - -Take a look at a list of [awesome reporters](https://github.com/jest-community/awesome-jest#reporters) from Awesome Jest. +#### Default Reporters -If custom reporters are specified, the default Jest reporters will be overridden. If you wish to keep them, `'default'` can be passed as a module name: +If custom reporters are specified, the default Jest reporter will be overridden. If you wish to keep it, `'default'` must be passed as a reporters name: ```json { "reporters": [ "default", - ["jest-junit", {"outputName": "junit-report.xml", "suiteName": "some-name"}] + ["jest-junit", {"outputDirectory": "reports", "outputName": "report.xml"}] ] } ``` +#### GitHub Actions Reporter + +If included in the list, the built-in GitHub Actions Reporter will annotate changed files with test failure messages: + +```json +{ + "reporters": ["default", "github-actions"] +} +``` + +#### Custom Reporters + +:::tip + +Hungry for reporters? Take a look at long list of [awesome reporters](https://github.com/jest-community/awesome-jest#reporters) from Awesome Jest. + ::: -Custom reporter module must export a class that takes `globalConfig`, `reporterConfig` and `reporterContext` as constructor arguments and implements at least `onRunComplete()` method (for the full list of methods and argument types see `Reporter` interface in [packages/jest-reporters/src/types.ts](https://github.com/facebook/jest/blob/main/packages/jest-reporters/src/types.ts)): +Custom reporter module must export a class that takes `globalConfig`, `reporterOptions` and `reporterContext` as constructor arguments and implements at least `onRunComplete()` method (for the full list of methods and argument types see `Reporter` interface in [packages/jest-reporters/src/types.ts](https://github.com/facebook/jest/blob/main/packages/jest-reporters/src/types.ts)): ```js title="custom-reporter.js" class CustomReporter { @@ -862,9 +876,9 @@ class CustomReporter { onRunComplete(testContexts, results) { console.log('Custom reporter output:'); - console.log('GlobalConfig: ', this._globalConfig); - console.log('Options: ', this._options); - console.log('Context: ', this._context); + console.log('global config: ', this._globalConfig); + console.log('options for this reporter from Jest config: ', this._options); + console.log('reporter context passed from test scheduler: ', this._context); } // Optionally, reporters can force Jest to exit with non zero code by returning From 1e7f07331134a3a6230fccb4e29ec27389c52dd6 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 11 Apr 2022 10:49:41 +0300 Subject: [PATCH 10/14] Update docs/Configuration.md --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 9eb4915808c5..b797d11bbe29 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -833,7 +833,7 @@ Use this configuration option to add reporters to Jest. It must be a list of rep } ``` -#### Default Reporters +#### Default Reporter If custom reporters are specified, the default Jest reporter will be overridden. If you wish to keep it, `'default'` must be passed as a reporters name: From bbb8f9767a6f92187a93db530c61bdc33180ffc1 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 11 Apr 2022 09:57:42 +0200 Subject: [PATCH 11/14] Update docs/Configuration.md --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index b797d11bbe29..afb0a21b0481 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -860,7 +860,7 @@ If included in the list, the built-in GitHub Actions Reporter will annotate chan :::tip -Hungry for reporters? Take a look at long list of [awesome reporters](https://github.com/jest-community/awesome-jest#reporters) from Awesome Jest. +Hungry for reporters? Take a look at long list of [awesome reporters](https://github.com/jest-community/awesome-jest/blob/main/README.md#reporters) from Awesome Jest. ::: From 59ee2aff139e7c5adb46609f20da1ac81025d283 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 11 Apr 2022 17:40:35 +0300 Subject: [PATCH 12/14] fix reporters type --- .../src/__tests__/normalize.test.ts | 44 +++++++++++++++++++ packages/jest-types/src/Config.ts | 5 ++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index d4e264ccebae..f1a5043e7b96 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -309,6 +309,50 @@ describe('roots', () => { testPathArray('roots'); }); +describe('reporters', () => { + let Resolver; + beforeEach(() => { + Resolver = require('jest-resolve').default; + Resolver.findNodeModule = jest.fn(name => name); + }); + + it('allows empty list', async () => { + const {options} = await normalize( + { + reporters: [], + rootDir: '/root/', + }, + {} as Config.Argv, + ); + + expect(options.reporters).toEqual([]); + }); + + it('normalizes the path and options object', async () => { + const {options} = await normalize( + { + reporters: [ + 'default', + 'github-actions', + '/custom-reporter.js', + ['/custom-reporter.js', {banana: 'yes', pineapple: 'no'}], + ['jest-junit', {outputName: 'report.xml'}], + ], + rootDir: '/root/', + }, + {} as Config.Argv, + ); + + expect(options.reporters).toEqual([ + ['default', {}], + ['github-actions', {}], + ['/root/custom-reporter.js', {}], + ['/root/custom-reporter.js', {banana: 'yes', pineapple: 'no'}], + ['jest-junit', {outputName: 'report.xml'}], + ]); + }); +}); + describe('transform', () => { let Resolver; beforeEach(() => { diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 02dcf2d8eb3d..7cc11a5df789 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -411,7 +411,7 @@ export type GlobalConfig = { passWithNoTests: boolean; projects: Array; replname?: string; - reporters?: Array; + reporters?: Array; runTestsByPath: boolean; rootDir: string; shard?: ShardConfig; @@ -546,8 +546,9 @@ export type Argv = Arguments< onlyFailures: boolean; outputFile: string; preset: string | null | undefined; - projects: Array; prettierPath: string | null | undefined; + projects: Array; + reporters: Array; resetMocks: boolean; resetModules: boolean; resolver: string | null | undefined; From ee0cc73e57be63ac3824d6249b9cf74d0b3a01d6 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 11 Apr 2022 17:41:16 +0300 Subject: [PATCH 13/14] clean up logic --- packages/jest-core/src/TestScheduler.ts | 54 ++++++++----------- .../src/__tests__/TestScheduler.test.js | 18 ++----- 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/packages/jest-core/src/TestScheduler.ts b/packages/jest-core/src/TestScheduler.ts index 9eb4465a77ca..0a6d551088f4 100644 --- a/packages/jest-core/src/TestScheduler.ts +++ b/packages/jest-core/src/TestScheduler.ts @@ -57,6 +57,8 @@ export type TestSchedulerContext = { sourcesRelatedToTestsInChangedFiles?: Set; }; +type ReporterMap = Record>; + export async function createTestScheduler( globalConfig: Config.GlobalConfig, options: TestSchedulerOptions, @@ -350,13 +352,19 @@ class TestScheduler { return; } - const reporterNames = reporters.map( - reporter => this._getReporterProps(reporter).path, - ); + let reporterMap: ReporterMap = {}; - const isDefault = reporterNames?.includes('default'); + reporters.forEach(reporter => { + reporterMap = Object.assign(reporterMap, { + [reporter[0]]: reporter[1], + }); + }); + + const reporterNames = Object.keys(reporterMap); + + const isDefault = reporterNames.includes('default'); const isGitHubActions = - GITHUB_ACTIONS && reporterNames?.includes('github-actions'); + GITHUB_ACTIONS && reporterNames.includes('github-actions'); if (isDefault) { this._setupDefaultReporters(collectCoverage); @@ -376,7 +384,9 @@ class TestScheduler { ); } - await this._addCustomReporters(reporters); + if (reporterNames.length) { + await this._addCustomReporters(reporterMap); + } } private _setupDefaultReporters(collectCoverage: boolean) { @@ -399,44 +409,22 @@ class TestScheduler { this.addReporter(new SummaryReporter(this._globalConfig)); } - private async _addCustomReporters( - reporters: Array, - ) { - for (const reporter of reporters) { - const {options, path} = this._getReporterProps(reporter); - + private async _addCustomReporters(reporters: ReporterMap) { + for (const path in reporters) { if (['default', 'github-actions'].includes(path)) continue; try { - const Reporter = await requireOrImportModule(path, true); - this.addReporter(new Reporter(this._globalConfig, options)); + const Reporter = await requireOrImportModule(path); + this.addReporter(new Reporter(this._globalConfig, reporters[path])); } catch (error: any) { error.message = `An error occurred while adding the reporter at path "${chalk.bold( path, - )}".${error.message}`; + )}".\n${error.message}`; throw error; } } } - /** - * Get properties of a reporter in an object - * to make dealing with them less painful. - */ - private _getReporterProps(reporter: string | Config.ReporterConfig): { - path: string; - options: Record; - } { - if (typeof reporter === 'string') { - return {options: this._options, path: reporter}; - } else if (Array.isArray(reporter)) { - const [path, options] = reporter; - return {options, path}; - } - - throw new Error('Reporter should be either a string or an array'); - } - private async _bailIfNeeded( contexts: Set, aggregatedResults: AggregatedResult, diff --git a/packages/jest-core/src/__tests__/TestScheduler.test.js b/packages/jest-core/src/__tests__/TestScheduler.test.js index 24dd450ee9a1..005dcabfb300 100644 --- a/packages/jest-core/src/__tests__/TestScheduler.test.js +++ b/packages/jest-core/src/__tests__/TestScheduler.test.js @@ -49,17 +49,6 @@ test('config for reporters supports `default`', async () => { const numberOfReporters = undefinedReportersScheduler._dispatcher._reporters.length; - const stringDefaultReportersScheduler = await createTestScheduler( - makeGlobalConfig({ - reporters: ['default'], - }), - {}, - {}, - ); - expect(stringDefaultReportersScheduler._dispatcher._reporters.length).toBe( - numberOfReporters, - ); - const defaultReportersScheduler = await createTestScheduler( makeGlobalConfig({ reporters: [['default', {}]], @@ -93,7 +82,7 @@ test('config for reporters supports `github-actions`', async () => { await createTestScheduler( makeGlobalConfig({ - reporters: ['github-actions'], + reporters: [['github-actions', {}]], }), {}, {}, @@ -102,7 +91,10 @@ test('config for reporters supports `github-actions`', async () => { await createTestScheduler( makeGlobalConfig({ - reporters: ['default', 'github-actions'], + reporters: [ + ['default', {}], + ['github-actions', {}], + ], }), {}, {}, From cdb3dafed8442fccfc33baf3a94381d229914dba Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 11 Apr 2022 18:29:34 +0300 Subject: [PATCH 14/14] more test --- .../__snapshots__/normalize.test.ts.snap | 68 +++++++++++++++++++ .../src/__tests__/normalize.test.ts | 52 ++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/packages/jest-config/src/__tests__/__snapshots__/normalize.test.ts.snap b/packages/jest-config/src/__tests__/__snapshots__/normalize.test.ts.snap index a3d700f5b943..284570033fc2 100644 --- a/packages/jest-config/src/__tests__/__snapshots__/normalize.test.ts.snap +++ b/packages/jest-config/src/__tests__/__snapshots__/normalize.test.ts.snap @@ -200,6 +200,74 @@ exports[`preset throws when preset not found 1`] = ` " `; +exports[`reporters throws an error if first value in the tuple is not a string 1`] = ` +"Reporter Validation Error: + + Unexpected value for Path at index 0 of reporter at index 0 + Expected: + string + Got: + number + Reporter configuration: + [ + 123 + ] + + Configuration Documentation: + https://jestjs.io/docs/configuration +" +`; + +exports[`reporters throws an error if second value in the tuple is not an object 1`] = ` +"Reporter Validation Error: + + Unexpected value for Reporter Configuration at index 1 of reporter at index 0 + Expected: + object + Got: + boolean + Reporter configuration: + [ + "some-reporter", + true + ] + + Configuration Documentation: + https://jestjs.io/docs/configuration +" +`; + +exports[`reporters throws an error if second value is missing in the tuple 1`] = ` +"Reporter Validation Error: + + Unexpected value for Reporter Configuration at index 1 of reporter at index 0 + Expected: + object + Got: + undefined + Reporter configuration: + [ + "some-reporter" + ] + + Configuration Documentation: + https://jestjs.io/docs/configuration +" +`; + +exports[`reporters throws an error if value is neither string nor array 1`] = ` +"Reporter Validation Error: + + Reporter at index 0 must be of type: + array or string + but instead received: + number + + Configuration Documentation: + https://jestjs.io/docs/configuration +" +`; + exports[`rootDir throws if the options is missing a rootDir property 1`] = ` "Validation Error: diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index f1a5043e7b96..457baef302f1 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -351,6 +351,58 @@ describe('reporters', () => { ['jest-junit', {outputName: 'report.xml'}], ]); }); + + it('throws an error if value is neither string nor array', async () => { + expect.assertions(1); + await expect( + normalize( + { + reporters: [123], + rootDir: '/root/', + }, + {} as Config.Argv, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('throws an error if first value in the tuple is not a string', async () => { + expect.assertions(1); + await expect( + normalize( + { + reporters: [[123]], + rootDir: '/root/', + }, + {} as Config.Argv, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('throws an error if second value is missing in the tuple', async () => { + expect.assertions(1); + await expect( + normalize( + { + reporters: [['some-reporter']], + rootDir: '/root/', + }, + {} as Config.Argv, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('throws an error if second value in the tuple is not an object', async () => { + expect.assertions(1); + await expect( + normalize( + { + reporters: [['some-reporter', true]], + rootDir: '/root/', + }, + {} as Config.Argv, + ), + ).rejects.toThrowErrorMatchingSnapshot(); + }); }); describe('transform', () => {