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