Skip to content

Commit 4f1cae7

Browse files
committed
ISSUE-13112: Add onTestCaseStart hook
1 parent d85d542 commit 4f1cae7

File tree

12 files changed

+242
-24
lines changed

12 files changed

+242
-24
lines changed

e2e/__tests__/__snapshots__/customReportersOnCircus.test.ts.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,25 @@ exports[`Custom Reporters Integration on jest-circus push test case results for
1414
"onTestCaseResult: sample, status: todo, numExpectations: 0
1515
onTestFileResult testCaseResult 0: sample, status: todo, numExpectations: 0"
1616
`;
17+
18+
exports[`Custom Reporters Integration on jest-circus push test case results for skip tests 1`] = `
19+
"onTestCaseResult: sample, status: pending, numExpectations: 0
20+
onTestFileResult testCaseResult 0: sample, status: pending, numExpectations: 0"
21+
`;
22+
23+
exports[`Custom Reporters Integration on jest-circus push test case start 1`] = `
24+
"onTestCaseStart: test 1, mode: undefined, ancestorTitles: Custom Reporters
25+
onTestCaseResult: test 1, status: passed
26+
onTestCaseStart: test 2, mode: undefined, ancestorTitles: Custom Reporters
27+
onTestCaseResult: test 2, status: passed"
28+
`;
29+
30+
exports[`Custom Reporters Integration on jest-circus push test case start for todo tests 1`] = `
31+
"onTestCaseStart: sample, mode: todo, ancestorTitles: Custom Reporters
32+
onTestCaseResult: sample, status: todo"
33+
`;
34+
35+
exports[`Custom Reporters Integration on jest-circus push test case start for skip tests 1`] = `
36+
"onTestCaseStart: sample, mode: skip, ancestorTitles: Custom Reporters
37+
onTestCaseResult: sample, status: pending"
38+
`;

e2e/__tests__/customReportersOnCircus.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,55 @@ describe('Custom Reporters Integration on jest-circus', () => {
5454

5555
expect(stdout).toMatchSnapshot();
5656
});
57+
58+
test('push test case results for skip tests', () => {
59+
const {stdout} = runJest('custom-reporters', [
60+
'--config',
61+
JSON.stringify({
62+
reporters: [
63+
'default',
64+
'<rootDir>/reporters/AssertionCountsReporter.js',
65+
],
66+
}),
67+
'skip.test.js',
68+
]);
69+
70+
expect(stdout).toMatchSnapshot();
71+
});
72+
73+
test('push test case start', () => {
74+
const {stdout} = runJest('custom-reporters', [
75+
'--config',
76+
JSON.stringify({
77+
reporters: ['default', '<rootDir>/reporters/TestCaseStartReporter.js'],
78+
}),
79+
'just2Tests.test.js',
80+
]);
81+
82+
expect(stdout).toMatchSnapshot();
83+
});
84+
85+
test('push test case start for todo tests', () => {
86+
const {stdout} = runJest('custom-reporters', [
87+
'--config',
88+
JSON.stringify({
89+
reporters: ['default', '<rootDir>/reporters/TestCaseStartReporter.js'],
90+
}),
91+
'todo.test.js',
92+
]);
93+
94+
expect(stdout).toMatchSnapshot();
95+
});
96+
97+
test('push test case start for skip tests', () => {
98+
const {stdout} = runJest('custom-reporters', [
99+
'--config',
100+
JSON.stringify({
101+
reporters: ['default', '<rootDir>/reporters/TestCaseStartReporter.js'],
102+
}),
103+
'skip.test.js',
104+
]);
105+
106+
expect(stdout).toMatchSnapshot();
107+
});
57108
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
describe('Custom Reporters', () => {
12+
it('test 1', () => {
13+
expect(true).toBeTruthy();
14+
});
15+
16+
it('test 2', () => {
17+
expect(true).toBeTruthy();
18+
});
19+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
describe('Custom Reporters', () => {
12+
it.skip('sample', () => {});
13+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
/**
11+
* @class
12+
* @implements {import('@jest/reporters').Reporter}
13+
*/
14+
class TestCaseStartReporter {
15+
onTestCaseStart(test, testCaseStartInfo) {
16+
const mode =
17+
testCaseStartInfo.mode != null ? testCaseStartInfo.mode : 'undefined';
18+
console.log(
19+
`onTestCaseStart: ${testCaseStartInfo.title}, ` +
20+
`mode: ${mode}, ` +
21+
`ancestorTitles: ${testCaseStartInfo.ancestorTitles.join('.')}`,
22+
);
23+
}
24+
onTestCaseResult(test, testCaseResult) {
25+
console.log(
26+
`onTestCaseResult: ${testCaseResult.title}, ` +
27+
`status: ${testCaseResult.status}`,
28+
);
29+
}
30+
}
31+
32+
module.exports = TestCaseStartReporter;

packages/jest-circus/src/testCaseReportHandler.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@
77

88
import type {TestFileEvent} from '@jest/test-result';
99
import type {Circus} from '@jest/types';
10-
import {makeSingleTestResult, parseSingleTestResult} from './utils';
10+
import {
11+
createTestCaseStartInfo,
12+
makeSingleTestResult,
13+
parseSingleTestResult,
14+
} from './utils';
1115

1216
const testCaseReportHandler =
1317
(testPath: string, sendMessageToJest: TestFileEvent) =>
1418
(event: Circus.Event): void => {
1519
switch (event.name) {
20+
case 'test_start': {
21+
const testCaseStartInfo = createTestCaseStartInfo(event.test);
22+
sendMessageToJest('test-case-start', [testPath, testCaseStartInfo]);
23+
break;
24+
}
25+
case 'test_skip':
1626
case 'test_todo':
1727
case 'test_done': {
1828
const testResult = makeSingleTestResult(event.test);

packages/jest-circus/src/utils.ts

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -328,19 +328,25 @@ export const makeRunResult = (
328328
unhandledErrors: unhandledErrors.map(_getError).map(getErrorStack),
329329
});
330330

331+
const getTestNamesPath = (test: Circus.TestEntry): Circus.TestNamesPath => {
332+
const titles = [];
333+
let parent: Circus.TestEntry | Circus.DescribeBlock | undefined = test;
334+
do {
335+
titles.unshift(parent.name);
336+
} while ((parent = parent.parent));
337+
338+
return titles;
339+
};
340+
331341
export const makeSingleTestResult = (
332342
test: Circus.TestEntry,
333343
): Circus.TestResult => {
334344
const {includeTestLocationInResult} = getState();
335-
const testPath = [];
336-
let parent: Circus.TestEntry | Circus.DescribeBlock | undefined = test;
337345

338346
const {status} = test;
339347
invariant(status, 'Status should be present after tests are run.');
340348

341-
do {
342-
testPath.unshift(parent.name);
343-
} while ((parent = parent.parent));
349+
const testPath = getTestNamesPath(test);
344350

345351
let location = null;
346352
if (includeTestLocationInResult) {
@@ -402,14 +408,9 @@ const makeTestResults = (
402408
// Return a string that identifies the test (concat of parent describe block
403409
// names + test title)
404410
export const getTestID = (test: Circus.TestEntry): string => {
405-
const titles = [];
406-
let parent: Circus.TestEntry | Circus.DescribeBlock | undefined = test;
407-
do {
408-
titles.unshift(parent.name);
409-
} while ((parent = parent.parent));
410-
411-
titles.shift(); // remove TOP_DESCRIBE_BLOCK_NAME
412-
return titles.join(' ');
411+
const testNamesPath = getTestNamesPath(test);
412+
testNamesPath.shift(); // remove TOP_DESCRIBE_BLOCK_NAME
413+
return testNamesPath.join(' ');
413414
};
414415

415416
const _getError = (
@@ -464,6 +465,29 @@ export function invariant(
464465
}
465466
}
466467

468+
type TestDescription = {
469+
ancestorTitles: Array<string>;
470+
fullName: string;
471+
title: string;
472+
};
473+
474+
const resolveTestCaseStartInfo = (
475+
testNamesPath: Circus.TestNamesPath,
476+
): TestDescription => {
477+
const ancestorTitles = testNamesPath.filter(
478+
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
479+
);
480+
const fullName = ancestorTitles.join(' ');
481+
const title = testNamesPath[testNamesPath.length - 1];
482+
// remove title
483+
ancestorTitles.pop();
484+
return {
485+
ancestorTitles,
486+
fullName,
487+
title,
488+
};
489+
};
490+
467491
export const parseSingleTestResult = (
468492
testResult: Circus.TestResult,
469493
): AssertionResult => {
@@ -478,24 +502,36 @@ export const parseSingleTestResult = (
478502
status = 'passed';
479503
}
480504

481-
const ancestorTitles = testResult.testPath.filter(
482-
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
505+
const {ancestorTitles, fullName, title} = resolveTestCaseStartInfo(
506+
testResult.testPath,
483507
);
484-
const title = ancestorTitles.pop();
485508

486509
return {
487510
ancestorTitles,
488511
duration: testResult.duration,
489512
failureDetails: testResult.errorsDetailed,
490513
failureMessages: Array.from(testResult.errors),
491-
fullName: title
492-
? ancestorTitles.concat(title).join(' ')
493-
: ancestorTitles.join(' '),
514+
fullName,
494515
invocations: testResult.invocations,
495516
location: testResult.location,
496517
numPassingAsserts: testResult.numPassingAsserts,
497518
retryReasons: Array.from(testResult.retryReasons),
498519
status,
499-
title: testResult.testPath[testResult.testPath.length - 1],
520+
title,
521+
};
522+
};
523+
524+
export const createTestCaseStartInfo = (
525+
test: Circus.TestEntry,
526+
): Circus.TestCaseStartInfo => {
527+
const testPath = getTestNamesPath(test);
528+
const {ancestorTitles, fullName, title} = resolveTestCaseStartInfo(testPath);
529+
530+
return {
531+
ancestorTitles,
532+
fullName,
533+
mode: test.mode,
534+
startedAt: test.startedAt,
535+
title,
500536
};
501537
};

packages/jest-core/src/ReporterDispatcher.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
TestContext,
1414
TestResult,
1515
} from '@jest/test-result';
16+
import type {Circus} from '@jest/types';
1617
import type {ReporterConstructor} from './TestScheduler';
1718

1819
export default class ReporterDispatcher {
@@ -69,6 +70,17 @@ export default class ReporterDispatcher {
6970
}
7071
}
7172

73+
async onTestCaseStart(
74+
test: Test,
75+
testCaseStartInfo: Circus.TestCaseStartInfo,
76+
): Promise<void> {
77+
for (const reporter of this._reporters) {
78+
if (reporter.onTestCaseStart) {
79+
await reporter.onTestCaseStart(test, testCaseStartInfo);
80+
}
81+
}
82+
}
83+
7284
async onTestCaseResult(
7385
test: Test,
7486
testCaseResult: TestCaseResult,

packages/jest-core/src/TestScheduler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,13 @@ class TestScheduler {
257257
testRunner.on('test-file-failure', ([test, error]) =>
258258
onFailure(test, error),
259259
),
260+
testRunner.on(
261+
'test-case-start',
262+
([testPath, testCaseStartInfo]) => {
263+
const test: Test = {context, path: testPath};
264+
this._dispatcher.onTestCaseStart(test, testCaseStartInfo);
265+
},
266+
),
260267
testRunner.on(
261268
'test-case-result',
262269
([testPath, testCaseResult]) => {

packages/jest-reporters/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
TestContext,
1313
TestResult,
1414
} from '@jest/test-result';
15-
import type {Config} from '@jest/types';
15+
import type {Circus, Config} from '@jest/types';
1616

1717
export type ReporterOnStartOptions = {
1818
estimatedTime: number;
@@ -30,6 +30,10 @@ export interface Reporter {
3030
testResult: TestResult,
3131
aggregatedResult: AggregatedResult,
3232
) => Promise<void> | void;
33+
readonly onTestCaseStart?: (
34+
test: Test,
35+
testCaseStartInfo: Circus.TestCaseStartInfo,
36+
) => Promise<void> | void;
3337
readonly onTestCaseResult?: (
3438
test: Test,
3539
testCaseResult: TestCaseResult,

0 commit comments

Comments
 (0)