diff --git a/CHANGELOG.md b/CHANGELOG.md index ca3b22b37435..fc857b12fcd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[expect, jest-circus, @jest/types]` Implement `numPassingAsserts` of testResults to track the number of passing asserts in a test ([#13795](https://github.com/facebook/jest/pull/13795)) - `[jest-core]` Add newlines to JSON output ([#13817](https://github.com/facebook/jest/pull/13817)) ### Fixes diff --git a/e2e/__tests__/__snapshots__/customReportersOnCircus.test.ts.snap b/e2e/__tests__/__snapshots__/customReportersOnCircus.test.ts.snap new file mode 100644 index 000000000000..0867a8829991 --- /dev/null +++ b/e2e/__tests__/__snapshots__/customReportersOnCircus.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Custom Reporters Integration on jest-circus valid failing assertion counts for adding reporters 1`] = ` +"onTestCaseResult: adds fail, status: failed, numExpectations: 0 +onTestFileResult testCaseResult 0: adds fail, status: failed, numExpectations: 0" +`; + +exports[`Custom Reporters Integration on jest-circus valid passing assertion counts for adding reporters 1`] = ` +"onTestCaseResult: adds ok, status: passed, numExpectations: 3 +onTestFileResult testCaseResult 0: adds ok, status: passed, numExpectations: 3" +`; diff --git a/e2e/__tests__/customReportersOnCircus.test.ts b/e2e/__tests__/customReportersOnCircus.test.ts new file mode 100644 index 000000000000..c30483085a14 --- /dev/null +++ b/e2e/__tests__/customReportersOnCircus.test.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {skipSuiteOnJasmine} from '@jest/test-utils'; +import runJest from '../runJest'; + +skipSuiteOnJasmine(); + +describe('Custom Reporters Integration on jest-circus', () => { + test('valid passing assertion counts for adding reporters', () => { + const {stdout} = runJest('custom-reporters', [ + '--config', + JSON.stringify({ + reporters: [ + 'default', + '/reporters/AssertionCountsReporter.js', + ], + }), + 'add.test.js', + ]); + + expect(stdout).toMatchSnapshot(); + }); + + test('valid failing assertion counts for adding reporters', () => { + const {stdout} = runJest('custom-reporters', [ + '--config', + JSON.stringify({ + reporters: [ + 'default', + '/reporters/AssertionCountsReporter.js', + ], + }), + 'addFail.test.js', + ]); + + expect(stdout).toMatchSnapshot(); + }); +}); diff --git a/e2e/custom-reporters/reporters/AssertionCountsReporter.js b/e2e/custom-reporters/reporters/AssertionCountsReporter.js new file mode 100644 index 000000000000..f5c5ac42d2c1 --- /dev/null +++ b/e2e/custom-reporters/reporters/AssertionCountsReporter.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +class AssertionCountsReporter { + onTestFileResult(test, testResult, aggregatedResult) { + testResult.testResults.forEach((testCaseResult, index) => { + console.log( + `onTestFileResult testCaseResult ${index}: ${testCaseResult.title}, ` + + `status: ${testCaseResult.status}, ` + + `numExpectations: ${testCaseResult.numPassingAsserts}`, + ); + }); + } + onTestCaseResult(test, testCaseResult) { + console.log( + `onTestCaseResult: ${testCaseResult.title}, ` + + `status: ${testCaseResult.status}, ` + + `numExpectations: ${testCaseResult.numPassingAsserts}`, + ); + } +} + +module.exports = AssertionCountsReporter; diff --git a/packages/expect/__typetests__/expect.test.ts b/packages/expect/__typetests__/expect.test.ts index a34f1c28f4ee..b9e32400f54f 100644 --- a/packages/expect/__typetests__/expect.test.ts +++ b/packages/expect/__typetests__/expect.test.ts @@ -77,6 +77,7 @@ expectType( expectType(this.isExpectingAssertions); expectType(this.isExpectingAssertionsError); expectType(this.isNot); + expectType(this.numPassingAsserts); expectType(this.promise); expectType>(this.suppressedErrors); expectType(this.testPath); diff --git a/packages/expect/src/__tests__/assertionCounts.test.ts b/packages/expect/src/__tests__/assertionCounts.test.ts index 0e45883363b3..f3cdfeb1bf18 100644 --- a/packages/expect/src/__tests__/assertionCounts.test.ts +++ b/packages/expect/src/__tests__/assertionCounts.test.ts @@ -44,3 +44,35 @@ describe('.hasAssertions()', () => { it('hasAssertions not leaking to global state', () => {}); }); + +describe('numPassingAsserts', () => { + it('verify the default value of numPassingAsserts', () => { + const {numPassingAsserts} = jestExpect.getState(); + expect(numPassingAsserts).toBe(0); + }); + + it('verify the resetting of numPassingAsserts after a test', () => { + expect('a').toBe('a'); + expect('a').toBe('a'); + // reset state + jestExpect.extractExpectedAssertionsErrors(); + const {numPassingAsserts} = jestExpect.getState(); + expect(numPassingAsserts).toBe(0); + }); + + it('verify the correctness of numPassingAsserts count for passing test', () => { + expect('a').toBe('a'); + expect('a').toBe('a'); + const {numPassingAsserts} = jestExpect.getState(); + expect(numPassingAsserts).toBe(2); + }); + + it('verify the correctness of numPassingAsserts count for failing test', () => { + expect('a').toBe('a'); + try { + expect('a').toBe('b'); + } catch (error) {} + const {numPassingAsserts} = jestExpect.getState(); + expect(numPassingAsserts).toBe(1); + }); +}); diff --git a/packages/expect/src/extractExpectedAssertionsErrors.ts b/packages/expect/src/extractExpectedAssertionsErrors.ts index d56fb2417f26..9af24602d50a 100644 --- a/packages/expect/src/extractExpectedAssertionsErrors.ts +++ b/packages/expect/src/extractExpectedAssertionsErrors.ts @@ -20,6 +20,7 @@ const resetAssertionsLocalState = () => { assertionCalls: 0, expectedAssertionsNumber: null, isExpectingAssertions: false, + numPassingAsserts: 0, }); }; diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 88b913e8f181..7c526815d23b 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -337,6 +337,8 @@ const makeThrowingMatcher = ( } else { getState().suppressedErrors.push(error); } + } else { + getState().numPassingAsserts++; } }; diff --git a/packages/expect/src/jestMatchersObject.ts b/packages/expect/src/jestMatchersObject.ts index 2cb600be6772..9272e4feed9a 100644 --- a/packages/expect/src/jestMatchersObject.ts +++ b/packages/expect/src/jestMatchersObject.ts @@ -29,6 +29,7 @@ if (!Object.prototype.hasOwnProperty.call(globalThis, JEST_MATCHERS_OBJECT)) { assertionCalls: 0, expectedAssertionsNumber: null, isExpectingAssertions: false, + numPassingAsserts: 0, suppressedErrors: [], // errors that are not thrown immediately. }; Object.defineProperty(globalThis, JEST_MATCHERS_OBJECT, { diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 2f449b124d94..f3687b5597cb 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -65,6 +65,7 @@ export interface MatcherState { isExpectingAssertions: boolean; isExpectingAssertionsError?: Error; isNot?: boolean; + numPassingAsserts: number; promise?: string; suppressedErrors: Array; testPath?: string; diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts index 4556c55185ae..4108fe133547 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts @@ -178,7 +178,7 @@ export const runAndTransformResultsToJestFormat = async ({ : ancestorTitles.join(' '), invocations: testResult.invocations, location: testResult.location, - numPassingAsserts: 0, + numPassingAsserts: testResult.numPassingAsserts, retryReasons: testResult.retryReasons, status, title: testResult.testPath[testResult.testPath.length - 1], @@ -238,6 +238,7 @@ const eventHandler = async (event: Circus.Event) => { break; } case 'test_done': { + event.test.numPassingAsserts = jestExpect.getState().numPassingAsserts; _addSuppressedErrors(event.test); _addExpectedAssertionErrors(event.test); break; diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 7eeb2dfb2f3a..d45bf21e91f5 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -78,6 +78,7 @@ export const makeTest = ( invocations: 0, mode, name: convertDescriptorToString(name), + numPassingAsserts: 0, parent, retryReasons: [], seenDone: false, @@ -363,6 +364,7 @@ export const makeSingleTestResult = ( errorsDetailed, invocations: test.invocations, location, + numPassingAsserts: test.numPassingAsserts, retryReasons: test.retryReasons.map(_getError).map(getErrorStack), status, testPath: Array.from(testPath), @@ -484,7 +486,7 @@ export const parseSingleTestResult = ( : ancestorTitles.join(' '), invocations: testResult.invocations, location: testResult.location, - numPassingAsserts: 0, + numPassingAsserts: testResult.numPassingAsserts, retryReasons: Array.from(testResult.retryReasons), status, title: testResult.testPath[testResult.testPath.length - 1], diff --git a/packages/jest-types/__typetests__/expect.test.ts b/packages/jest-types/__typetests__/expect.test.ts index 79b68993379b..e1178fc54b74 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -446,6 +446,7 @@ expectType( expectType(this.isExpectingAssertions); expectType(this.isExpectingAssertionsError); expectType(this.isNot); + expectType(this.numPassingAsserts); expectType(this.promise); expectType>(this.suppressedErrors); expectType(this.testPath); diff --git a/packages/jest-types/src/Circus.ts b/packages/jest-types/src/Circus.ts index 7ed5023d6caf..c94c74a20e70 100644 --- a/packages/jest-types/src/Circus.ts +++ b/packages/jest-types/src/Circus.ts @@ -185,6 +185,7 @@ export type TestResult = { invocations: number; status: TestStatus; location?: {column: number; line: number} | null; + numPassingAsserts: number; retryReasons: Array; testPath: Array; }; @@ -245,6 +246,7 @@ export type TestEntry = { mode: TestMode; concurrent: boolean; name: TestName; + numPassingAsserts: number; parent: DescribeBlock; startedAt?: number | null; duration?: number | null;