Skip to content

Commit

Permalink
feat(jest-reporters): pass reporterContext to custom reporter const…
Browse files Browse the repository at this point in the history
…ructors as third argument (#12657)
  • Loading branch information
mrazauskas committed Apr 12, 2022
1 parent 8a8eb36 commit 8167899
Show file tree
Hide file tree
Showing 30 changed files with 405 additions and 294 deletions.
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))
- `[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
73 changes: 38 additions & 35 deletions docs/Configuration.md
Expand Up @@ -822,73 +822,76 @@ 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. It must be a list of reporter names, additional options can be passed to a reporter using the tuple form:

```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:
#### 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:

```json
{
"reporters": ["default", "<rootDir>/my-custom-reporter.js"]
"reporters": [
"default",
["jest-junit", {"outputDirectory": "reports", "outputName": "report.xml"}]
]
}
```

Additionally, custom reporters can be configured by passing an `options` object as a second argument:
#### 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",
["<rootDir>/my-custom-reporter.js", {"banana": "yes", "pineapple": "no"}]
]
"reporters": ["default", "github-actions"]
}
```

Custom reporter modules must define a class that takes a `GlobalConfig` and reporter options as constructor arguments:
#### Custom Reporters

Example reporter:
:::tip

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.

:::

```js title="my-custom-reporter.js"
class MyCustomReporter {
constructor(globalConfig, options) {
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 {
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('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);
}
}

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
Expand Up @@ -200,6 +200,74 @@ exports[`preset throws when preset not found 1`] = `
<red></>"
`;

exports[`reporters throws an error if first value in the tuple is not a string 1`] = `
"<red><bold><bold>● </><bold>Reporter Validation Error</>:</>
<red></>
<red> Unexpected value for Path at index 0 of reporter at index 0</>
<red> Expected:</>
<red> <bold><red>string</><red></></>
<red> Got:</>
<red> <bold><green>number</><red></></>
<red> Reporter configuration:</>
<red> <bold><green>[</><red></></>
<red><bold><green> 123</><red></></>
<red><bold><green> ]</><red></></>
<red></>
<red> <bold>Configuration Documentation:</></>
<red> https://jestjs.io/docs/configuration</>
<red></>"
`;

exports[`reporters throws an error if second value in the tuple is not an object 1`] = `
"<red><bold><bold>● </><bold>Reporter Validation Error</>:</>
<red></>
<red> Unexpected value for Reporter Configuration at index 1 of reporter at index 0</>
<red> Expected:</>
<red> <bold><red>object</><red></></>
<red> Got:</>
<red> <bold><green>boolean</><red></></>
<red> Reporter configuration:</>
<red> <bold><green>[</><red></></>
<red><bold><green> "some-reporter",</><red></></>
<red><bold><green> true</><red></></>
<red><bold><green> ]</><red></></>
<red></>
<red> <bold>Configuration Documentation:</></>
<red> https://jestjs.io/docs/configuration</>
<red></>"
`;

exports[`reporters throws an error if second value is missing in the tuple 1`] = `
"<red><bold><bold>● </><bold>Reporter Validation Error</>:</>
<red></>
<red> Unexpected value for Reporter Configuration at index 1 of reporter at index 0</>
<red> Expected:</>
<red> <bold><red>object</><red></></>
<red> Got:</>
<red> <bold><green>undefined</><red></></>
<red> Reporter configuration:</>
<red> <bold><green>[</><red></></>
<red><bold><green> "some-reporter"</><red></></>
<red><bold><green> ]</><red></></>
<red></>
<red> <bold>Configuration Documentation:</></>
<red> https://jestjs.io/docs/configuration</>
<red></>"
`;

exports[`reporters throws an error if value is neither string nor array 1`] = `
"<red><bold><bold>● </><bold>Reporter Validation Error</>:</>
<red></>
<red> Reporter at index 0 must be of type:</>
<red> <bold><green>array or string</><red></></>
<red> but instead received:</>
<red> <bold><red>number</><red></></>
<red></>
<red> <bold>Configuration Documentation:</></>
<red> https://jestjs.io/docs/configuration</>
<red></>"
`;

exports[`rootDir throws if the options is missing a rootDir property 1`] = `
"<red><bold><bold>● </><bold>Validation Error</>:</>
<red></>
Expand Down

0 comments on commit 8167899

Please sign in to comment.