diff --git a/CHANGELOG.md b/CHANGELOG.md index 0585c1ec3104..fe1a0477439e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ### Features -- `[jest-config, jest-haste-map, jest-resolve, jest-runner, jest-runtime, jest-test-sequencer, jest-transform, jest-types]` [**BREAKING**] Add custom HasteMap class implementation config option ([#11107](https://github.com/facebook/jest/pull/11107)) - `[babel-jest]` Add async transformation ([#11192](https://github.com/facebook/jest/pull/11192)) - `[jest-changed-files]` Use '--' to separate paths from revisions ([#11160](https://github.com/facebook/jest/pull/11160)) - `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624)) @@ -13,6 +12,7 @@ - `[jest-config, jest-runtime]` Support ESM for files other than `.js` and `.mjs` ([#10823](https://github.com/facebook/jest/pull/10823)) - `[jest-config, jest-runtime]` [**BREAKING**] Use "modern" implementation as default for fake timers ([#10874](https://github.com/facebook/jest/pull/10874) & [#11197](https://github.com/facebook/jest/pull/11197)) - `[jest-config` Allow passing `forceNodeFilesystemAPI` through to `jest-haste-map` ([#11264](https://github.com/facebook/jest/pull/11264)) +- `[jest-config, jest-haste-map, jest-resolve, jest-runner, jest-runtime, jest-test-sequencer, jest-transform, jest-types]` [**BREAKING**] Add custom HasteMap class implementation config option ([#11107](https://github.com/facebook/jest/pull/11107)) - `[jest-core]` make `TestWatcher` extend `emittery` ([#10324](https://github.com/facebook/jest/pull/10324)) - `[jest-core]` Run failed tests interactively the same way we do with snapshots ([#10858](https://github.com/facebook/jest/pull/10858)) - `[jest-core]` more `TestSequencer` methods can be async ([#10980](https://github.com/facebook/jest/pull/10980)) @@ -20,6 +20,7 @@ - `[jest-core]` Add support for `globalSetup` and `globalTeardown` written in ESM ([#11267](https://github.com/facebook/jest/pull/11267)) - `[jest-core]` Add support for `watchPlugins` written in ESM ([#11315](https://github.com/facebook/jest/pull/11315)) - `[jest-core]` Add support for `runner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232)) +- `[jest-core]` Add support for `reporters` written in ESM ([#11427](https://github.com/facebook/jest/pull/11427)) - `[jest-each]` Add support for interpolation with object properties ([#11388](https://github.com/facebook/jest/pull/11388)) - `[jest-environment-node]` Add AbortController to globals ([#11182](https://github.com/facebook/jest/pull/11182)) - `[@jest/fake-timers]` Update to `@sinonjs/fake-timers` to v7 ([#11198](https://github.com/facebook/jest/pull/11198)) @@ -117,6 +118,7 @@ - `[babel-jest]` [**BREAKING**] Migrate to ESM ([#11193](https://github.com/facebook/jest/pull/11193)) - `[docs]` Correct example using `browser-resolve` ([#11140](https://github.com/facebook/jest/pull/11140)) - `[docs]` Clarify `timers` configuration property ([#11376](https://github.com/facebook/jest/pull/11376)) +- `[jest, jest-core]` [**BREAKING**] Replace `TestScheduler` export with `createTestScheduler` ([#11427](https://github.com/facebook/jest/pull/11427)) - `[jest-config]` [**BREAKING**] Remove `enabledTestsMap` config, use `filter` instead ([#10787](https://github.com/facebook/jest/pull/10787)) - `[jest-console]` [**BREAKING**] Move `root` into `config` and take `GlobalConfig` as mandatory parameter for `getConsoleOutput` ([#10126](https://github.com/facebook/jest/pull/10126)) - `[jest-console]` Export LogEntry ([#11017](https://github.com/facebook/jest/pull/11017)) diff --git a/e2e/__tests__/customReporters.test.ts b/e2e/__tests__/customReporters.test.ts index d0dc1d78455c..e2210710c50b 100644 --- a/e2e/__tests__/customReporters.test.ts +++ b/e2e/__tests__/customReporters.test.ts @@ -8,6 +8,7 @@ import {tmpdir} from 'os'; import * as path from 'path'; import {wrap} from 'jest-snapshot-serializer-raw'; +import {onNodeVersions} from '@jest/test-utils'; import {cleanup, extractSummary, writeFiles} from '../Utils'; import runJest from '../runJest'; @@ -159,4 +160,29 @@ describe('Custom Reporters Integration', () => { expect(stderr).toMatch(/ON_RUN_START_ERROR/); expect(exitCode).toBe(1); }); + + onNodeVersions('^12.17.0 || >=13.2.0', () => { + test('supports reporter written in ESM', () => { + writeFiles(DIR, { + '__tests__/test.test.js': `test('test', () => {});`, + 'package.json': JSON.stringify({ + jest: { + reporters: ['default', '/reporter.mjs'], + testEnvironment: 'node', + }, + }), + 'reporter.mjs': ` + export default class Reporter { + onRunStart() { + throw new Error('ON_RUN_START_ERROR'); + } + }; + `, + }); + + const {stderr, exitCode} = runJest(DIR); + expect(stderr).toMatch(/ON_RUN_START_ERROR/); + expect(exitCode).toBe(1); + }); + }); }); diff --git a/packages/jest-core/src/TestScheduler.ts b/packages/jest-core/src/TestScheduler.ts index 0bf7746a6092..b20759cf34d8 100644 --- a/packages/jest-core/src/TestScheduler.ts +++ b/packages/jest-core/src/TestScheduler.ts @@ -31,7 +31,7 @@ import {formatExecError} from 'jest-message-util'; import TestRunner, {Test} from 'jest-runner'; import type {Context} from 'jest-runtime'; import snapshot = require('jest-snapshot'); -import {interopRequireDefault} from 'jest-util'; +import {requireOrImportModule} from 'jest-util'; import ReporterDispatcher from './ReporterDispatcher'; import type TestWatcher from './TestWatcher'; import {shouldRunInBand} from './testSchedulerHelper'; @@ -49,7 +49,20 @@ export type TestSchedulerContext = { changedFiles?: Set; sourcesRelatedToTestsInChangedFiles?: Set; }; -export default class TestScheduler { + +export async function createTestScheduler( + globalConfig: Config.GlobalConfig, + options: TestSchedulerOptions, + context: TestSchedulerContext, +): Promise { + const scheduler = new TestScheduler(globalConfig, options, context); + + await scheduler._setupReporters(); + + return scheduler; +} + +class TestScheduler { private readonly _dispatcher: ReporterDispatcher; private readonly _globalConfig: Config.GlobalConfig; private readonly _options: TestSchedulerOptions; @@ -64,7 +77,6 @@ export default class TestScheduler { this._globalConfig = globalConfig; this._options = options; this._context = context; - this._setupReporters(); } addReporter(reporter: Reporter): void { @@ -337,7 +349,7 @@ export default class TestScheduler { ); } - private _setupReporters() { + async _setupReporters() { const {collectCoverage, notify, reporters} = this._globalConfig; const isDefault = this._shouldAddDefaultReporters(reporters); @@ -366,7 +378,7 @@ export default class TestScheduler { } if (reporters && Array.isArray(reporters)) { - this._addCustomReporters(reporters); + await this._addCustomReporters(reporters); } } @@ -390,17 +402,16 @@ export default class TestScheduler { this.addReporter(new SummaryReporter(this._globalConfig)); } - private _addCustomReporters( + private async _addCustomReporters( reporters: Array, ) { - reporters.forEach(reporter => { + for (const reporter of reporters) { const {options, path} = this._getReporterProps(reporter); - if (path === 'default') return; + if (path === 'default') continue; try { - // TODO: Use `requireAndTranspileModule` for Jest 26 - const Reporter = interopRequireDefault(require(path)).default; + const Reporter = await requireOrImportModule(path, true); this.addReporter(new Reporter(this._globalConfig, options)); } catch (error) { error.message = @@ -410,7 +421,7 @@ export default class TestScheduler { error.message; throw error; } - }); + } } /** diff --git a/packages/jest-core/src/__tests__/TestScheduler.test.js b/packages/jest-core/src/__tests__/TestScheduler.test.js index 464a923206fc..b707ef3289c9 100644 --- a/packages/jest-core/src/__tests__/TestScheduler.test.js +++ b/packages/jest-core/src/__tests__/TestScheduler.test.js @@ -8,7 +8,7 @@ import {SummaryReporter} from '@jest/reporters'; import {makeProjectConfig} from '@jest/test-utils'; -import TestScheduler from '../TestScheduler'; +import {createTestScheduler} from '../TestScheduler'; import * as testSchedulerHelper from '../testSchedulerHelper'; jest.mock('@jest/reporters'); @@ -35,8 +35,8 @@ beforeEach(() => { spyShouldRunInBand.mockClear(); }); -test('config for reporters supports `default`', () => { - const undefinedReportersScheduler = new TestScheduler( +test('config for reporters supports `default`', async () => { + const undefinedReportersScheduler = await createTestScheduler( { reporters: undefined, }, @@ -45,7 +45,7 @@ test('config for reporters supports `default`', () => { const numberOfReporters = undefinedReportersScheduler._dispatcher._reporters.length; - const stringDefaultReportersScheduler = new TestScheduler( + const stringDefaultReportersScheduler = await createTestScheduler( { reporters: ['default'], }, @@ -55,7 +55,7 @@ test('config for reporters supports `default`', () => { numberOfReporters, ); - const defaultReportersScheduler = new TestScheduler( + const defaultReportersScheduler = await createTestScheduler( { reporters: [['default', {}]], }, @@ -65,7 +65,7 @@ test('config for reporters supports `default`', () => { numberOfReporters, ); - const emptyReportersScheduler = new TestScheduler( + const emptyReportersScheduler = await createTestScheduler( { reporters: [], }, @@ -74,8 +74,8 @@ test('config for reporters supports `default`', () => { expect(emptyReportersScheduler._dispatcher._reporters.length).toBe(0); }); -test('.addReporter() .removeReporter()', () => { - const scheduler = new TestScheduler({}, {}); +test('.addReporter() .removeReporter()', async () => { + const scheduler = await createTestScheduler({}, {}); const reporter = new SummaryReporter(); scheduler.addReporter(reporter); expect(scheduler._dispatcher._reporters).toContain(reporter); @@ -84,7 +84,7 @@ test('.addReporter() .removeReporter()', () => { }); test('schedule tests run in parallel per default', async () => { - const scheduler = new TestScheduler({}, {}); + const scheduler = await createTestScheduler({}, {}); const test = { context: { config: makeProjectConfig({ @@ -107,7 +107,7 @@ test('schedule tests run in parallel per default', async () => { }); test('schedule tests run in serial if the runner flags them', async () => { - const scheduler = new TestScheduler({}, {}); + const scheduler = await createTestScheduler({}, {}); const test = { context: { config: makeProjectConfig({ @@ -130,7 +130,7 @@ test('schedule tests run in serial if the runner flags them', async () => { }); test('should bail after `n` failures', async () => { - const scheduler = new TestScheduler({bail: 2}, {}); + const scheduler = await createTestScheduler({bail: 2}, {}); const test = { context: { config: makeProjectConfig({ @@ -162,7 +162,7 @@ test('should bail after `n` failures', async () => { }); test('should not bail if less than `n` failures', async () => { - const scheduler = new TestScheduler({bail: 2}, {}); + const scheduler = await createTestScheduler({bail: 2}, {}); const test = { context: { config: makeProjectConfig({ @@ -194,7 +194,7 @@ test('should not bail if less than `n` failures', async () => { }); test('should set runInBand to run in serial', async () => { - const scheduler = new TestScheduler({}, {}); + const scheduler = await createTestScheduler({}, {}); const test = { context: { config: makeProjectConfig({ @@ -220,7 +220,7 @@ test('should set runInBand to run in serial', async () => { }); test('should set runInBand to not run in serial', async () => { - const scheduler = new TestScheduler({}, {}); + const scheduler = await createTestScheduler({}, {}); const test = { context: { config: makeProjectConfig({ diff --git a/packages/jest-core/src/jest.ts b/packages/jest-core/src/jest.ts index dfba24c2c4b5..6912817f6bfa 100644 --- a/packages/jest-core/src/jest.ts +++ b/packages/jest-core/src/jest.ts @@ -6,7 +6,7 @@ */ export {default as SearchSource} from './SearchSource'; -export {default as TestScheduler} from './TestScheduler'; +export {createTestScheduler} from './TestScheduler'; export {default as TestWatcher} from './TestWatcher'; export {runCLI} from './cli'; export {default as getVersion} from './version'; diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index 51af70ed884e..90d68275ed8c 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -24,7 +24,7 @@ import {requireOrImportModule, tryRealpath} from 'jest-util'; import {JestHook, JestHookEmitter} from 'jest-watcher'; import type FailedTestsCache from './FailedTestsCache'; import SearchSource from './SearchSource'; -import TestScheduler, {TestSchedulerContext} from './TestScheduler'; +import {TestSchedulerContext, createTestScheduler} from './TestScheduler'; import type TestWatcher from './TestWatcher'; import collectNodeHandles, {HandleCollectionResult} from './collectHandles'; import getNoTestsFoundMessage from './getNoTestsFoundMessage'; @@ -268,11 +268,13 @@ export default async function runJest({ } } - const results = await new TestScheduler( + const scheduler = await createTestScheduler( globalConfig, {startRun}, testSchedulerContext, - ).scheduleTests(allTests, testWatcher); + ); + + const results = await scheduler.scheduleTests(allTests, testWatcher); await sequencer.cacheResults(allTests, results); diff --git a/packages/jest/src/jest.ts b/packages/jest/src/jest.ts index ff2c31c2f394..9c4f958cb98d 100644 --- a/packages/jest/src/jest.ts +++ b/packages/jest/src/jest.ts @@ -7,8 +7,8 @@ export { SearchSource, - TestScheduler, TestWatcher, + createTestScheduler, getVersion, runCLI, } from '@jest/core';