From 4612898ae5490dafe09174f97e4d9ade2cd0ac7c Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Sun, 11 Oct 2020 23:16:36 +0530 Subject: [PATCH 01/18] fix: 'done' should not be called more than once Update CHANGELOG.md Co-authored-by: Simen Bekkhus Update packages/jest-circus/src/utils.ts Co-authored-by: Simen Bekkhus Apply suggestions from code review Co-authored-by: Simen Bekkhus Update packages/jest-circus/src/utils.ts Co-authored-by: Simen Bekkhus move logic outside promise migrate `seenDone` from global state to local update snapshot rm `seenDone` reset Apply suggestions from code review Co-authored-by: Simen Bekkhus fix linting modify errorstack and test snapshot update snapshot move seenDone check to after reason move seenDone check revert position change lint fix test --- CHANGELOG.md | 3 +++ .../__snapshots__/callDoneTwice.test.ts.snap | 19 +++++++++++++++++++ .../circusDeclarationErrors.test.ts.snap | 4 ++-- e2e/__tests__/callDoneTwice.test.ts | 18 ++++++++++++++++++ e2e/call-done-twice/__tests__/index.test.js | 10 ++++++++++ e2e/call-done-twice/package.json | 5 +++++ packages/jest-circus/src/eventHandler.ts | 9 ++++++++- packages/jest-circus/src/utils.ts | 12 ++++++++++++ packages/jest-types/src/Circus.ts | 2 ++ 9 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap create mode 100644 e2e/__tests__/callDoneTwice.test.ts create mode 100644 e2e/call-done-twice/__tests__/index.test.js create mode 100644 e2e/call-done-twice/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 692ac1876000..31e5abebed2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624)) - `[jest-config]` [**BREAKING**] Default to Node testing environment instead of browser (JSDOM) ([#9874](https://github.com/facebook/jest/pull/9874)) - `[jest-config]` [**BREAKING**] Use `jest-circus` as default test runner ([#10686](https://github.com/facebook/jest/pull/10686)) - `[jest-config, jest-runtime]` Support ESM for files other than `.js` and `.mjs` ([#10823](https://github.com/facebook/jest/pull/10823)) @@ -40,6 +41,8 @@ - `[jest-worker]` [**BREAKING**] Use named exports ([#10623] (https://github.com/facebook/jest/pull/10623)) - `[pretty-format]` [**BREAKING**] Convert to ES Modules ([#10515](https://github.com/facebook/jest/pull/10515)) +### Fixes + ### Chore & Maintenance - `[*]` [**BREAKING**] Only support Node LTS releases and Node 15 ([#10685](https://github.com/facebook/jest/pull/10685)) diff --git a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap new file mode 100644 index 000000000000..aaf2cc16af60 --- /dev/null +++ b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`\`done()\` should not be called more than once 1`] = ` +FAIL __tests__/index.test.js + ✕ \`done()\` should not be called more than once + + ● \`done()\` should not be called more than once + + Expected done to be called once, but it was called multiple times. + + 7 | test('\`done()\` should not be called more than once', done => { + 8 | done(); + > 9 | done(); + | ^ + 10 | }); + 11 | + + at Object.done (__tests__/index.test.js:9:3) +`; diff --git a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap index 1d1d382ab232..7d8c07ab197b 100644 --- a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap +++ b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap @@ -16,7 +16,7 @@ FAIL __tests__/asyncDefinition.test.js 14 | }); 15 | }); - at eventHandler (../../packages/jest-circus/build/eventHandler.js:144:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:143:11) at test (__tests__/asyncDefinition.test.js:12:5) ● Test suite failed to run @@ -46,7 +46,7 @@ FAIL __tests__/asyncDefinition.test.js 20 | }); 21 | - at eventHandler (../../packages/jest-circus/build/eventHandler.js:144:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:143:11) at test (__tests__/asyncDefinition.test.js:18:3) ● Test suite failed to run diff --git a/e2e/__tests__/callDoneTwice.test.ts b/e2e/__tests__/callDoneTwice.test.ts new file mode 100644 index 000000000000..96679bfebcb6 --- /dev/null +++ b/e2e/__tests__/callDoneTwice.test.ts @@ -0,0 +1,18 @@ +/** + * 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 wrap from 'jest-snapshot-serializer-raw'; +import {skipSuiteOnJasmine} from '@jest/test-utils'; +import runJest from '../runJest'; +import {extractSummary} from '../Utils'; + +skipSuiteOnJasmine(); +test('`done()` should not be called more than once', () => { + const {exitCode, stderr} = runJest('call-done-twice'); + const {rest} = extractSummary(stderr); + expect(wrap(rest)).toMatchSnapshot(); + expect(exitCode).toBe(1); +}); diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js new file mode 100644 index 000000000000..69f6429602ef --- /dev/null +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -0,0 +1,10 @@ +/** + * 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. + */ +test('`done()` should not be called more than once', done => { + done(); + done(); +}); diff --git a/e2e/call-done-twice/package.json b/e2e/call-done-twice/package.json new file mode 100644 index 000000000000..e22eaf20573d --- /dev/null +++ b/e2e/call-done-twice/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-circus/src/eventHandler.ts b/packages/jest-circus/src/eventHandler.ts index af46770bcc62..d66dc3bfc971 100644 --- a/packages/jest-circus/src/eventHandler.ts +++ b/packages/jest-circus/src/eventHandler.ts @@ -113,7 +113,14 @@ const eventHandler: Circus.EventHandler = ( } const parent = currentDescribeBlock; - currentDescribeBlock.hooks.push({asyncError, fn, parent, timeout, type}); + currentDescribeBlock.hooks.push({ + asyncError, + fn, + parent, + seenDone: false, + timeout, + type, + }); break; } case 'add_test': { diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index ae8a6695eead..15e5fe2692fd 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -70,6 +70,7 @@ export const makeTest = ( mode, name: convertDescriptorToString(name), parent, + seenDone: false, startedAt: null, status: null, timeout, @@ -189,9 +190,20 @@ export const callAsyncCircusFn = ( // soon as `done` called. if (takesDoneCallback(fn)) { let returnedValue: unknown = undefined; + const done = (reason?: Error | string): void => { + if (testOrHook.seenDone) { + throw new ErrorWithStack( + 'Expected done to be called once, but it was called multiple times.', + done, + ); + } else { + testOrHook.seenDone = true; + } + // We need to keep a stack here before the promise tick const errorAtDone = new ErrorWithStack(undefined, done); + // Use `Promise.resolve` to allow the event loop to go a single tick in case `done` is called synchronously Promise.resolve().then(() => { if (returnedValue !== undefined) { diff --git a/packages/jest-types/src/Circus.ts b/packages/jest-types/src/Circus.ts index 3dab1d8d7abe..d93e653d7822 100644 --- a/packages/jest-types/src/Circus.ts +++ b/packages/jest-types/src/Circus.ts @@ -28,6 +28,7 @@ export type Hook = { fn: HookFn; type: HookType; parent: DescribeBlock; + seenDone: boolean; timeout: number | undefined | null; }; @@ -238,6 +239,7 @@ export type TestEntry = { parent: DescribeBlock; startedAt?: number | null; duration?: number | null; + seenDone: boolean; status?: TestStatus | null; // whether the test has been skipped or run already timeout?: number; }; From b6507c8ed16e5bb4b0621248b90debd7af7f7884 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Mon, 7 Dec 2020 23:33:13 +0530 Subject: [PATCH 02/18] fix import order --- e2e/__tests__/callDoneTwice.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/__tests__/callDoneTwice.test.ts b/e2e/__tests__/callDoneTwice.test.ts index 96679bfebcb6..10d84ffc1ddf 100644 --- a/e2e/__tests__/callDoneTwice.test.ts +++ b/e2e/__tests__/callDoneTwice.test.ts @@ -6,8 +6,8 @@ */ import wrap from 'jest-snapshot-serializer-raw'; import {skipSuiteOnJasmine} from '@jest/test-utils'; -import runJest from '../runJest'; import {extractSummary} from '../Utils'; +import runJest from '../runJest'; skipSuiteOnJasmine(); test('`done()` should not be called more than once', () => { From f4253ca6c931cbbbc14c64858940a27ff06f881f Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Mon, 7 Dec 2020 23:37:00 +0530 Subject: [PATCH 03/18] fix snapshot line number --- .../__snapshots__/circusDeclarationErrors.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap index 7d8c07ab197b..a0a0d2c00d7d 100644 --- a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap +++ b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap @@ -16,7 +16,7 @@ FAIL __tests__/asyncDefinition.test.js 14 | }); 15 | }); - at eventHandler (../../packages/jest-circus/build/eventHandler.js:143:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:145:11) at test (__tests__/asyncDefinition.test.js:12:5) ● Test suite failed to run @@ -46,7 +46,7 @@ FAIL __tests__/asyncDefinition.test.js 20 | }); 21 | - at eventHandler (../../packages/jest-circus/build/eventHandler.js:143:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:145:11) at test (__tests__/asyncDefinition.test.js:18:3) ● Test suite failed to run From 1b2b07d38c40047e22a9ea8e5ecf8c56a354e081 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan <40818234+flozender@users.noreply.github.com> Date: Tue, 8 Dec 2020 21:08:01 +0530 Subject: [PATCH 04/18] Update CHANGELOG.md Co-authored-by: Simen Bekkhus --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9cd2ca74cd5..6f5aa2bd24a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,8 +42,6 @@ - `[jest-worker]` [**BREAKING**] Use named exports ([#10623] (https://github.com/facebook/jest/pull/10623)) - `[pretty-format]` [**BREAKING**] Convert to ES Modules ([#10515](https://github.com/facebook/jest/pull/10515)) -### Fixes - ### Chore & Maintenance - `[*]` [**BREAKING**] Only support Node LTS releases and Node 15 ([#10685](https://github.com/facebook/jest/pull/10685)) From 300ab59509b818efa4fadbd352df9125a2e3c862 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Tue, 8 Dec 2020 21:16:25 +0530 Subject: [PATCH 05/18] replace `then` with `await` --- .../src/__tests__/SearchSource.test.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/jest-core/src/__tests__/SearchSource.test.ts b/packages/jest-core/src/__tests__/SearchSource.test.ts index af43fd464335..eccd32d78c45 100644 --- a/packages/jest-core/src/__tests__/SearchSource.test.ts +++ b/packages/jest-core/src/__tests__/SearchSource.test.ts @@ -415,7 +415,7 @@ describe('SearchSource', () => { ); const rootPath = path.join(rootDir, 'root.js'); - beforeEach(done => { + beforeEach(async () => { const {options: config} = normalize( { haste: { @@ -435,12 +435,11 @@ describe('SearchSource', () => { }, {} as Config.Argv, ); - Runtime.createContext(config, {maxWorkers, watchman: false}).then( - context => { - searchSource = new SearchSource(context); - done(); - }, - ); + const context = await Runtime.createContext(config, { + maxWorkers, + watchman: false, + }); + searchSource = new SearchSource(context); }); it('makes sure a file is related to itself', () => { @@ -477,7 +476,7 @@ describe('SearchSource', () => { }); describe('findRelatedTestsFromPattern', () => { - beforeEach(done => { + beforeEach(async () => { const {options: config} = normalize( { moduleFileExtensions: ['js', 'jsx', 'foobar'], @@ -487,12 +486,11 @@ describe('SearchSource', () => { }, {} as Config.Argv, ); - Runtime.createContext(config, {maxWorkers, watchman: false}).then( - context => { - searchSource = new SearchSource(context); - done(); - }, - ); + const context = await Runtime.createContext(config, { + maxWorkers, + watchman: false, + }); + searchSource = new SearchSource(context); }); it('returns empty search result for empty input', () => { From d507eb1b896791cbd5015accafc05280ac90f2f3 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Tue, 8 Dec 2020 22:57:17 +0530 Subject: [PATCH 06/18] move seenDone check after environment teardown check --- packages/jest-circus/src/utils.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 15e5fe2692fd..1ea4d66ba3b6 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -192,15 +192,6 @@ export const callAsyncCircusFn = ( let returnedValue: unknown = undefined; const done = (reason?: Error | string): void => { - if (testOrHook.seenDone) { - throw new ErrorWithStack( - 'Expected done to be called once, but it was called multiple times.', - done, - ); - } else { - testOrHook.seenDone = true; - } - // We need to keep a stack here before the promise tick const errorAtDone = new ErrorWithStack(undefined, done); @@ -234,6 +225,15 @@ export const callAsyncCircusFn = ( throw errorAsErrorObject; } + if (testOrHook.seenDone) { + throw new ErrorWithStack( + 'Expected done to be called once, but it was called multiple times.', + done, + ); + } else { + testOrHook.seenDone = true; + } + return reason ? reject(errorAsErrorObject) : resolve(); }); }; From f5e61d3e19bb114c04f4aec83db730c0dab84863 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Tue, 8 Dec 2020 23:49:37 +0530 Subject: [PATCH 07/18] add `completed` to `seenDone` check --- packages/jest-circus/src/utils.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 1ea4d66ba3b6..edf7f6ac57b8 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -192,6 +192,14 @@ export const callAsyncCircusFn = ( let returnedValue: unknown = undefined; const done = (reason?: Error | string): void => { + if (!completed && testOrHook.seenDone) { + throw new ErrorWithStack( + 'Expected done to be called once, but it was called multiple times.', + done, + ); + } else { + testOrHook.seenDone = true; + } // We need to keep a stack here before the promise tick const errorAtDone = new ErrorWithStack(undefined, done); @@ -225,15 +233,6 @@ export const callAsyncCircusFn = ( throw errorAsErrorObject; } - if (testOrHook.seenDone) { - throw new ErrorWithStack( - 'Expected done to be called once, but it was called multiple times.', - done, - ); - } else { - testOrHook.seenDone = true; - } - return reason ? reject(errorAsErrorObject) : resolve(); }); }; From e03d21102dbe99fb7a7414307bbc6d7cd15e02d2 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 12:15:54 +0530 Subject: [PATCH 08/18] add reason to message --- packages/jest-circus/src/utils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index edf7f6ac57b8..031b7cce7bbd 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -193,10 +193,14 @@ export const callAsyncCircusFn = ( const done = (reason?: Error | string): void => { if (!completed && testOrHook.seenDone) { - throw new ErrorWithStack( - 'Expected done to be called once, but it was called multiple times.', - done, - ); + let message = + 'Expected done to be called once, but it was called multiple times.'; + + if (reason) { + message += reason.toString(); + } + + throw new ErrorWithStack(message, done); } else { testOrHook.seenDone = true; } From 3dd1f5e2c6b7596732a5bf1ed796337eab31728f Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 12:55:33 +0530 Subject: [PATCH 09/18] reset `seenDone` before each test and hook --- e2e/call-done-twice/__tests__/index.test.js | 17 ++++++++++++++--- e2e/done-in-hooks/__tests__/index.test.js | 16 ++++++++++++++++ e2e/done-in-hooks/package.json | 5 +++++ packages/jest-circus/src/run.ts | 2 ++ packages/jest-circus/src/utils.ts | 3 +-- 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 e2e/done-in-hooks/__tests__/index.test.js create mode 100644 e2e/done-in-hooks/package.json diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js index 69f6429602ef..d342ff560d09 100644 --- a/e2e/call-done-twice/__tests__/index.test.js +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -4,7 +4,18 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -test('`done()` should not be called more than once', done => { - done(); - done(); +describe('`done()` called more than once', () => { + it('should fail', done => { + done(); + done(); + }); + + it('should fail inside a promise', done => { + Promise.resolve() + .then(() => { + done(); + done(); + }) + .catch(err => err); + }); }); diff --git a/e2e/done-in-hooks/__tests__/index.test.js b/e2e/done-in-hooks/__tests__/index.test.js new file mode 100644 index 000000000000..b042dec1bf83 --- /dev/null +++ b/e2e/done-in-hooks/__tests__/index.test.js @@ -0,0 +1,16 @@ +/** + * 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. + * + */ +describe('`done()` should work with hooks', done => { + beforeEach(done => done()); + it('foo', () => { + expect('foo').toMatch('foo'); + }); + it('bar', () => { + expect('bar').toMatch('bar'); + }); +}); diff --git a/e2e/done-in-hooks/package.json b/e2e/done-in-hooks/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/done-in-hooks/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index d49cfdcb31bf..7fdeb05b7639 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -149,6 +149,7 @@ const _callCircusHook = async ({ testContext?: Circus.TestContext; }): Promise => { await dispatch({hook, name: 'hook_start'}); + hook.seenDone = false; const timeout = hook.timeout || getState().testTimeout; try { @@ -167,6 +168,7 @@ const _callCircusTest = async ( testContext: Circus.TestContext, ): Promise => { await dispatch({name: 'test_fn_start', test}); + test.seenDone = false; const timeout = test.timeout || getState().testTimeout; invariant(test.fn, `Tests with no 'fn' should have 'mode' set to 'skipped'`); diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 031b7cce7bbd..3b9525625f35 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -197,7 +197,7 @@ export const callAsyncCircusFn = ( 'Expected done to be called once, but it was called multiple times.'; if (reason) { - message += reason.toString(); + message += ' Reason: ' + reason.toString(); } throw new ErrorWithStack(message, done); @@ -218,7 +218,6 @@ export const callAsyncCircusFn = ( } let errorAsErrorObject: Error; - if (checkIsError(reason)) { errorAsErrorObject = reason; } else { From 9364669d6956cbbb77978198180e10028e30a44d Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 12:55:33 +0530 Subject: [PATCH 10/18] reset `seenDone` before each test and hook --- e2e/__tests__/doneInHooks.test.ts | 13 +++++++++++++ e2e/call-done-twice/__tests__/index.test.js | 17 ++++++++++++++--- e2e/done-in-hooks/__tests__/index.test.js | 16 ++++++++++++++++ e2e/done-in-hooks/package.json | 5 +++++ packages/jest-circus/src/run.ts | 2 ++ packages/jest-circus/src/utils.ts | 3 +-- 6 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 e2e/__tests__/doneInHooks.test.ts create mode 100644 e2e/done-in-hooks/__tests__/index.test.js create mode 100644 e2e/done-in-hooks/package.json diff --git a/e2e/__tests__/doneInHooks.test.ts b/e2e/__tests__/doneInHooks.test.ts new file mode 100644 index 000000000000..7d86d63673d0 --- /dev/null +++ b/e2e/__tests__/doneInHooks.test.ts @@ -0,0 +1,13 @@ +/** + * 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 runJest from '../runJest'; + +test('`done()` works properly in hooks', () => { + const {exitCode} = runJest('done-in-hooks'); + expect(exitCode).toEqual(0); +}); diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js index 69f6429602ef..d342ff560d09 100644 --- a/e2e/call-done-twice/__tests__/index.test.js +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -4,7 +4,18 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -test('`done()` should not be called more than once', done => { - done(); - done(); +describe('`done()` called more than once', () => { + it('should fail', done => { + done(); + done(); + }); + + it('should fail inside a promise', done => { + Promise.resolve() + .then(() => { + done(); + done(); + }) + .catch(err => err); + }); }); diff --git a/e2e/done-in-hooks/__tests__/index.test.js b/e2e/done-in-hooks/__tests__/index.test.js new file mode 100644 index 000000000000..b042dec1bf83 --- /dev/null +++ b/e2e/done-in-hooks/__tests__/index.test.js @@ -0,0 +1,16 @@ +/** + * 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. + * + */ +describe('`done()` should work with hooks', done => { + beforeEach(done => done()); + it('foo', () => { + expect('foo').toMatch('foo'); + }); + it('bar', () => { + expect('bar').toMatch('bar'); + }); +}); diff --git a/e2e/done-in-hooks/package.json b/e2e/done-in-hooks/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/done-in-hooks/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index d49cfdcb31bf..7fdeb05b7639 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -149,6 +149,7 @@ const _callCircusHook = async ({ testContext?: Circus.TestContext; }): Promise => { await dispatch({hook, name: 'hook_start'}); + hook.seenDone = false; const timeout = hook.timeout || getState().testTimeout; try { @@ -167,6 +168,7 @@ const _callCircusTest = async ( testContext: Circus.TestContext, ): Promise => { await dispatch({name: 'test_fn_start', test}); + test.seenDone = false; const timeout = test.timeout || getState().testTimeout; invariant(test.fn, `Tests with no 'fn' should have 'mode' set to 'skipped'`); diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 031b7cce7bbd..3b9525625f35 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -197,7 +197,7 @@ export const callAsyncCircusFn = ( 'Expected done to be called once, but it was called multiple times.'; if (reason) { - message += reason.toString(); + message += ' Reason: ' + reason.toString(); } throw new ErrorWithStack(message, done); @@ -218,7 +218,6 @@ export const callAsyncCircusFn = ( } let errorAsErrorObject: Error; - if (checkIsError(reason)) { errorAsErrorObject = reason; } else { From 1b92362d0e6027367827a3ccdcad2439ec6fcfaa Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan <40818234+flozender@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:41:14 +0530 Subject: [PATCH 11/18] Update packages/jest-circus/src/utils.ts Co-authored-by: Simen Bekkhus --- packages/jest-circus/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 3b9525625f35..f00b134027f1 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -197,7 +197,7 @@ export const callAsyncCircusFn = ( 'Expected done to be called once, but it was called multiple times.'; if (reason) { - message += ' Reason: ' + reason.toString(); + message += ' Reason: ' + prettyFormat(reason, {maxDepth: 3}); } throw new ErrorWithStack(message, done); From 113264f75526174340dc9403c377e2fc5fd498e8 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 16:30:34 +0530 Subject: [PATCH 12/18] reuse error, update snapshot, and add `seenDone` resets --- .../__snapshots__/callDoneTwice.test.ts.snap | 35 ++++++++++++++----- packages/jest-circus/src/eventHandler.ts | 5 +++ packages/jest-circus/src/run.ts | 2 -- packages/jest-circus/src/utils.ts | 14 ++++---- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap index aaf2cc16af60..68c49b421d9e 100644 --- a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap +++ b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap @@ -2,18 +2,35 @@ exports[`\`done()\` should not be called more than once 1`] = ` FAIL __tests__/index.test.js - ✕ \`done()\` should not be called more than once + \`done()\` called more than once + ✕ should fail + ✕ should fail inside a promise - ● \`done()\` should not be called more than once + ● \`done()\` called more than once › should fail Expected done to be called once, but it was called multiple times. - 7 | test('\`done()\` should not be called more than once', done => { - 8 | done(); - > 9 | done(); - | ^ - 10 | }); - 11 | + 8 | it('should fail', done => { + 9 | done(); + > 10 | done(); + | ^ + 11 | }); + 12 | + 13 | it('should fail inside a promise', done => { - at Object.done (__tests__/index.test.js:9:3) + at Object.done (__tests__/index.test.js:10:5) + + ● \`done()\` called more than once › should fail inside a promise + + Expected done to be called once, but it was called multiple times. + + 15 | .then(() => { + 16 | done(); + > 17 | done(); + | ^ + 18 | }) + 19 | .catch(err => err); + 20 | }); + + at done (__tests__/index.test.js:17:9) `; diff --git a/packages/jest-circus/src/eventHandler.ts b/packages/jest-circus/src/eventHandler.ts index d66dc3bfc971..017c72599888 100644 --- a/packages/jest-circus/src/eventHandler.ts +++ b/packages/jest-circus/src/eventHandler.ts @@ -31,6 +31,7 @@ const eventHandler: Circus.EventHandler = ( break; } case 'hook_start': { + event.hook.seenDone = false; break; } case 'start_describe_definition': { @@ -195,6 +196,10 @@ const eventHandler: Circus.EventHandler = ( event.test.invocations += 1; break; } + case 'test_fn_start': { + event.test.seenDone = false; + break; + } case 'test_fn_failure': { const { error, diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index 7fdeb05b7639..d49cfdcb31bf 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -149,7 +149,6 @@ const _callCircusHook = async ({ testContext?: Circus.TestContext; }): Promise => { await dispatch({hook, name: 'hook_start'}); - hook.seenDone = false; const timeout = hook.timeout || getState().testTimeout; try { @@ -168,7 +167,6 @@ const _callCircusTest = async ( testContext: Circus.TestContext, ): Promise => { await dispatch({name: 'test_fn_start', test}); - test.seenDone = false; const timeout = test.timeout || getState().testTimeout; invariant(test.fn, `Tests with no 'fn' should have 'mode' set to 'skipped'`); diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index f00b134027f1..933e65688f83 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -192,20 +192,22 @@ export const callAsyncCircusFn = ( let returnedValue: unknown = undefined; const done = (reason?: Error | string): void => { + // We need to keep a stack here before the promise tick + const errorAtDone = new ErrorWithStack(undefined, done); + if (!completed && testOrHook.seenDone) { - let message = + errorAtDone.message = 'Expected done to be called once, but it was called multiple times.'; if (reason) { - message += ' Reason: ' + prettyFormat(reason, {maxDepth: 3}); + errorAtDone.message += + ' Reason: ' + prettyFormat(reason, {maxDepth: 3}); } - - throw new ErrorWithStack(message, done); + reject(errorAtDone); + throw errorAtDone; } else { testOrHook.seenDone = true; } - // We need to keep a stack here before the promise tick - const errorAtDone = new ErrorWithStack(undefined, done); // Use `Promise.resolve` to allow the event loop to go a single tick in case `done` is called synchronously Promise.resolve().then(() => { From 66d60920af7c9b42d645b31b27380a1a62aafcc0 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 16:49:41 +0530 Subject: [PATCH 13/18] update snapshot --- .../__snapshots__/circusDeclarationErrors.test.ts.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap index a0a0d2c00d7d..f58244619e04 100644 --- a/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap +++ b/e2e/__tests__/__snapshots__/circusDeclarationErrors.test.ts.snap @@ -16,7 +16,7 @@ FAIL __tests__/asyncDefinition.test.js 14 | }); 15 | }); - at eventHandler (../../packages/jest-circus/build/eventHandler.js:145:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:146:11) at test (__tests__/asyncDefinition.test.js:12:5) ● Test suite failed to run @@ -31,7 +31,7 @@ FAIL __tests__/asyncDefinition.test.js 15 | }); 16 | - at eventHandler (../../packages/jest-circus/build/eventHandler.js:113:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:114:11) at afterAll (__tests__/asyncDefinition.test.js:13:5) ● Test suite failed to run @@ -46,7 +46,7 @@ FAIL __tests__/asyncDefinition.test.js 20 | }); 21 | - at eventHandler (../../packages/jest-circus/build/eventHandler.js:145:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:146:11) at test (__tests__/asyncDefinition.test.js:18:3) ● Test suite failed to run @@ -60,6 +60,6 @@ FAIL __tests__/asyncDefinition.test.js 20 | }); 21 | - at eventHandler (../../packages/jest-circus/build/eventHandler.js:113:11) + at eventHandler (../../packages/jest-circus/build/eventHandler.js:114:11) at afterAll (__tests__/asyncDefinition.test.js:19:3) `; From b3929c828db5676b00f72f65ee281d008f487fe8 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 17:11:07 +0530 Subject: [PATCH 14/18] test multiple `done` inside a hook --- .../__snapshots__/callDoneTwice.test.ts.snap | 16 ++++++++++++++++ e2e/call-done-twice/__tests__/index.test.js | 11 +++++++++++ 2 files changed, 27 insertions(+) diff --git a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap index 68c49b421d9e..39515a527d5c 100644 --- a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap +++ b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap @@ -5,6 +5,8 @@ FAIL __tests__/index.test.js \`done()\` called more than once ✕ should fail ✕ should fail inside a promise + multiple \`done()\` inside a hook + ✕ should fail ● \`done()\` called more than once › should fail @@ -33,4 +35,18 @@ FAIL __tests__/index.test.js 20 | }); at done (__tests__/index.test.js:17:9) + + ● multiple \`done()\` inside a hook › should fail + + Expected done to be called once, but it was called multiple times. + + 24 | beforeEach(done => { + 25 | done(); + > 26 | done(); + | ^ + 27 | }); + 28 | + 29 | it('should fail', () => { + + at Object.done (__tests__/index.test.js:26:5) `; diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js index d342ff560d09..56bb4566df51 100644 --- a/e2e/call-done-twice/__tests__/index.test.js +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -19,3 +19,14 @@ describe('`done()` called more than once', () => { .catch(err => err); }); }); + +describe('multiple `done()` inside a hook', () => { + beforeEach(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); From 92a09b26d90410a5c4b2d6cf44e2c7dac6b0ee7d Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 17:23:23 +0530 Subject: [PATCH 15/18] skip on jasmine --- e2e/__tests__/doneInHooks.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/__tests__/doneInHooks.test.ts b/e2e/__tests__/doneInHooks.test.ts index 7d86d63673d0..7f43632e4ad1 100644 --- a/e2e/__tests__/doneInHooks.test.ts +++ b/e2e/__tests__/doneInHooks.test.ts @@ -5,8 +5,10 @@ * LICENSE file in the root directory of this source tree. */ +import {skipSuiteOnJasmine} from '@jest/test-utils'; import runJest from '../runJest'; +skipSuiteOnJasmine(); test('`done()` works properly in hooks', () => { const {exitCode} = runJest('done-in-hooks'); expect(exitCode).toEqual(0); From b661de82ca3770e08aa0c733a6e0f80804169a74 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 18:13:08 +0530 Subject: [PATCH 16/18] add all hooks --- .../__snapshots__/callDoneTwice.test.ts.snap | 51 ++++++++++++++++--- e2e/call-done-twice/__tests__/index.test.js | 35 ++++++++++++- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap index 39515a527d5c..dbab176a469b 100644 --- a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap +++ b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap @@ -2,12 +2,6 @@ exports[`\`done()\` should not be called more than once 1`] = ` FAIL __tests__/index.test.js - \`done()\` called more than once - ✕ should fail - ✕ should fail inside a promise - multiple \`done()\` inside a hook - ✕ should fail - ● \`done()\` called more than once › should fail Expected done to be called once, but it was called multiple times. @@ -36,7 +30,7 @@ FAIL __tests__/index.test.js at done (__tests__/index.test.js:17:9) - ● multiple \`done()\` inside a hook › should fail + ● multiple \`done()\` inside a beforeEach › should fail Expected done to be called once, but it was called multiple times. @@ -49,4 +43,47 @@ FAIL __tests__/index.test.js 29 | it('should fail', () => { at Object.done (__tests__/index.test.js:26:5) + + ● multiple \`done()\` inside a afterEach › should fail + + Expected done to be called once, but it was called multiple times. + + 35 | afterEach(done => { + 36 | done(); + > 37 | done(); + | ^ + 38 | }); + 39 | + 40 | it('should fail', () => { + + at Object.done (__tests__/index.test.js:37:5) + + ● multiple \`done()\` inside a beforeAll › should fail + + Expected done to be called once, but it was called multiple times. + + 46 | beforeAll(done => { + 47 | done(); + > 48 | done(); + | ^ + 49 | }); + 50 | + 51 | it('should fail', () => { + + at done (__tests__/index.test.js:48:5) + + + ● Test suite failed to run + + Expected done to be called once, but it was called multiple times. + + 57 | afterAll(done => { + 58 | done(); + > 59 | done(); + | ^ + 60 | }); + 61 | + 62 | it('should fail', () => { + + at done (__tests__/index.test.js:59:5) `; diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js index 56bb4566df51..ab78bed386e5 100644 --- a/e2e/call-done-twice/__tests__/index.test.js +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -20,7 +20,7 @@ describe('`done()` called more than once', () => { }); }); -describe('multiple `done()` inside a hook', () => { +describe('multiple `done()` inside a beforeEach', () => { beforeEach(done => { done(); done(); @@ -30,3 +30,36 @@ describe('multiple `done()` inside a hook', () => { expect('foo').toMatch('foo'); }); }); + +describe('multiple `done()` inside a afterEach', () => { + afterEach(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); + +describe('multiple `done()` inside a beforeAll', () => { + beforeAll(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); + +describe('multiple `done()` inside a afterAll', () => { + afterAll(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); From 50938cdfd287fbd8078922907a89ecd7b42d6f22 Mon Sep 17 00:00:00 2001 From: Tayeeb Hasan Date: Wed, 9 Dec 2020 18:13:08 +0530 Subject: [PATCH 17/18] add all hooks --- .../__snapshots__/callDoneTwice.test.ts.snap | 109 ++++++++++++------ e2e/call-done-twice/__tests__/index.test.js | 35 +++++- 2 files changed, 107 insertions(+), 37 deletions(-) diff --git a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap index 39515a527d5c..0f75da304438 100644 --- a/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap +++ b/e2e/__tests__/__snapshots__/callDoneTwice.test.ts.snap @@ -1,52 +1,89 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`\`done()\` should not be called more than once 1`] = ` -FAIL __tests__/index.test.js - \`done()\` called more than once - ✕ should fail - ✕ should fail inside a promise - multiple \`done()\` inside a hook - ✕ should fail +FAIL __tests__/index.test.js +● `done()` called more than once › should fail - ● \`done()\` called more than once › should fail + Expected done to be called once, but it was called multiple times. - Expected done to be called once, but it was called multiple times. + 8 | it('should fail', done => { + 9 | done(); + > 10 | done(); + | ^ + 11 | }); + 12 | + 13 | it('should fail inside a promise', done => { - 8 | it('should fail', done => { - 9 | done(); - > 10 | done(); - | ^ - 11 | }); - 12 | - 13 | it('should fail inside a promise', done => { + at Object.done (__tests__/index.test.js:10:5) - at Object.done (__tests__/index.test.js:10:5) +● `done()` called more than once › should fail inside a promise - ● \`done()\` called more than once › should fail inside a promise + Expected done to be called once, but it was called multiple times. - Expected done to be called once, but it was called multiple times. + 15 | .then(() => { + 16 | done(); + > 17 | done(); + | ^ + 18 | }) + 19 | .catch(err => err); + 20 | }); - 15 | .then(() => { - 16 | done(); - > 17 | done(); - | ^ - 18 | }) - 19 | .catch(err => err); - 20 | }); + at done (__tests__/index.test.js:17:9) - at done (__tests__/index.test.js:17:9) +● multiple `done()` inside beforeEach › should fail - ● multiple \`done()\` inside a hook › should fail + Expected done to be called once, but it was called multiple times. - Expected done to be called once, but it was called multiple times. + 24 | beforeEach(done => { + 25 | done(); + > 26 | done(); + | ^ + 27 | }); + 28 | + 29 | it('should fail', () => { - 24 | beforeEach(done => { - 25 | done(); - > 26 | done(); - | ^ - 27 | }); - 28 | - 29 | it('should fail', () => { + at Object.done (__tests__/index.test.js:26:5) - at Object.done (__tests__/index.test.js:26:5) +● multiple `done()` inside afterEach › should fail + + Expected done to be called once, but it was called multiple times. + + 35 | afterEach(done => { + 36 | done(); + > 37 | done(); + | ^ + 38 | }); + 39 | + 40 | it('should fail', () => { + + at Object.done (__tests__/index.test.js:37:5) + +● multiple `done()` inside beforeAll › should fail + + Expected done to be called once, but it was called multiple times. + + 46 | beforeAll(done => { + 47 | done(); + > 48 | done(); + | ^ + 49 | }); + 50 | + 51 | it('should fail', () => { + + at done (__tests__/index.test.js:48:5) + + +● Test suite failed to run + + Expected done to be called once, but it was called multiple times. + + 57 | afterAll(done => { + 58 | done(); + > 59 | done(); + | ^ + 60 | }); + 61 | + 62 | it('should fail', () => { + + at done (__tests__/index.test.js:59:5) `; diff --git a/e2e/call-done-twice/__tests__/index.test.js b/e2e/call-done-twice/__tests__/index.test.js index 56bb4566df51..5f0e25005ff0 100644 --- a/e2e/call-done-twice/__tests__/index.test.js +++ b/e2e/call-done-twice/__tests__/index.test.js @@ -20,7 +20,7 @@ describe('`done()` called more than once', () => { }); }); -describe('multiple `done()` inside a hook', () => { +describe('multiple `done()` inside beforeEach', () => { beforeEach(done => { done(); done(); @@ -30,3 +30,36 @@ describe('multiple `done()` inside a hook', () => { expect('foo').toMatch('foo'); }); }); + +describe('multiple `done()` inside afterEach', () => { + afterEach(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); + +describe('multiple `done()` inside beforeAll', () => { + beforeAll(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); + +describe('multiple `done()` inside afterAll', () => { + afterAll(done => { + done(); + done(); + }); + + it('should fail', () => { + expect('foo').toMatch('foo'); + }); +}); From 7cdb33a2b970050196130f22f91689656a0f8c81 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 22 Dec 2020 12:53:17 +0100 Subject: [PATCH 18/18] Update utils.ts --- packages/jest-circus/src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-circus/src/utils.ts b/packages/jest-circus/src/utils.ts index 933e65688f83..ea0f1858fc60 100644 --- a/packages/jest-circus/src/utils.ts +++ b/packages/jest-circus/src/utils.ts @@ -290,12 +290,12 @@ export const callAsyncCircusFn = ( completed = true; // If timeout is not cleared/unrefed the node process won't exit until // it's resolved. - timeoutID.unref && timeoutID.unref(); + timeoutID.unref?.(); clearTimeout(timeoutID); }) .catch(error => { completed = true; - timeoutID.unref && timeoutID.unref(); + timeoutID.unref?.(); clearTimeout(timeoutID); throw error; });