From bfcfbd4761cef2d0be3427b15d6701433b16116b Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 10 Feb 2019 14:35:57 +0100 Subject: [PATCH 01/11] Fail test suite if describe returns a Promise --- CHANGELOG.md | 2 ++ e2e/__tests__/jasmineAsync.test.ts | 7 ++++++ .../__tests__/promiseDescribeFails.test.js | 14 +++++++++++ packages/jest-circus/src/index.ts | 10 ++++++-- packages/jest-jasmine2/src/jasmine/Env.ts | 11 ++++++-- .../jest-util/src/__tests__/isPromise.test.ts | 25 +++++++++++++++++++ packages/jest-util/src/index.ts | 2 ++ packages/jest-util/src/isPromise.ts | 4 +++ 8 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 e2e/jasmine-async/__tests__/promiseDescribeFails.test.js create mode 100644 packages/jest-util/src/__tests__/isPromise.test.ts create mode 100644 packages/jest-util/src/isPromise.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b35aa33644df..30fdf68889db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - `[jest-config]` Print error information on preset normalization error ([#7935](https://github.com/facebook/jest/pull/7935)) - `[jest-haste-map]` Add `skipPackageJson` option ([#7778](https://github.com/facebook/jest/pull/7778)) - `[jest-get-type]` Add `isPrimitive` function ([#7708](https://github.com/facebook/jest/pull/7708)) +- `[jest-circus/jest-jasmine2]` Fail test suite if describe returns a Promise ([#7852](https://github.com/facebook/jest/pull/7852)) +- `[jest-util]` Add `isPromise` ([#7852](https://github.com/facebook/jest/pull/7852)) ### Fixes diff --git a/e2e/__tests__/jasmineAsync.test.ts b/e2e/__tests__/jasmineAsync.test.ts index 28b4aa827d0d..32cc2e73552d 100644 --- a/e2e/__tests__/jasmineAsync.test.ts +++ b/e2e/__tests__/jasmineAsync.test.ts @@ -83,6 +83,13 @@ describe('async jasmine', () => { expect(json.numPendingTests).toBe(1); }); + it('fails if describe returns a Promise', () => { + const result = runJest('jasmine-async', ['promiseDescribeFails.test.js']); + + expect(result.status).toBe(1); + expect(result.stderr).toMatch(/Tests must be defined synchronously/); + }); + it('throws when not a promise is returned', () => { const result = runWithJson('jasmine-async', ['returningValues.test.js']); const json = result.json; diff --git a/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js b/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js new file mode 100644 index 000000000000..d5dbe1ad9d6a --- /dev/null +++ b/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js @@ -0,0 +1,14 @@ +/** + * 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'; + +describe('Promise describe fails', async () => { + await Promise.resolve(); + it('not declared', () => {}); +}); diff --git a/packages/jest-circus/src/index.ts b/packages/jest-circus/src/index.ts index 788af87df0c0..9a111cd29645 100644 --- a/packages/jest-circus/src/index.ts +++ b/packages/jest-circus/src/index.ts @@ -6,7 +6,7 @@ */ import {bind as bindEach} from 'jest-each'; -import {ErrorWithStack} from 'jest-util'; +import {ErrorWithStack, isPromise} from 'jest-util'; import {Global} from '@jest/types'; import { BlockFn, @@ -63,7 +63,13 @@ const _dispatchDescribe = ( mode, name: 'start_describe_definition', }); - blockFn(); + const describeReturn = blockFn(); + if (isPromise(describeReturn)) { + throw new ErrorWithStack( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.', + describeFn, + ); + } dispatch({blockName, mode, name: 'finish_describe_definition'}); }; diff --git a/packages/jest-jasmine2/src/jasmine/Env.ts b/packages/jest-jasmine2/src/jasmine/Env.ts index 365c72855fbb..b60af2e483ae 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.ts +++ b/packages/jest-jasmine2/src/jasmine/Env.ts @@ -31,7 +31,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* eslint-disable sort-keys */ import {AssertionError} from 'assert'; -import {ErrorWithStack} from 'jest-util'; +import {ErrorWithStack, isPromise} from 'jest-util'; import queueRunner, { Options as QueueRunnerOptions, QueueableFn, @@ -415,12 +415,19 @@ export default function(j$: Jasmine) { currentDeclarationSuite = suite; let declarationError: null | Error = null; + let describeReturnValue: null | Error = null; try { - specDefinitions.call(suite); + describeReturnValue = specDefinitions.call(suite); } catch (e) { declarationError = e; } + if (isPromise(describeReturnValue)) { + declarationError = new Error( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.', + ); + } + if (declarationError) { this.it('encountered a declaration exception', () => { throw declarationError; diff --git a/packages/jest-util/src/__tests__/isPromise.test.ts b/packages/jest-util/src/__tests__/isPromise.test.ts new file mode 100644 index 000000000000..41d9a758a70b --- /dev/null +++ b/packages/jest-util/src/__tests__/isPromise.test.ts @@ -0,0 +1,25 @@ +/** + * 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 isPromise from '../isPromise'; + +describe('not a Promise: ', () => { + test.each([undefined, null, true, 42, '1337', Symbol(), [], {}])( + '%p', + value => { + expect(isPromise(value)).toBe(false); + }, + ); +}); + +test('a resolved Promise', () => { + expect(isPromise(Promise.resolve(42))).toBe(true); +}); + +test('a rejected Promise', () => { + expect(isPromise(Promise.reject().catch(() => {}))).toBe(true); +}); diff --git a/packages/jest-util/src/index.ts b/packages/jest-util/src/index.ts index 4adeb9e3c470..79c797da9d40 100644 --- a/packages/jest-util/src/index.ts +++ b/packages/jest-util/src/index.ts @@ -21,6 +21,7 @@ import ErrorWithStack from './ErrorWithStack'; import getFailedSnapshotTests from './getFailedSnapshotTests'; import installCommonGlobals from './installCommonGlobals'; import isInteractive from './isInteractive'; +import isPromise from './isPromise'; import setGlobal from './setGlobal'; import deepCyclicCopy from './deepCyclicCopy'; import convertDescriptorToString from './convertDescriptorToString'; @@ -46,6 +47,7 @@ export = { getFailedSnapshotTests, installCommonGlobals, isInteractive, + isPromise, pluralize, preRunMessage, replacePathSepForGlob, diff --git a/packages/jest-util/src/isPromise.ts b/packages/jest-util/src/isPromise.ts new file mode 100644 index 000000000000..6ff4f319792c --- /dev/null +++ b/packages/jest-util/src/isPromise.ts @@ -0,0 +1,4 @@ +// see ES2015 spec 25.4.4.5, https://stackoverflow.com/a/38339199 +const isPromise = (candidate: unknown): candidate is Promise => + Promise.resolve(candidate) === candidate; +export default isPromise; From 408cb5334b4d4814a67f42bc7868c0e212039956 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 10 Feb 2019 15:01:02 +0100 Subject: [PATCH 02/11] copyright header --- packages/jest-util/src/isPromise.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/jest-util/src/isPromise.ts b/packages/jest-util/src/isPromise.ts index 6ff4f319792c..5e77f9e2cf7a 100644 --- a/packages/jest-util/src/isPromise.ts +++ b/packages/jest-util/src/isPromise.ts @@ -1,3 +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. + */ + // see ES2015 spec 25.4.4.5, https://stackoverflow.com/a/38339199 const isPromise = (candidate: unknown): candidate is Promise => Promise.resolve(candidate) === candidate; From 6aead76599f7bb88a0ea82199a5b5b22f83ef844 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 10 Feb 2019 16:32:26 +0100 Subject: [PATCH 03/11] fix for overwritten global.Promise --- packages/jest-util/src/isPromise.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/jest-util/src/isPromise.ts b/packages/jest-util/src/isPromise.ts index 5e77f9e2cf7a..1006dac25651 100644 --- a/packages/jest-util/src/isPromise.ts +++ b/packages/jest-util/src/isPromise.ts @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +// capture global.Promise before it may potentially be overwritten +const Promise: any = global.Promise; + // see ES2015 spec 25.4.4.5, https://stackoverflow.com/a/38339199 const isPromise = (candidate: unknown): candidate is Promise => Promise.resolve(candidate) === candidate; From 64fa63b2fe0eca9df319d598857d1c862fae03f2 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 10 Feb 2019 18:06:41 +0100 Subject: [PATCH 04/11] fix e2e test for Node 6 --- .../__tests__/promiseDescribeFails.test.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js b/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js index d5dbe1ad9d6a..f9703543a858 100644 --- a/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js +++ b/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js @@ -8,7 +8,13 @@ 'use strict'; -describe('Promise describe fails', async () => { - await Promise.resolve(); - it('not declared', () => {}); -}); +// TODO after dropping Node 6: Convert to async-await +// describe('Promise describe fails', async () => { +// await Promise.resolve(); +// it('not declared', () => {}); +// }); + +describe('Promise describe fails', () => + Promise.resolve().then(() => { + it('not declared', () => {}); + })); From 7409d622fa7a8454e104d587631b9adf9aaf0e75 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sat, 2 Mar 2019 21:39:47 +0100 Subject: [PATCH 05/11] only console.warn for now --- CHANGELOG.md | 2 +- e2e/__tests__/declarationErrors.test.ts | 26 + e2e/__tests__/jasmineAsync.test.ts | 7 - .../__tests__/describeReturnPromise.test.js | 14 + .../__tests__/describeReturnSomething.test.js | 14 + e2e/declaration-errors/package.json | 5 + .../__tests__/promiseDescribeFails.test.js | 20 - packages/jest-circus/src/index.ts | 14 +- packages/jest-jasmine2/src/jasmine/Env.js | 626 ++++++++++++++++++ packages/jest-jasmine2/src/jasmine/Env.ts | 11 +- 10 files changed, 706 insertions(+), 33 deletions(-) create mode 100644 e2e/__tests__/declarationErrors.test.ts create mode 100644 e2e/declaration-errors/__tests__/describeReturnPromise.test.js create mode 100644 e2e/declaration-errors/__tests__/describeReturnSomething.test.js create mode 100644 e2e/declaration-errors/package.json delete mode 100644 e2e/jasmine-async/__tests__/promiseDescribeFails.test.js create mode 100644 packages/jest-jasmine2/src/jasmine/Env.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fdf68889db..bd655f7171f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - `[jest-config]` Print error information on preset normalization error ([#7935](https://github.com/facebook/jest/pull/7935)) - `[jest-haste-map]` Add `skipPackageJson` option ([#7778](https://github.com/facebook/jest/pull/7778)) - `[jest-get-type]` Add `isPrimitive` function ([#7708](https://github.com/facebook/jest/pull/7708)) -- `[jest-circus/jest-jasmine2]` Fail test suite if describe returns a Promise ([#7852](https://github.com/facebook/jest/pull/7852)) +- `[jest-circus/jest-jasmine2]` Warn if describe returns a value ([#7852](https://github.com/facebook/jest/pull/7852)) - `[jest-util]` Add `isPromise` ([#7852](https://github.com/facebook/jest/pull/7852)) ### Fixes diff --git a/e2e/__tests__/declarationErrors.test.ts b/e2e/__tests__/declarationErrors.test.ts new file mode 100644 index 000000000000..5aa52099d538 --- /dev/null +++ b/e2e/__tests__/declarationErrors.test.ts @@ -0,0 +1,26 @@ +/** + * 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'; + +it('warns if describe returns a Promise', () => { + const result = runJest('declaration-errors', [ + 'describeReturnPromise.test.js', + ]); + + expect(result.status).toBe(0); + expect(result.stdout).toMatch(/Tests must be defined synchronously/); +}); + +it('warns if describe returns something', () => { + const result = runJest('declaration-errors', [ + 'describeReturnSomething.test.js', + ]); + + expect(result.status).toBe(0); + expect(result.stdout).toMatch(/"describe" callback must not return a value/); +}); diff --git a/e2e/__tests__/jasmineAsync.test.ts b/e2e/__tests__/jasmineAsync.test.ts index 32cc2e73552d..28b4aa827d0d 100644 --- a/e2e/__tests__/jasmineAsync.test.ts +++ b/e2e/__tests__/jasmineAsync.test.ts @@ -83,13 +83,6 @@ describe('async jasmine', () => { expect(json.numPendingTests).toBe(1); }); - it('fails if describe returns a Promise', () => { - const result = runJest('jasmine-async', ['promiseDescribeFails.test.js']); - - expect(result.status).toBe(1); - expect(result.stderr).toMatch(/Tests must be defined synchronously/); - }); - it('throws when not a promise is returned', () => { const result = runWithJson('jasmine-async', ['returningValues.test.js']); const json = result.json; diff --git a/e2e/declaration-errors/__tests__/describeReturnPromise.test.js b/e2e/declaration-errors/__tests__/describeReturnPromise.test.js new file mode 100644 index 000000000000..f40120ab34a1 --- /dev/null +++ b/e2e/declaration-errors/__tests__/describeReturnPromise.test.js @@ -0,0 +1,14 @@ +/** + * 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'; + +describe('Promise describe warns', () => { + it('t', () => {}); + return Promise.resolve(); +}); diff --git a/e2e/declaration-errors/__tests__/describeReturnSomething.test.js b/e2e/declaration-errors/__tests__/describeReturnSomething.test.js new file mode 100644 index 000000000000..d1e5e158ae52 --- /dev/null +++ b/e2e/declaration-errors/__tests__/describeReturnSomething.test.js @@ -0,0 +1,14 @@ +/** + * 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'; + +describe('describe return warns', () => { + it('t', () => {}); + return 42; +}); diff --git a/e2e/declaration-errors/package.json b/e2e/declaration-errors/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/declaration-errors/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js b/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js deleted file mode 100644 index f9703543a858..000000000000 --- a/e2e/jasmine-async/__tests__/promiseDescribeFails.test.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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'; - -// TODO after dropping Node 6: Convert to async-await -// describe('Promise describe fails', async () => { -// await Promise.resolve(); -// it('not declared', () => {}); -// }); - -describe('Promise describe fails', () => - Promise.resolve().then(() => { - it('not declared', () => {}); - })); diff --git a/packages/jest-circus/src/index.ts b/packages/jest-circus/src/index.ts index 9a111cd29645..eaf0dd64d10a 100644 --- a/packages/jest-circus/src/index.ts +++ b/packages/jest-circus/src/index.ts @@ -64,12 +64,20 @@ const _dispatchDescribe = ( name: 'start_describe_definition', }); const describeReturn = blockFn(); + + // TODO throw in Jest 25 if (isPromise(describeReturn)) { - throw new ErrorWithStack( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.', - describeFn, + console.warn( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ); + } else if (describeReturn !== undefined) { + console.warn( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', ); } + dispatch({blockName, mode, name: 'finish_describe_definition'}); }; diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js new file mode 100644 index 000000000000..bf48b0346870 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -0,0 +1,626 @@ +/** + * 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. + * + */ +// This file is a heavily modified fork of Jasmine. Original license: +/* +Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/* eslint-disable sort-keys */ + +import {AssertionError} from 'assert'; +import queueRunner from '../queueRunner'; +import treeProcessor from '../treeProcessor'; +import isError from '../isError'; +import assertionErrorMessage from '../assertionErrorMessage'; +import {ErrorWithStack, isPromise} from 'jest-util'; + +export default function(j$) { + function Env(options) { + options = options || {}; + + const self = this; + + let totalSpecsDefined = 0; + + let catchExceptions = true; + + const realSetTimeout = global.setTimeout; + const realClearTimeout = global.clearTimeout; + + const runnableResources = {}; + let currentSpec = null; + const currentlyExecutingSuites = []; + let currentDeclarationSuite = null; + let throwOnExpectationFailure = false; + let random = false; + let seed = null; + + const currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + const currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + const reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone', + ]); + + this.specFilter = function() { + return true; + }; + + let nextSpecId = 0; + const getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + let nextSuiteId = 0; + const getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + const defaultResourcesForRunnable = function(id, parentRunnableId) { + const resources = {spies: []}; + + runnableResources[id] = resources; + }; + + const clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + const beforeAndAfterFns = function(suite) { + return function() { + let afters = []; + let befores = []; + + while (suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite; + } + + return { + befores: befores.reverse(), + afters, + }; + }; + }; + + const getSpecName = function(spec, suite) { + const fullName = [spec.description]; + const suiteFullName = suite.getFullName(); + + if (suiteFullName !== '') { + fullName.unshift(suiteFullName); + } + + return fullName.join(' '); + }; + + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + this.randomizeTests = function(value) { + random = !!value; + }; + + this.randomTests = function() { + return random; + }; + + this.seed = function(value) { + if (value) { + seed = value; + } + return seed; + }; + + function queueRunnerFactory(options) { + options.clearTimeout = realClearTimeout; + options.fail = self.fail; + options.setTimeout = realSetTimeout; + return queueRunner(options); + } + + const topSuite = new j$.Suite({ + id: getNextSuiteId(), + getTestPath() { + return j$.testPath; + }, + }); + + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + const uncaught = err => { + if (currentSpec) { + currentSpec.onException(err); + currentSpec.cancel(); + } else { + console.error('Unhandled error'); + console.error(err.stack); + } + }; + + let oldListenersException; + let oldListenersRejection; + const executionSetup = function() { + // Need to ensure we are the only ones handling these exceptions. + oldListenersException = process.listeners('uncaughtException').slice(); + oldListenersRejection = process.listeners('unhandledRejection').slice(); + + j$.process.removeAllListeners('uncaughtException'); + j$.process.removeAllListeners('unhandledRejection'); + + j$.process.on('uncaughtException', uncaught); + j$.process.on('unhandledRejection', uncaught); + }; + + const executionTeardown = function() { + j$.process.removeListener('uncaughtException', uncaught); + j$.process.removeListener('unhandledRejection', uncaught); + + // restore previous exception handlers + oldListenersException.forEach(listener => { + j$.process.on('uncaughtException', listener); + }); + + oldListenersRejection.forEach(listener => { + j$.process.on('unhandledRejection', listener); + }); + }; + + this.execute = async function(runnablesToRun, suiteTree = topSuite) { + if (!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [suiteTree.id]; + } + } + + if (currentlyExecutingSuites.length === 0) { + executionSetup(); + } + + const lastDeclarationSuite = currentDeclarationSuite; + + await treeProcessor({ + nodeComplete(suite) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + if (suite === topSuite) { + reporter.jasmineDone({ + failedExpectations: topSuite.result.failedExpectations, + }); + } else { + reporter.suiteDone(suite.getResult()); + } + }, + nodeStart(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable( + suite.id, + suite.parentSuite && suite.parentSuite.id, + ); + if (suite === topSuite) { + reporter.jasmineStarted({totalSpecsDefined}); + } else { + reporter.suiteStarted(suite.result); + } + }, + queueRunnerFactory, + runnableIds: runnablesToRun, + tree: suiteTree, + }); + + currentDeclarationSuite = lastDeclarationSuite; + + if (currentlyExecutingSuites.length === 0) { + executionTeardown(); + } + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + this.provideFallbackReporter = function(reporterToAdd) { + reporter.provideFallbackReporter(reporterToAdd); + }; + + this.clearReporters = function() { + reporter.clearReporters(); + }; + + const spyRegistry = new j$.SpyRegistry({ + currentSpies() { + if (!currentRunnable()) { + throw new Error( + 'Spies must be created in a before function or a spec', + ); + } + return runnableResources[currentRunnable().id].spies; + }, + }); + + this.allowRespy = function(allow) { + spyRegistry.allowRespy(allow); + }; + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + const suiteFactory = function(description) { + const suite = new j$.Suite({ + id: getNextSuiteId(), + description, + parentSuite: currentDeclarationSuite, + throwOnExpectationFailure, + getTestPath() { + return j$.testPath; + }, + }); + + return suite; + }; + + this.describe = function(description, specDefinitions) { + const suite = suiteFactory(description); + if (specDefinitions === undefined) { + throw new Error( + `Missing second argument. It must be a callback function.`, + ); + } + if (typeof specDefinitions !== 'function') { + throw new Error( + `Invalid second argument, ${specDefinitions}. It must be a callback function.`, + ); + } + if (specDefinitions.length > 0) { + throw new Error('describe does not expect any arguments'); + } + if (currentDeclarationSuite.markedPending) { + suite.pend(); + } + if (currentDeclarationSuite.markedTodo) { + suite.todo(); + } + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + const suite = suiteFactory(description); + suite.pend(); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + const focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + const suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + const parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + let declarationError = null; + let describeReturnValue = null; + try { + describeReturnValue = specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + // TODO throw in Jest 25: declarationError = new Error + if (isPromise(describeReturnValue)) { + console.warn( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ); + } else if (describeReturnValue !== undefined) { + console.warn( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ); + } + + if (declarationError) { + self.it('encountered a declaration exception', () => { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + const focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (let i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + const specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + const spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + resultCallback: specResultCallback, + getSpecName(spec) { + return getSpecName(spec, suite); + }, + getTestPath() { + return j$.testPath; + }, + onStart: specStarted, + description, + queueRunnerFactory, + userContext() { + return suite.clonedSharedUserContext(); + }, + queueableFn: { + fn, + timeout() { + return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; + }, + }, + throwOnExpectationFailure, + }); + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + if (typeof description !== 'string') { + throw new Error( + `Invalid first argument, ${description}. It must be a string.`, + ); + } + if (fn === undefined) { + throw new Error( + 'Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder.', + ); + } + if (typeof fn !== 'function') { + throw new Error( + `Invalid second argument, ${fn}. It must be a callback function.`, + ); + } + const spec = specFactory( + description, + fn, + currentDeclarationSuite, + timeout, + ); + if (currentDeclarationSuite.markedPending) { + spec.pend(); + } + + // When a test is defined inside another, jasmine will not run it. + // This check throws an error to warn the user about the edge-case. + if (currentSpec !== null) { + throw new Error( + 'Tests cannot be nested. Test `' + + spec.description + + '` cannot run because it is nested within `' + + currentSpec.description + + '`.', + ); + } + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + const spec = this.it.apply(this, arguments); + spec.pend('Temporarily disabled with xit'); + return spec; + }; + + this.todo = function() { + const description = arguments[0]; + if (arguments.length !== 1 || typeof description !== 'string') { + throw new ErrorWithStack( + 'Todo must be called with only a description.', + test.todo, + ); + } + + const spec = specFactory(description, () => {}, currentDeclarationSuite); + spec.todo(); + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.fit = function(description, fn, timeout) { + const spec = specFactory( + description, + fn, + currentDeclarationSuite, + timeout, + ); + currentDeclarationSuite.addChild(spec); + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout() { + return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; + }, + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout() { + return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; + }, + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout() { + return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; + }, + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout() { + return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; + }, + }); + }; + + this.pending = function(message) { + let fullMessage = j$.Spec.pendingSpecExceptionMessage; + if (message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + let checkIsError; + let message; + + if (error instanceof AssertionError) { + checkIsError = false; + message = assertionErrorMessage(error, {expand: j$.Spec.expand}); + } else { + const check = isError(error); + + checkIsError = check.isError; + message = check.message; + } + + const errorAsErrorObject = checkIsError ? error : new Error(message); + const runnable = currentRunnable(); + + if (!runnable) { + errorAsErrorObject.message = + 'Caught error after test environment was torn down\n\n' + + errorAsErrorObject.message; + + throw errorAsErrorObject; + } + + runnable.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message, + error: errorAsErrorObject, + }); + }; + } + + return Env; +} diff --git a/packages/jest-jasmine2/src/jasmine/Env.ts b/packages/jest-jasmine2/src/jasmine/Env.ts index b60af2e483ae..522ae69c6503 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.ts +++ b/packages/jest-jasmine2/src/jasmine/Env.ts @@ -422,9 +422,16 @@ export default function(j$: Jasmine) { declarationError = e; } + // TODO throw in Jest 25: declarationError = new Error if (isPromise(describeReturnValue)) { - declarationError = new Error( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.', + console.warn( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ); + } else if (describeReturnValue !== undefined) { + console.warn( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', ); } From 73f9fed345844b8748e7e76a1c7d1c08ee751906 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sat, 2 Mar 2019 23:57:10 +0100 Subject: [PATCH 06/11] stack trace --- e2e/__tests__/declarationErrors.test.ts | 12 ++++++++++-- packages/jest-circus/src/index.ts | 21 +++++++++++++++++---- packages/jest-jasmine2/src/jasmine/Env.js | 21 +++++++++++++++++---- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/e2e/__tests__/declarationErrors.test.ts b/e2e/__tests__/declarationErrors.test.ts index 5aa52099d538..f0614b6fa72d 100644 --- a/e2e/__tests__/declarationErrors.test.ts +++ b/e2e/__tests__/declarationErrors.test.ts @@ -13,7 +13,10 @@ it('warns if describe returns a Promise', () => { ]); expect(result.status).toBe(0); - expect(result.stdout).toMatch(/Tests must be defined synchronously/); + expect(result.stdout).toContain('Tests must be defined synchronously'); + expect(result.stdout).toContain( + 'at Object.describe (__tests__/describeReturnPromise.test.js', + ); }); it('warns if describe returns something', () => { @@ -22,5 +25,10 @@ it('warns if describe returns something', () => { ]); expect(result.status).toBe(0); - expect(result.stdout).toMatch(/"describe" callback must not return a value/); + expect(result.stdout).toContain( + '"describe" callback must not return a value', + ); + expect(result.stdout).toContain( + 'at Object.describe (__tests__/describeReturnSomething.test.js', + ); }); diff --git a/packages/jest-circus/src/index.ts b/packages/jest-circus/src/index.ts index eaf0dd64d10a..2280ed522faa 100644 --- a/packages/jest-circus/src/index.ts +++ b/packages/jest-circus/src/index.ts @@ -6,6 +6,7 @@ */ import {bind as bindEach} from 'jest-each'; +import {formatExecError} from 'jest-message-util'; import {ErrorWithStack, isPromise} from 'jest-util'; import {Global} from '@jest/types'; import { @@ -68,13 +69,25 @@ const _dispatchDescribe = ( // TODO throw in Jest 25 if (isPromise(describeReturn)) { console.warn( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + formatExecError( + new Error( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); } else if (describeReturn !== undefined) { console.warn( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + formatExecError( + new Error( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); } diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index bf48b0346870..5daefd4355e0 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -35,6 +35,7 @@ import queueRunner from '../queueRunner'; import treeProcessor from '../treeProcessor'; import isError from '../isError'; import assertionErrorMessage from '../assertionErrorMessage'; +import {formatExecError} from 'jest-message-util'; import {ErrorWithStack, isPromise} from 'jest-util'; export default function(j$) { @@ -379,13 +380,25 @@ export default function(j$) { // TODO throw in Jest 25: declarationError = new Error if (isPromise(describeReturnValue)) { console.warn( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + formatExecError( + new Error( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); } else if (describeReturnValue !== undefined) { console.warn( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + formatExecError( + new Error( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); } From a8474160d9fad8837a949c2b1975547cc8fd4fa8 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 3 Mar 2019 00:28:19 +0100 Subject: [PATCH 07/11] remove circus stack entry --- packages/jest-circus/src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/jest-circus/src/index.ts b/packages/jest-circus/src/index.ts index 2280ed522faa..50d9f0668936 100644 --- a/packages/jest-circus/src/index.ts +++ b/packages/jest-circus/src/index.ts @@ -70,9 +70,10 @@ const _dispatchDescribe = ( if (isPromise(describeReturn)) { console.warn( formatExecError( - new Error( + new ErrorWithStack( 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + 'Returning a value from "describe" will fail the test in a future version of Jest.', + describeFn, ), {rootDir: '', testMatch: []}, {noStackTrace: false}, @@ -81,9 +82,10 @@ const _dispatchDescribe = ( } else if (describeReturn !== undefined) { console.warn( formatExecError( - new Error( + new ErrorWithStack( 'A "describe" callback must not return a value.\n' + 'Returning a value from "describe" will fail the test in a future version of Jest.', + describeFn, ), {rootDir: '', testMatch: []}, {noStackTrace: false}, From 6d0c9cbe0e4b54ee9f6d337cf8dee82453da0ac6 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 3 Mar 2019 13:21:13 +0100 Subject: [PATCH 08/11] snapshot test --- .../declarationErrors.test.ts.snap | 41 +++++++++++++++++++ e2e/__tests__/declarationErrors.test.ts | 17 ++++---- 2 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap diff --git a/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap new file mode 100644 index 000000000000..2cd6c1550a68 --- /dev/null +++ b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`warns if describe returns a Promise 1`] = ` +" console.warn + ● Test suite failed to run + + Returning a Promise from \\"describe\\" is not supported. Tests must be defined synchronously. + Returning a value from \\"describe\\" will fail the test in a future version of Jest. + + 9 | 'use strict'; + 10 | + > 11 | describe('Promise describe warns', () => { + | ^ + 12 | it('t', () => {}); + 13 | return Promise.resolve(); + 14 | }); + + at Object.describe (__tests__/describeReturnPromise.test.js:11:1) + +" +`; + +exports[`warns if describe returns something 1`] = ` +" console.warn + ● Test suite failed to run + + A \\"describe\\" callback must not return a value. + Returning a value from \\"describe\\" will fail the test in a future version of Jest. + + 9 | 'use strict'; + 10 | + > 11 | describe('describe return warns', () => { + | ^ + 12 | it('t', () => {}); + 13 | return 42; + 14 | }); + + at Object.describe (__tests__/describeReturnSomething.test.js:11:1) + +" +`; diff --git a/e2e/__tests__/declarationErrors.test.ts b/e2e/__tests__/declarationErrors.test.ts index f0614b6fa72d..35e0ad983896 100644 --- a/e2e/__tests__/declarationErrors.test.ts +++ b/e2e/__tests__/declarationErrors.test.ts @@ -7,16 +7,18 @@ import runJest from '../runJest'; +const normalizeCircusJasmine = (str: string) => + str + .replace(/console\.warn .+:\d+/, 'console.warn') + .replace(/.+addSpecsToSuite (.+:\d+:\d+).+\n/, ''); + it('warns if describe returns a Promise', () => { const result = runJest('declaration-errors', [ 'describeReturnPromise.test.js', ]); expect(result.status).toBe(0); - expect(result.stdout).toContain('Tests must be defined synchronously'); - expect(result.stdout).toContain( - 'at Object.describe (__tests__/describeReturnPromise.test.js', - ); + expect(normalizeCircusJasmine(result.stdout)).toMatchSnapshot(); }); it('warns if describe returns something', () => { @@ -25,10 +27,5 @@ it('warns if describe returns something', () => { ]); expect(result.status).toBe(0); - expect(result.stdout).toContain( - '"describe" callback must not return a value', - ); - expect(result.stdout).toContain( - 'at Object.describe (__tests__/describeReturnSomething.test.js', - ); + expect(normalizeCircusJasmine(result.stdout)).toMatchSnapshot(); }); From 8c2236d3639334c33afc6c767fbcd93a48de87b3 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 3 Mar 2019 19:46:50 +0100 Subject: [PATCH 09/11] console.warn => console.log --- .../declarationErrors.test.ts.snap | 4 ++-- e2e/__tests__/declarationErrors.test.ts | 2 +- packages/jest-circus/src/index.ts | 17 +++++++++++------ packages/jest-jasmine2/src/jasmine/Env.js | 17 +++++++++++------ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap index 2cd6c1550a68..ae697a3dfe6c 100644 --- a/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap +++ b/e2e/__tests__/__snapshots__/declarationErrors.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`warns if describe returns a Promise 1`] = ` -" console.warn +" console.log ● Test suite failed to run Returning a Promise from \\"describe\\" is not supported. Tests must be defined synchronously. @@ -21,7 +21,7 @@ exports[`warns if describe returns a Promise 1`] = ` `; exports[`warns if describe returns something 1`] = ` -" console.warn +" console.log ● Test suite failed to run A \\"describe\\" callback must not return a value. diff --git a/e2e/__tests__/declarationErrors.test.ts b/e2e/__tests__/declarationErrors.test.ts index 35e0ad983896..fcd5050cfe6d 100644 --- a/e2e/__tests__/declarationErrors.test.ts +++ b/e2e/__tests__/declarationErrors.test.ts @@ -9,7 +9,7 @@ import runJest from '../runJest'; const normalizeCircusJasmine = (str: string) => str - .replace(/console\.warn .+:\d+/, 'console.warn') + .replace(/console\.log .+:\d+/, 'console.log') .replace(/.+addSpecsToSuite (.+:\d+:\d+).+\n/, ''); it('warns if describe returns a Promise', () => { diff --git a/packages/jest-circus/src/index.ts b/packages/jest-circus/src/index.ts index 50d9f0668936..b7e93f3da0f4 100644 --- a/packages/jest-circus/src/index.ts +++ b/packages/jest-circus/src/index.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import chalk from 'chalk'; import {bind as bindEach} from 'jest-each'; import {formatExecError} from 'jest-message-util'; import {ErrorWithStack, isPromise} from 'jest-util'; @@ -68,11 +69,13 @@ const _dispatchDescribe = ( // TODO throw in Jest 25 if (isPromise(describeReturn)) { - console.warn( + console.log( formatExecError( new ErrorWithStack( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + chalk.yellow( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), describeFn, ), {rootDir: '', testMatch: []}, @@ -80,11 +83,13 @@ const _dispatchDescribe = ( ), ); } else if (describeReturn !== undefined) { - console.warn( + console.log( formatExecError( new ErrorWithStack( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + chalk.yellow( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), describeFn, ), {rootDir: '', testMatch: []}, diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index 5daefd4355e0..0da0b92ac1f5 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -31,6 +31,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* eslint-disable sort-keys */ import {AssertionError} from 'assert'; +import chalk from 'chalk'; import queueRunner from '../queueRunner'; import treeProcessor from '../treeProcessor'; import isError from '../isError'; @@ -379,22 +380,26 @@ export default function(j$) { // TODO throw in Jest 25: declarationError = new Error if (isPromise(describeReturnValue)) { - console.warn( + console.log( formatExecError( new Error( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + chalk.yellow( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), ), {rootDir: '', testMatch: []}, {noStackTrace: false}, ), ); } else if (describeReturnValue !== undefined) { - console.warn( + console.log( formatExecError( new Error( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + chalk.yellow( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), ), {rootDir: '', testMatch: []}, {noStackTrace: false}, From 799c85c482611b88a1ff65c268438e441e12732b Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Thu, 7 Mar 2019 00:33:23 +0100 Subject: [PATCH 10/11] remove Env.js after rebase --- packages/jest-jasmine2/src/jasmine/Env.js | 644 ---------------------- 1 file changed, 644 deletions(-) delete mode 100644 packages/jest-jasmine2/src/jasmine/Env.js diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js deleted file mode 100644 index 0da0b92ac1f5..000000000000 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ /dev/null @@ -1,644 +0,0 @@ -/** - * 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. - * - */ -// This file is a heavily modified fork of Jasmine. Original license: -/* -Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -/* eslint-disable sort-keys */ - -import {AssertionError} from 'assert'; -import chalk from 'chalk'; -import queueRunner from '../queueRunner'; -import treeProcessor from '../treeProcessor'; -import isError from '../isError'; -import assertionErrorMessage from '../assertionErrorMessage'; -import {formatExecError} from 'jest-message-util'; -import {ErrorWithStack, isPromise} from 'jest-util'; - -export default function(j$) { - function Env(options) { - options = options || {}; - - const self = this; - - let totalSpecsDefined = 0; - - let catchExceptions = true; - - const realSetTimeout = global.setTimeout; - const realClearTimeout = global.clearTimeout; - - const runnableResources = {}; - let currentSpec = null; - const currentlyExecutingSuites = []; - let currentDeclarationSuite = null; - let throwOnExpectationFailure = false; - let random = false; - let seed = null; - - const currentSuite = function() { - return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; - }; - - const currentRunnable = function() { - return currentSpec || currentSuite(); - }; - - const reporter = new j$.ReportDispatcher([ - 'jasmineStarted', - 'jasmineDone', - 'suiteStarted', - 'suiteDone', - 'specStarted', - 'specDone', - ]); - - this.specFilter = function() { - return true; - }; - - let nextSpecId = 0; - const getNextSpecId = function() { - return 'spec' + nextSpecId++; - }; - - let nextSuiteId = 0; - const getNextSuiteId = function() { - return 'suite' + nextSuiteId++; - }; - - const defaultResourcesForRunnable = function(id, parentRunnableId) { - const resources = {spies: []}; - - runnableResources[id] = resources; - }; - - const clearResourcesForRunnable = function(id) { - spyRegistry.clearSpies(); - delete runnableResources[id]; - }; - - const beforeAndAfterFns = function(suite) { - return function() { - let afters = []; - let befores = []; - - while (suite) { - befores = befores.concat(suite.beforeFns); - afters = afters.concat(suite.afterFns); - - suite = suite.parentSuite; - } - - return { - befores: befores.reverse(), - afters, - }; - }; - }; - - const getSpecName = function(spec, suite) { - const fullName = [spec.description]; - const suiteFullName = suite.getFullName(); - - if (suiteFullName !== '') { - fullName.unshift(suiteFullName); - } - - return fullName.join(' '); - }; - - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - this.throwOnExpectationFailure = function(value) { - throwOnExpectationFailure = !!value; - }; - - this.throwingExpectationFailures = function() { - return throwOnExpectationFailure; - }; - - this.randomizeTests = function(value) { - random = !!value; - }; - - this.randomTests = function() { - return random; - }; - - this.seed = function(value) { - if (value) { - seed = value; - } - return seed; - }; - - function queueRunnerFactory(options) { - options.clearTimeout = realClearTimeout; - options.fail = self.fail; - options.setTimeout = realSetTimeout; - return queueRunner(options); - } - - const topSuite = new j$.Suite({ - id: getNextSuiteId(), - getTestPath() { - return j$.testPath; - }, - }); - - currentDeclarationSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - const uncaught = err => { - if (currentSpec) { - currentSpec.onException(err); - currentSpec.cancel(); - } else { - console.error('Unhandled error'); - console.error(err.stack); - } - }; - - let oldListenersException; - let oldListenersRejection; - const executionSetup = function() { - // Need to ensure we are the only ones handling these exceptions. - oldListenersException = process.listeners('uncaughtException').slice(); - oldListenersRejection = process.listeners('unhandledRejection').slice(); - - j$.process.removeAllListeners('uncaughtException'); - j$.process.removeAllListeners('unhandledRejection'); - - j$.process.on('uncaughtException', uncaught); - j$.process.on('unhandledRejection', uncaught); - }; - - const executionTeardown = function() { - j$.process.removeListener('uncaughtException', uncaught); - j$.process.removeListener('unhandledRejection', uncaught); - - // restore previous exception handlers - oldListenersException.forEach(listener => { - j$.process.on('uncaughtException', listener); - }); - - oldListenersRejection.forEach(listener => { - j$.process.on('unhandledRejection', listener); - }); - }; - - this.execute = async function(runnablesToRun, suiteTree = topSuite) { - if (!runnablesToRun) { - if (focusedRunnables.length) { - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [suiteTree.id]; - } - } - - if (currentlyExecutingSuites.length === 0) { - executionSetup(); - } - - const lastDeclarationSuite = currentDeclarationSuite; - - await treeProcessor({ - nodeComplete(suite) { - if (!suite.disabled) { - clearResourcesForRunnable(suite.id); - } - currentlyExecutingSuites.pop(); - if (suite === topSuite) { - reporter.jasmineDone({ - failedExpectations: topSuite.result.failedExpectations, - }); - } else { - reporter.suiteDone(suite.getResult()); - } - }, - nodeStart(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable( - suite.id, - suite.parentSuite && suite.parentSuite.id, - ); - if (suite === topSuite) { - reporter.jasmineStarted({totalSpecsDefined}); - } else { - reporter.suiteStarted(suite.result); - } - }, - queueRunnerFactory, - runnableIds: runnablesToRun, - tree: suiteTree, - }); - - currentDeclarationSuite = lastDeclarationSuite; - - if (currentlyExecutingSuites.length === 0) { - executionTeardown(); - } - }; - - this.addReporter = function(reporterToAdd) { - reporter.addReporter(reporterToAdd); - }; - - this.provideFallbackReporter = function(reporterToAdd) { - reporter.provideFallbackReporter(reporterToAdd); - }; - - this.clearReporters = function() { - reporter.clearReporters(); - }; - - const spyRegistry = new j$.SpyRegistry({ - currentSpies() { - if (!currentRunnable()) { - throw new Error( - 'Spies must be created in a before function or a spec', - ); - } - return runnableResources[currentRunnable().id].spies; - }, - }); - - this.allowRespy = function(allow) { - spyRegistry.allowRespy(allow); - }; - - this.spyOn = function() { - return spyRegistry.spyOn.apply(spyRegistry, arguments); - }; - - const suiteFactory = function(description) { - const suite = new j$.Suite({ - id: getNextSuiteId(), - description, - parentSuite: currentDeclarationSuite, - throwOnExpectationFailure, - getTestPath() { - return j$.testPath; - }, - }); - - return suite; - }; - - this.describe = function(description, specDefinitions) { - const suite = suiteFactory(description); - if (specDefinitions === undefined) { - throw new Error( - `Missing second argument. It must be a callback function.`, - ); - } - if (typeof specDefinitions !== 'function') { - throw new Error( - `Invalid second argument, ${specDefinitions}. It must be a callback function.`, - ); - } - if (specDefinitions.length > 0) { - throw new Error('describe does not expect any arguments'); - } - if (currentDeclarationSuite.markedPending) { - suite.pend(); - } - if (currentDeclarationSuite.markedTodo) { - suite.todo(); - } - addSpecsToSuite(suite, specDefinitions); - return suite; - }; - - this.xdescribe = function(description, specDefinitions) { - const suite = suiteFactory(description); - suite.pend(); - addSpecsToSuite(suite, specDefinitions); - return suite; - }; - - const focusedRunnables = []; - - this.fdescribe = function(description, specDefinitions) { - const suite = suiteFactory(description); - suite.isFocused = true; - - focusedRunnables.push(suite.id); - unfocusAncestor(); - addSpecsToSuite(suite, specDefinitions); - - return suite; - }; - - function addSpecsToSuite(suite, specDefinitions) { - const parentSuite = currentDeclarationSuite; - parentSuite.addChild(suite); - currentDeclarationSuite = suite; - - let declarationError = null; - let describeReturnValue = null; - try { - describeReturnValue = specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - // TODO throw in Jest 25: declarationError = new Error - if (isPromise(describeReturnValue)) { - console.log( - formatExecError( - new Error( - chalk.yellow( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', - ), - ), - {rootDir: '', testMatch: []}, - {noStackTrace: false}, - ), - ); - } else if (describeReturnValue !== undefined) { - console.log( - formatExecError( - new Error( - chalk.yellow( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', - ), - ), - {rootDir: '', testMatch: []}, - {noStackTrace: false}, - ), - ); - } - - if (declarationError) { - self.it('encountered a declaration exception', () => { - throw declarationError; - }); - } - - currentDeclarationSuite = parentSuite; - } - - function findFocusedAncestor(suite) { - while (suite) { - if (suite.isFocused) { - return suite.id; - } - suite = suite.parentSuite; - } - - return null; - } - - function unfocusAncestor() { - const focusedAncestor = findFocusedAncestor(currentDeclarationSuite); - if (focusedAncestor) { - for (let i = 0; i < focusedRunnables.length; i++) { - if (focusedRunnables[i] === focusedAncestor) { - focusedRunnables.splice(i, 1); - break; - } - } - } - } - - const specFactory = function(description, fn, suite, timeout) { - totalSpecsDefined++; - const spec = new j$.Spec({ - id: getNextSpecId(), - beforeAndAfterFns: beforeAndAfterFns(suite), - resultCallback: specResultCallback, - getSpecName(spec) { - return getSpecName(spec, suite); - }, - getTestPath() { - return j$.testPath; - }, - onStart: specStarted, - description, - queueRunnerFactory, - userContext() { - return suite.clonedSharedUserContext(); - }, - queueableFn: { - fn, - timeout() { - return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; - }, - }, - throwOnExpectationFailure, - }); - - if (!self.specFilter(spec)) { - spec.disable(); - } - - return spec; - - function specResultCallback(result) { - clearResourcesForRunnable(spec.id); - currentSpec = null; - reporter.specDone(result); - } - - function specStarted(spec) { - currentSpec = spec; - defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); - } - }; - - this.it = function(description, fn, timeout) { - if (typeof description !== 'string') { - throw new Error( - `Invalid first argument, ${description}. It must be a string.`, - ); - } - if (fn === undefined) { - throw new Error( - 'Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder.', - ); - } - if (typeof fn !== 'function') { - throw new Error( - `Invalid second argument, ${fn}. It must be a callback function.`, - ); - } - const spec = specFactory( - description, - fn, - currentDeclarationSuite, - timeout, - ); - if (currentDeclarationSuite.markedPending) { - spec.pend(); - } - - // When a test is defined inside another, jasmine will not run it. - // This check throws an error to warn the user about the edge-case. - if (currentSpec !== null) { - throw new Error( - 'Tests cannot be nested. Test `' + - spec.description + - '` cannot run because it is nested within `' + - currentSpec.description + - '`.', - ); - } - currentDeclarationSuite.addChild(spec); - return spec; - }; - - this.xit = function() { - const spec = this.it.apply(this, arguments); - spec.pend('Temporarily disabled with xit'); - return spec; - }; - - this.todo = function() { - const description = arguments[0]; - if (arguments.length !== 1 || typeof description !== 'string') { - throw new ErrorWithStack( - 'Todo must be called with only a description.', - test.todo, - ); - } - - const spec = specFactory(description, () => {}, currentDeclarationSuite); - spec.todo(); - currentDeclarationSuite.addChild(spec); - return spec; - }; - - this.fit = function(description, fn, timeout) { - const spec = specFactory( - description, - fn, - currentDeclarationSuite, - timeout, - ); - currentDeclarationSuite.addChild(spec); - focusedRunnables.push(spec.id); - unfocusAncestor(); - return spec; - }; - - this.beforeEach = function(beforeEachFunction, timeout) { - currentDeclarationSuite.beforeEach({ - fn: beforeEachFunction, - timeout() { - return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; - }, - }); - }; - - this.beforeAll = function(beforeAllFunction, timeout) { - currentDeclarationSuite.beforeAll({ - fn: beforeAllFunction, - timeout() { - return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; - }, - }); - }; - - this.afterEach = function(afterEachFunction, timeout) { - currentDeclarationSuite.afterEach({ - fn: afterEachFunction, - timeout() { - return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; - }, - }); - }; - - this.afterAll = function(afterAllFunction, timeout) { - currentDeclarationSuite.afterAll({ - fn: afterAllFunction, - timeout() { - return timeout || j$._DEFAULT_TIMEOUT_INTERVAL; - }, - }); - }; - - this.pending = function(message) { - let fullMessage = j$.Spec.pendingSpecExceptionMessage; - if (message) { - fullMessage += message; - } - throw fullMessage; - }; - - this.fail = function(error) { - let checkIsError; - let message; - - if (error instanceof AssertionError) { - checkIsError = false; - message = assertionErrorMessage(error, {expand: j$.Spec.expand}); - } else { - const check = isError(error); - - checkIsError = check.isError; - message = check.message; - } - - const errorAsErrorObject = checkIsError ? error : new Error(message); - const runnable = currentRunnable(); - - if (!runnable) { - errorAsErrorObject.message = - 'Caught error after test environment was torn down\n\n' + - errorAsErrorObject.message; - - throw errorAsErrorObject; - } - - runnable.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - message, - error: errorAsErrorObject, - }); - }; - } - - return Env; -} From 0c8038e0a97a265de4fe47b7dd5a86a533804bda Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Thu, 7 Mar 2019 00:41:51 +0100 Subject: [PATCH 11/11] fix jasmine2 after rebase --- packages/jest-jasmine2/src/jasmine/Env.ts | 30 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/Env.ts b/packages/jest-jasmine2/src/jasmine/Env.ts index 522ae69c6503..14d3ea9587ee 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.ts +++ b/packages/jest-jasmine2/src/jasmine/Env.ts @@ -31,6 +31,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* eslint-disable sort-keys */ import {AssertionError} from 'assert'; +import chalk from 'chalk'; +import {formatExecError} from 'jest-message-util'; import {ErrorWithStack, isPromise} from 'jest-util'; import queueRunner, { Options as QueueRunnerOptions, @@ -424,14 +426,30 @@ export default function(j$: Jasmine) { // TODO throw in Jest 25: declarationError = new Error if (isPromise(describeReturnValue)) { - console.warn( - 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + console.log( + formatExecError( + new Error( + chalk.yellow( + 'Returning a Promise from "describe" is not supported. Tests must be defined synchronously.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); } else if (describeReturnValue !== undefined) { - console.warn( - 'A "describe" callback must not return a value.\n' + - 'Returning a value from "describe" will fail the test in a future version of Jest.', + console.log( + formatExecError( + new Error( + chalk.yellow( + 'A "describe" callback must not return a value.\n' + + 'Returning a value from "describe" will fail the test in a future version of Jest.', + ), + ), + {rootDir: '', testMatch: []}, + {noStackTrace: false}, + ), ); }