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 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
44 changes: 44 additions & 0 deletions packages/jest-config/src/__tests__/normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,50 @@ describe('roots', () => {
testPathArray('roots');
});

describe('reporters', () => {
let Resolver;
beforeEach(() => {
Resolver = require('jest-resolve').default;
Resolver.findNodeModule = jest.fn(name => name);
});

it('allows empty list', async () => {
const {options} = await normalize(
{
reporters: [],
rootDir: '/root/',
},
{} as Config.Argv,
);

expect(options.reporters).toEqual([]);
});

it('normalizes the path and options object', async () => {
const {options} = await normalize(
{
reporters: [
'default',
'github-actions',
'<rootDir>/custom-reporter.js',
['<rootDir>/custom-reporter.js', {banana: 'yes', pineapple: 'no'}],
['jest-junit', {outputName: 'report.xml'}],
],
rootDir: '/root/',
},
{} as Config.Argv,
);

expect(options.reporters).toEqual([
['default', {}],
['github-actions', {}],
['/root/custom-reporter.js', {}],
['/root/custom-reporter.js', {banana: 'yes', pineapple: 'no'}],
['jest-junit', {outputName: 'report.xml'}],
]);
});
});

describe('transform', () => {
let Resolver;
beforeEach(() => {
Expand Down
13 changes: 6 additions & 7 deletions packages/jest-core/src/ReporterDispatcher.ts
Original file line number Diff line number Diff line change
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
Loading