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(jest-reporters): pass reporterContext to custom reporter constructors as third argument #12657

Merged
merged 17 commits into from Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 0 additions & 2 deletions .eslintrc.cjs
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -38,6 +38,7 @@
- `[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), [#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))
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved
- `[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))
Expand Down
57 changes: 23 additions & 34 deletions docs/Configuration.md
Expand Up @@ -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:
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved

```json
{
"reporters": ["<rootDir>/my-custom-reporter.js"]
"reporters": [
"default",
["<rootDir>/custom-reporter.js", {"banana": "yes", "pineapple": "no"}]
]
}
```

This will use custom reporter in addition to default reporters that Jest provides:
:::tip

```json
{
"reporters": ["default", "<rootDir>/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",
["<rootDir>/my-custom-reporter.js", {"banana": "yes", "pineapple": "no"}]
["jest-junit", {"outputName": "junit-report.xml", "suiteName": "some-name"}]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to convince Prettier to keep this as separate line.

]
}
```

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)):
mrazauskas marked this conversation as resolved.
Show resolved Hide resolved

```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]

Expand Down
24 changes: 20 additions & 4 deletions e2e/__tests__/__snapshots__/customReporters.test.ts.snap
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -97,7 +105,11 @@ Object {
"called": true,
"path": false,
},
"options": Object {},
"reporterContext": Object {
"firstRun": true,
"previousSuccess": true,
},
"reporterOptions": Object {},
}
`;

Expand Down Expand Up @@ -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"
}
}"
Expand Down
2 changes: 1 addition & 1 deletion e2e/custom-reporters/reporters/IncompleteReporter.js
Expand Up @@ -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}`);
Expand Down
14 changes: 8 additions & 6 deletions e2e/custom-reporters/reporters/TestReporter.js
Expand Up @@ -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
Expand All @@ -30,7 +31,8 @@ class TestReporter {
onRunStart: {},
onTestResult: {times: 0},
onTestStart: {},
options,
reporterContext,
reporterOptions,
};
}

Expand Down Expand Up @@ -66,7 +68,7 @@ class TestReporter {
onRunStart.options = typeof options;
}

onRunComplete(contexts, results) {
onRunComplete(testContexts, results) {
const onRunComplete = this._statsCollected.onRunComplete;

onRunComplete.called = true;
Expand All @@ -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 = '<<REPLACED>>';
this._statsCollected.reporterOptions.maxWorkers = '<<REPLACED>>';
}
// The Final Call
process.stdout.write(JSON.stringify(this._statsCollected, null, 4));
Expand Down
13 changes: 6 additions & 7 deletions packages/jest-core/src/ReporterDispatcher.ts
Expand Up @@ -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<Reporter>;
Expand All @@ -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),
);
}

Expand Down Expand Up @@ -82,12 +81,12 @@ export default class ReporterDispatcher {
}

async onRunComplete(
contexts: Set<Context>,
testContexts: Set<TestContext>,
results: AggregatedResult,
): Promise<void> {
for (const reporter of this._reporters) {
if (reporter.onRunComplete) {
await reporter.onRunComplete(contexts, results);
await reporter.onRunComplete(testContexts, results);
}
}
}
Expand Down