Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support reporters written in ESM #11427

Merged
merged 2 commits into from May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -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))
Expand All @@ -13,13 +12,15 @@
- `[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))
- `[jest-core]` Add support for `testSequencer` written in ESM ([#11207](https://github.com/facebook/jest/pull/11207))
- `[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))
Expand Down Expand Up @@ -116,6 +117,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))
Expand Down
26 changes: 26 additions & 0 deletions e2e/__tests__/customReporters.test.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -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', '<rootDir>/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);
});
});
});
33 changes: 22 additions & 11 deletions packages/jest-core/src/TestScheduler.ts
Expand Up @@ -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';
Expand All @@ -49,7 +49,20 @@ export type TestSchedulerContext = {
changedFiles?: Set<Config.Path>;
sourcesRelatedToTestsInChangedFiles?: Set<Config.Path>;
};
export default class TestScheduler {

export async function createTestScheduler(
globalConfig: Config.GlobalConfig,
options: TestSchedulerOptions,
context: TestSchedulerContext,
): Promise<TestScheduler> {
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;
Expand All @@ -64,7 +77,6 @@ export default class TestScheduler {
this._globalConfig = globalConfig;
this._options = options;
this._context = context;
this._setupReporters();
}

addReporter(reporter: Reporter): void {
Expand Down Expand Up @@ -337,7 +349,7 @@ export default class TestScheduler {
);
}

private _setupReporters() {
async _setupReporters() {
const {collectCoverage, notify, reporters} = this._globalConfig;
const isDefault = this._shouldAddDefaultReporters(reporters);

Expand Down Expand Up @@ -366,7 +378,7 @@ export default class TestScheduler {
}

if (reporters && Array.isArray(reporters)) {
this._addCustomReporters(reporters);
await this._addCustomReporters(reporters);
}
}

Expand All @@ -390,17 +402,16 @@ export default class TestScheduler {
this.addReporter(new SummaryReporter(this._globalConfig));
}

private _addCustomReporters(
private async _addCustomReporters(
reporters: Array<string | Config.ReporterConfig>,
) {
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<any>(path, true);
this.addReporter(new Reporter(this._globalConfig, options));
} catch (error) {
error.message =
Expand All @@ -410,7 +421,7 @@ export default class TestScheduler {
error.message;
throw error;
}
});
}
}

/**
Expand Down
28 changes: 14 additions & 14 deletions packages/jest-core/src/__tests__/TestScheduler.test.js
Expand Up @@ -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');
Expand All @@ -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,
},
Expand All @@ -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'],
},
Expand All @@ -55,7 +55,7 @@ test('config for reporters supports `default`', () => {
numberOfReporters,
);

const defaultReportersScheduler = new TestScheduler(
const defaultReportersScheduler = await createTestScheduler(
{
reporters: [['default', {}]],
},
Expand All @@ -65,7 +65,7 @@ test('config for reporters supports `default`', () => {
numberOfReporters,
);

const emptyReportersScheduler = new TestScheduler(
const emptyReportersScheduler = await createTestScheduler(
{
reporters: [],
},
Expand All @@ -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);
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand All @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-core/src/jest.ts
Expand Up @@ -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';
8 changes: 5 additions & 3 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion packages/jest/src/jest.ts
Expand Up @@ -7,8 +7,8 @@

export {
SearchSource,
TestScheduler,
TestWatcher,
createTestScheduler,
getVersion,
runCLI,
} from '@jest/core';
Expand Down