diff --git a/CHANGELOG.md b/CHANGELOG.md index a35f09f8192f..310a302bf2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ ### Chore & Maintenance +- `[jest-jasmine2]`: TS migration ([#7970](https://github.com/facebook/jest/pull/7970)) - `[jest-each]`: Refactor into multiple files with better types ([#8018](https://github.com/facebook/jest/pull/8018)) - `[jest-each]`: Migrate to Typescript ([#8007](https://github.com/facebook/jest/pull/8007)) - `[jest-environment-jsdom]`: Migrate to TypeScript ([#7985](https://github.com/facebook/jest/pull/8003)) diff --git a/packages/jest-jasmine2/package.json b/packages/jest-jasmine2/package.json index 562d0d5397e5..ffca52f38b8b 100644 --- a/packages/jest-jasmine2/package.json +++ b/packages/jest-jasmine2/package.json @@ -8,8 +8,11 @@ }, "license": "MIT", "main": "build/index.js", + "types": "build/index.d.ts", "dependencies": { "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.1.0", + "@jest/types": "^24.1.0", "chalk": "^2.0.1", "co": "^4.6.0", "expect": "^24.1.0", @@ -17,15 +20,14 @@ "jest-each": "^24.0.0", "jest-matcher-utils": "^24.0.0", "jest-message-util": "^24.0.0", + "jest-runtime": "^24.1.0", "jest-snapshot": "^24.1.0", "jest-util": "^24.0.0", "pretty-format": "^24.0.0", "throat": "^4.0.0" }, "devDependencies": { - "@types/babel__traverse": "^7.0.4", - "jest-diff": "^24.0.0", - "jest-runtime": "^24.1.0" + "@types/babel__traverse": "^7.0.4" }, "engines": { "node": ">= 6" diff --git a/packages/jest-jasmine2/src/ExpectationFailed.js b/packages/jest-jasmine2/src/ExpectationFailed.ts similarity index 95% rename from packages/jest-jasmine2/src/ExpectationFailed.js rename to packages/jest-jasmine2/src/ExpectationFailed.ts index 2469c8143a28..ad9910ff3e9e 100644 --- a/packages/jest-jasmine2/src/ExpectationFailed.js +++ b/packages/jest-jasmine2/src/ExpectationFailed.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ export default class ExpectationFailed extends Error {} diff --git a/packages/jest-jasmine2/src/PCancelable.js b/packages/jest-jasmine2/src/PCancelable.js index deadee0fbd03..f5a488ec0efb 100644 --- a/packages/jest-jasmine2/src/PCancelable.js +++ b/packages/jest-jasmine2/src/PCancelable.js @@ -1,4 +1,9 @@ -// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +/** + * 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'; diff --git a/packages/jest-jasmine2/src/__tests__/Suite.test.js b/packages/jest-jasmine2/src/__tests__/Suite.test.ts similarity index 81% rename from packages/jest-jasmine2/src/__tests__/Suite.test.js rename to packages/jest-jasmine2/src/__tests__/Suite.test.ts index 6850a9351496..e8323ea2e589 100644 --- a/packages/jest-jasmine2/src/__tests__/Suite.test.js +++ b/packages/jest-jasmine2/src/__tests__/Suite.test.ts @@ -6,21 +6,20 @@ * */ -'use strict'; - -import Suite from '../jasmine/Suite'; +import Suite, {Attributes} from '../jasmine/Suite'; describe('Suite', () => { - let suite; + let suite: Suite; beforeEach(() => { suite = new Suite({ getTestPath: () => '', - }); + } as Attributes); }); it("doesn't throw on addExpectationResult when there are no children", () => { expect(() => { + // @ts-ignore suite.addExpectationResult(); }).not.toThrow(); }); diff --git a/packages/jest-jasmine2/src/__tests__/__snapshots__/expectationResultFactory.test.js.snap b/packages/jest-jasmine2/src/__tests__/__snapshots__/expectationResultFactory.test.ts.snap similarity index 100% rename from packages/jest-jasmine2/src/__tests__/__snapshots__/expectationResultFactory.test.js.snap rename to packages/jest-jasmine2/src/__tests__/__snapshots__/expectationResultFactory.test.ts.snap diff --git a/packages/jest-jasmine2/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/jest-jasmine2/src/__tests__/__snapshots__/matchers.test.ts.snap similarity index 100% rename from packages/jest-jasmine2/src/__tests__/__snapshots__/matchers.test.js.snap rename to packages/jest-jasmine2/src/__tests__/__snapshots__/matchers.test.ts.snap diff --git a/packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.js b/packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.ts similarity index 99% rename from packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.js rename to packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.ts index af70b5f3b952..fdf940eab04c 100644 --- a/packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.js +++ b/packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - import expectationResultFactory from '../expectationResultFactory'; describe('expectationResultFactory', () => { diff --git a/packages/jest-jasmine2/src/__tests__/hooksError.test.js b/packages/jest-jasmine2/src/__tests__/hooksError.test.ts similarity index 83% rename from packages/jest-jasmine2/src/__tests__/hooksError.test.js rename to packages/jest-jasmine2/src/__tests__/hooksError.test.ts index 020c5f111e82..e760b48b7b1c 100644 --- a/packages/jest-jasmine2/src/__tests__/hooksError.test.js +++ b/packages/jest-jasmine2/src/__tests__/hooksError.test.ts @@ -3,15 +3,14 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -'use strict'; +export type SharedHookType = 'afterAll' | 'beforeAll'; +export type HookType = SharedHookType | 'afterEach' | 'beforeEach'; describe.each([['beforeEach'], ['beforeAll'], ['afterEach'], ['afterAll']])( '%s hooks error throwing', - fn => { + (fn: HookType) => { test.each([ ['String'], [1], diff --git a/packages/jest-jasmine2/src/__tests__/itTestError.test.js b/packages/jest-jasmine2/src/__tests__/itTestError.test.ts similarity index 95% rename from packages/jest-jasmine2/src/__tests__/itTestError.test.js rename to packages/jest-jasmine2/src/__tests__/itTestError.test.ts index bcce79f21121..e230949e8f28 100644 --- a/packages/jest-jasmine2/src/__tests__/itTestError.test.js +++ b/packages/jest-jasmine2/src/__tests__/itTestError.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - describe('test/it error throwing', () => { it(`it throws error with missing callback function`, () => { expect(() => { @@ -18,11 +16,13 @@ describe('test/it error throwing', () => { }); it(`it throws an error when first argument isn't a string`, () => { expect(() => { + // @ts-ignore it(() => {}); }).toThrowError(`Invalid first argument, () => {}. It must be a string.`); }); it('it throws an error when callback function is not a function', () => { expect(() => { + // @ts-ignore it('test3', 'test3b'); }).toThrowError( 'Invalid second argument, test3b. It must be a callback function.', @@ -37,11 +37,13 @@ describe('test/it error throwing', () => { }); test(`test throws an error when first argument isn't a string`, () => { expect(() => { + // @ts-ignore test(() => {}); }).toThrowError(`Invalid first argument, () => {}. It must be a string.`); }); test('test throws an error when callback function is not a function', () => { expect(() => { + // @ts-ignore test('test6', 'test6b'); }).toThrowError( 'Invalid second argument, test6b. It must be a callback function.', diff --git a/packages/jest-jasmine2/src/__tests__/itToTestAlias.test.js b/packages/jest-jasmine2/src/__tests__/itToTestAlias.test.ts similarity index 94% rename from packages/jest-jasmine2/src/__tests__/itToTestAlias.test.js rename to packages/jest-jasmine2/src/__tests__/itToTestAlias.test.ts index a2e55740c1ce..4d108b2b0b93 100644 --- a/packages/jest-jasmine2/src/__tests__/itToTestAlias.test.js +++ b/packages/jest-jasmine2/src/__tests__/itToTestAlias.test.ts @@ -6,6 +6,4 @@ * */ -'use strict'; - test('global.test', () => {}); diff --git a/packages/jest-jasmine2/src/__tests__/iterators.test.js b/packages/jest-jasmine2/src/__tests__/iterators.test.ts similarity index 88% rename from packages/jest-jasmine2/src/__tests__/iterators.test.js rename to packages/jest-jasmine2/src/__tests__/iterators.test.ts index c7d214bbff50..1c45192c41e6 100644 --- a/packages/jest-jasmine2/src/__tests__/iterators.test.js +++ b/packages/jest-jasmine2/src/__tests__/iterators.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - describe('iterators', () => { it('works for arrays', () => { const mixedArray = [1, {}, []]; @@ -55,9 +53,14 @@ describe('iterators', () => { }); it('works for Maps', () => { - const keyValuePairs = [['key1', 'value1'], ['key2', 'value2']]; - const smallerKeyValuePairs = [['key1', 'value1']]; - const biggerKeyValuePairs = [ + const keyValuePairs: ReadonlyArray<[string, string]> = [ + ['key1', 'value1'], + ['key2', 'value2'], + ]; + const smallerKeyValuePairs: ReadonlyArray<[string, string]> = [ + ['key1', 'value1'], + ]; + const biggerKeyValuePairs: ReadonlyArray<[string, string]> = [ ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3'], diff --git a/packages/jest-jasmine2/src/__tests__/matchers.test.js b/packages/jest-jasmine2/src/__tests__/matchers.test.ts similarity index 96% rename from packages/jest-jasmine2/src/__tests__/matchers.test.js rename to packages/jest-jasmine2/src/__tests__/matchers.test.ts index 15a0c8a708cd..3dfb0b77aed4 100644 --- a/packages/jest-jasmine2/src/__tests__/matchers.test.js +++ b/packages/jest-jasmine2/src/__tests__/matchers.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - describe('matchers', () => { it('proxies matchers to expect', () => { expect(() => expect(1).toBe(2)).toThrowErrorMatchingSnapshot(); diff --git a/packages/jest-jasmine2/src/__tests__/pTimeout.test.js b/packages/jest-jasmine2/src/__tests__/pTimeout.test.ts similarity index 99% rename from packages/jest-jasmine2/src/__tests__/pTimeout.test.js rename to packages/jest-jasmine2/src/__tests__/pTimeout.test.ts index 84c8e7408a9a..fef80a2bd541 100644 --- a/packages/jest-jasmine2/src/__tests__/pTimeout.test.js +++ b/packages/jest-jasmine2/src/__tests__/pTimeout.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - jest.useFakeTimers(); import pTimeout from '../pTimeout'; diff --git a/packages/jest-jasmine2/src/__tests__/queueRunner.test.js b/packages/jest-jasmine2/src/__tests__/queueRunner.test.ts similarity index 96% rename from packages/jest-jasmine2/src/__tests__/queueRunner.test.js rename to packages/jest-jasmine2/src/__tests__/queueRunner.test.ts index 12f4dd6f719b..d98fe7fcfe3d 100644 --- a/packages/jest-jasmine2/src/__tests__/queueRunner.test.js +++ b/packages/jest-jasmine2/src/__tests__/queueRunner.test.ts @@ -6,8 +6,6 @@ * */ -'use strict'; - import queueRunner from '../queueRunner'; describe('queueRunner', () => { @@ -28,6 +26,7 @@ describe('queueRunner', () => { ], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(fnOne).toHaveBeenCalled(); expect(fnTwo).toHaveBeenCalled(); @@ -51,6 +50,7 @@ describe('queueRunner', () => { ], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(fnOne).toHaveBeenCalled(); expect(fail).toHaveBeenCalled(); @@ -79,6 +79,7 @@ describe('queueRunner', () => { ], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(fnOne).toHaveBeenCalled(); expect(onException).toHaveBeenCalledWith(error); @@ -87,7 +88,7 @@ describe('queueRunner', () => { }); it('passes an error to `onException` on timeout.', async () => { - const fnOne = jest.fn(next => {}); + const fnOne = jest.fn(_next => {}); const fnTwo = jest.fn(next => next()); const onException = jest.fn(); const options = { @@ -106,6 +107,7 @@ describe('queueRunner', () => { ], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(fnOne).toHaveBeenCalled(); expect(onException).toHaveBeenCalled(); @@ -125,6 +127,7 @@ describe('queueRunner', () => { queueableFns: [{fn: failFn}], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(options.fail).toHaveBeenCalledWith('miserably', 'failed'); @@ -149,6 +152,7 @@ describe('queueRunner', () => { ], setTimeout, }; + // @ts-ignore await queueRunner(options); expect(fnOne).toHaveBeenCalled(); expect(fail).toHaveBeenCalledWith(error); diff --git a/packages/jest-jasmine2/src/__tests__/reporter.test.js b/packages/jest-jasmine2/src/__tests__/reporter.test.ts similarity index 62% rename from packages/jest-jasmine2/src/__tests__/reporter.test.js rename to packages/jest-jasmine2/src/__tests__/reporter.test.ts index ee005c8fd743..4e92f9b9cf4a 100644 --- a/packages/jest-jasmine2/src/__tests__/reporter.test.js +++ b/packages/jest-jasmine2/src/__tests__/reporter.test.ts @@ -6,29 +6,33 @@ * */ -'use strict'; - import JasmineReporter from '../reporter'; +import {SuiteResult} from '../jasmine/Suite'; +import {SpecResult} from '../jasmine/Spec'; describe('Jasmine2Reporter', () => { - let reporter; + let reporter: JasmineReporter; beforeEach(() => { + // @ts-ignore reporter = new JasmineReporter({}); }); it('reports nested suites', () => { - const makeSpec = name => ({ - description: 'description', - failedExpectations: [], - fullName: name, - }); - reporter.suiteStarted({description: 'parent'}); - reporter.suiteStarted({description: 'child'}); + const makeSpec = (name: string) => + ({ + description: 'description', + failedExpectations: [], + fullName: name, + } as SpecResult); + reporter.suiteStarted({description: 'parent'} as SuiteResult); + reporter.suiteStarted({description: 'child'} as SuiteResult); reporter.specDone(makeSpec('spec 1')); + // @ts-ignore reporter.suiteDone(); - reporter.suiteStarted({description: 'child 2'}); + reporter.suiteStarted({description: 'child 2'} as SuiteResult); reporter.specDone(makeSpec('spec 2')); + // @ts-ignore reporter.jasmineDone(); return reporter.getResults().then(runResults => { diff --git a/packages/jest-jasmine2/src/__tests__/todoError.test.js b/packages/jest-jasmine2/src/__tests__/todoError.test.ts similarity index 95% rename from packages/jest-jasmine2/src/__tests__/todoError.test.js rename to packages/jest-jasmine2/src/__tests__/todoError.test.ts index ffde32d86406..758cfda9140e 100644 --- a/packages/jest-jasmine2/src/__tests__/todoError.test.js +++ b/packages/jest-jasmine2/src/__tests__/todoError.test.ts @@ -6,11 +6,10 @@ * */ -'use strict'; - describe('test/it.todo error throwing', () => { it('it throws error when given no arguments', () => { expect(() => { + // @ts-ignore it.todo(); }).toThrowError('Todo must be called with only a description.'); }); @@ -21,6 +20,7 @@ describe('test/it.todo error throwing', () => { }); it('it throws error when given none string description', () => { expect(() => { + // @ts-ignore it.todo(() => {}); }).toThrowError('Todo must be called with only a description.'); }); diff --git a/packages/jest-jasmine2/src/assertionErrorMessage.js b/packages/jest-jasmine2/src/assertionErrorMessage.ts similarity index 81% rename from packages/jest-jasmine2/src/assertionErrorMessage.js rename to packages/jest-jasmine2/src/assertionErrorMessage.ts index dd83b4cee4a8..3a5e6761ec84 100644 --- a/packages/jest-jasmine2/src/assertionErrorMessage.js +++ b/packages/jest-jasmine2/src/assertionErrorMessage.ts @@ -3,34 +3,25 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -// TODO: Converted to TS. It's also not exported, but should be imported from `matcher-utils` -import type {DiffOptions} from 'jest-diff'; - -import {diff, printReceived, printExpected} from 'jest-matcher-utils'; +import { + diff, + printReceived, + printExpected, + DiffOptions, +} from 'jest-matcher-utils'; import chalk from 'chalk'; +import {AssertionErrorWithStack} from './types'; -type AssertionError = {| - actual: ?string, - expected: ?string, - generatedMessage: boolean, - message: string, - name: string, - operator: ?string, - stack: string, -|}; - -const assertOperatorsMap = { +const assertOperatorsMap: {[key: string]: string} = { '!=': 'notEqual', '!==': 'notStrictEqual', '==': 'equal', '===': 'strictEqual', }; -const humanReadableOperators = { +const humanReadableOperators: {[key: string]: string} = { deepEqual: 'to deeply equal', deepStrictEqual: 'to deeply and strictly equal', equal: 'to be equal', @@ -41,7 +32,7 @@ const humanReadableOperators = { strictEqual: 'to strictly be equal', }; -const getOperatorName = (operator: ?string, stack: string) => { +const getOperatorName = (operator: string | null, stack: string) => { if (typeof operator === 'string') { return assertOperatorsMap[operator] || operator; } @@ -54,7 +45,7 @@ const getOperatorName = (operator: ?string, stack: string) => { return ''; }; -const operatorMessage = (operator: ?string) => { +const operatorMessage = (operator: string | null) => { const niceOperatorName = getOperatorName(operator, ''); // $FlowFixMe: we default to the operator itself, so holes in the map doesn't matter const humanReadableOperator = humanReadableOperators[niceOperatorName]; @@ -70,7 +61,7 @@ const assertThrowingMatcherHint = (operatorName: string) => chalk.red('function') + chalk.dim(')'); -const assertMatcherHint = (operator: ?string, operatorName: string) => { +const assertMatcherHint = (operator: string | null, operatorName: string) => { let message = chalk.dim('assert') + chalk.dim('.' + operatorName + '(') + @@ -91,7 +82,10 @@ const assertMatcherHint = (operator: ?string, operatorName: string) => { return message; }; -function assertionErrorMessage(error: AssertionError, options: DiffOptions) { +function assertionErrorMessage( + error: AssertionErrorWithStack, + options: DiffOptions, +) { const {expected, actual, generatedMessage, message, operator, stack} = error; const diffString = diff(expected, actual, options); const hasCustomMessage = !generatedMessage; diff --git a/packages/jest-jasmine2/src/each.js b/packages/jest-jasmine2/src/each.ts similarity index 86% rename from packages/jest-jasmine2/src/each.js rename to packages/jest-jasmine2/src/each.ts index 23574b1cb302..05bc252304a6 100644 --- a/packages/jest-jasmine2/src/each.js +++ b/packages/jest-jasmine2/src/each.ts @@ -3,15 +3,12 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Environment} from 'types/Environment'; - +import {JestEnvironment} from '@jest/environment'; import {bind as bindEach} from 'jest-each'; -export default (environment: Environment): void => { +export default (environment: JestEnvironment): void => { environment.global.it.each = bindEach(environment.global.it); environment.global.fit.each = bindEach(environment.global.fit); environment.global.xit.each = bindEach(environment.global.xit); diff --git a/packages/jest-jasmine2/src/errorOnPrivate.js b/packages/jest-jasmine2/src/errorOnPrivate.ts similarity index 64% rename from packages/jest-jasmine2/src/errorOnPrivate.js rename to packages/jest-jasmine2/src/errorOnPrivate.ts index 2028628d4adc..afc0eb54eefc 100644 --- a/packages/jest-jasmine2/src/errorOnPrivate.js +++ b/packages/jest-jasmine2/src/errorOnPrivate.ts @@ -3,23 +3,33 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Global} from '../../../types/Global'; +import {Global} from '@jest/types'; import {ErrorWithStack} from 'jest-util'; +import {Jasmine} from './types'; + +type DisabledGlobalKeys = 'fail' | 'pending' | 'spyOn' | 'spyOnProperty'; // prettier-ignore -const disabledGlobals = { +const disabledGlobals: Record = { fail: 'Illegal usage of global `fail`, prefer throwing an error, or the `done.fail` callback.', pending: 'Illegal usage of global `pending`, prefer explicitly skipping a test using `test.skip`', spyOn: 'Illegal usage of global `spyOn`, prefer `jest.spyOn`.', spyOnProperty: 'Illegal usage of global `spyOnProperty`, prefer `jest.spyOn`.', }; +type DisabledJasmineMethodsKeys = + | 'addMatchers' + | 'any' + | 'anything' + | 'arrayContaining' + | 'createSpy' + | 'objectContaining' + | 'stringMatching'; + // prettier-ignore -const disabledJasmineMethods = { +const disabledJasmineMethods: Record = { addMatchers: 'Illegal usage of `jasmine.addMatchers`, prefer `expect.extends`.', any: 'Illegal usage of `jasmine.any`, prefer `expect.any`.', anything: 'Illegal usage of `jasmine.anything`, prefer `expect.anything`.', @@ -29,15 +39,20 @@ const disabledJasmineMethods = { stringMatching: 'Illegal usage of `jasmine.stringMatching`, prefer `expect.stringMatching`.', }; -export function installErrorOnPrivate(global: Global): void { - const {jasmine} = global; - Object.keys(disabledGlobals).forEach(functionName => { - global[functionName] = () => { - throwAtFunction(disabledGlobals[functionName], global[functionName]); - }; - }); +export function installErrorOnPrivate(global: Global.Global): void { + const jasmine = global.jasmine as Jasmine; + + (Object.keys(disabledGlobals) as Array).forEach( + functionName => { + global[functionName] = () => { + throwAtFunction(disabledGlobals[functionName], global[functionName]); + }; + }, + ); - Object.keys(disabledJasmineMethods).forEach(methodName => { + (Object.keys(disabledJasmineMethods) as Array< + DisabledJasmineMethodsKeys + >).forEach(methodName => { jasmine[methodName] = () => { throwAtFunction(disabledJasmineMethods[methodName], jasmine[methodName]); }; @@ -51,7 +66,7 @@ export function installErrorOnPrivate(global: Global): void { } const original = jasmine.DEFAULT_TIMEOUT_INTERVAL; - // $FlowFixMe Flow seems to be confused about accessors and tries to enfoce having a `value` property. + Object.defineProperty(jasmine, 'DEFAULT_TIMEOUT_INTERVAL', { configurable: true, enumerable: true, @@ -60,6 +75,6 @@ export function installErrorOnPrivate(global: Global): void { }); } -function throwAtFunction(message, fn) { +function throwAtFunction(message: string, fn: Function) { throw new ErrorWithStack(message, fn); } diff --git a/packages/jest-jasmine2/src/expectationResultFactory.js b/packages/jest-jasmine2/src/expectationResultFactory.ts similarity index 80% rename from packages/jest-jasmine2/src/expectationResultFactory.js rename to packages/jest-jasmine2/src/expectationResultFactory.ts index 2bd3b7c65dec..e18b59a15b97 100644 --- a/packages/jest-jasmine2/src/expectationResultFactory.js +++ b/packages/jest-jasmine2/src/expectationResultFactory.ts @@ -3,13 +3,12 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import prettyFormat from 'pretty-format'; +import {TestResult} from '@jest/types'; -function messageFormatter({error, message, passed}) { +function messageFormatter({error, message, passed}: Options) { if (passed) { return 'Passed.'; } @@ -31,7 +30,11 @@ function messageFormatter({error, message, passed}) { return `thrown: ${prettyFormat(error, {maxDepth: 3})}`; } -function stackFormatter(options, initError, errorMessage) { +function stackFormatter( + options: Options, + initError: Error | undefined, + errorMessage: string, +) { if (options.passed) { return ''; } @@ -53,19 +56,19 @@ function stackFormatter(options, initError, errorMessage) { return new Error(errorMessage).stack; } -type Options = { - matcherName: string, - passed: boolean, - actual?: any, - error?: any, - expected?: any, - message?: string, +export type Options = { + matcherName: string; + passed: boolean; + actual?: any; + error?: any; + expected?: any; + message?: string | null; }; export default function expectationResultFactory( options: Options, initError?: Error, -) { +): TestResult.FailedAssertion { const message = messageFormatter(options); const stack = stackFormatter(options, initError, message); diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.ts similarity index 73% rename from packages/jest-jasmine2/src/index.js rename to packages/jest-jasmine2/src/index.ts index 78ae577ce84b..fc51347f0d79 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.ts @@ -3,32 +3,31 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Environment} from 'types/Environment'; -import type {GlobalConfig, ProjectConfig} from 'types/Config'; -import type {SnapshotState} from 'jest-snapshot'; -import type {TestResult} from 'types/TestResult'; -import type Runtime from 'jest-runtime'; - import path from 'path'; +import {Config, Global, TestResult} from '@jest/types'; +import {JestEnvironment} from '@jest/environment'; +import {SnapshotStateType} from 'jest-snapshot'; +import Runtime from 'jest-runtime'; + +import {getCallsite} from 'jest-util'; import installEach from './each'; import {installErrorOnPrivate} from './errorOnPrivate'; -import {getCallsite} from 'jest-util'; import JasmineReporter from './reporter'; import jasmineAsyncInstall from './jasmineAsyncInstall'; +import Spec from './jasmine/Spec'; +import {Jasmine as JestJasmine} from './types'; -const JASMINE = require.resolve('./jasmine/jasmineLight.js'); +const JASMINE = require.resolve('./jasmine/jasmineLight'); async function jasmine2( - globalConfig: GlobalConfig, - config: ProjectConfig, - environment: Environment, + globalConfig: Config.GlobalConfig, + config: Config.ProjectConfig, + environment: JestEnvironment, runtime: Runtime, testPath: string, -): Promise { +): Promise { const reporter = new JasmineReporter(globalConfig, config, testPath); const jasmineFactory = runtime.requireInternalModule(JASMINE); const jasmine = jasmineFactory.create({ @@ -45,34 +44,37 @@ async function jasmine2( // in a future version if (config.testLocationInResults === true) { const originalIt = environment.global.it; - environment.global.it = (...args) => { + environment.global.it = ((...args) => { const stack = getCallsite(1, runtime.getSourceMaps()); const it = originalIt(...args); + // @ts-ignore it.result.__callsite = stack; return it; - }; + }) as Global.Global['it']; const originalXit = environment.global.xit; - environment.global.xit = (...args) => { + environment.global.xit = ((...args) => { const stack = getCallsite(1, runtime.getSourceMaps()); const xit = originalXit(...args); + // @ts-ignore xit.result.__callsite = stack; return xit; - }; + }) as Global.Global['xit']; const originalFit = environment.global.fit; - environment.global.fit = (...args) => { + environment.global.fit = ((...args) => { const stack = getCallsite(1, runtime.getSourceMaps()); const fit = originalFit(...args); + // @ts-ignore fit.result.__callsite = stack; return fit; - }; + }) as Global.Global['fit']; } jasmineAsyncInstall(globalConfig, environment.global); @@ -88,7 +90,7 @@ async function jasmine2( environment.global.describe.only = environment.global.fdescribe; if (config.timers === 'fake') { - environment.fakeTimers.useFakeTimers(); + environment.fakeTimers!.useFakeTimers(); } env.beforeEach(() => { @@ -104,7 +106,7 @@ async function jasmine2( runtime.resetAllMocks(); if (config.timers === 'fake') { - environment.fakeTimers.useFakeTimers(); + environment.fakeTimers!.useFakeTimers(); } } @@ -137,7 +139,7 @@ async function jasmine2( }); } - const snapshotState: SnapshotState = runtime + const snapshotState: SnapshotStateType = runtime .requireInternalModule(path.resolve(__dirname, './setup_jest_globals.js')) .default({ config, @@ -146,10 +148,12 @@ async function jasmine2( testPath, }); - config.setupFilesAfterEnv.forEach(path => runtime.requireModule(path)); + config.setupFilesAfterEnv.forEach((path: Config.Path) => + runtime.requireModule(path), + ); if (globalConfig.enabledTestsMap) { - env.specFilter = spec => { + env.specFilter = (spec: Spec) => { const suiteMap = globalConfig.enabledTestsMap && globalConfig.enabledTestsMap[spec.result.testPath]; @@ -157,7 +161,7 @@ async function jasmine2( }; } else if (globalConfig.testNamePattern) { const testNameRegex = new RegExp(globalConfig.testNamePattern, 'i'); - env.specFilter = spec => testNameRegex.test(spec.getFullName()); + env.specFilter = (spec: Spec) => testNameRegex.test(spec.getFullName()); } runtime.requireModule(testPath); @@ -168,14 +172,19 @@ async function jasmine2( return addSnapshotData(results, snapshotState); } -const addSnapshotData = (results, snapshotState) => { - results.testResults.forEach(({fullName, status}) => { - if (status === 'pending' || status === 'failed') { - // if test is skipped or failed, we don't want to mark - // its snapshots as obsolete. - snapshotState.markSnapshotsAsCheckedForTest(fullName); - } - }); +const addSnapshotData = ( + results: TestResult.TestResult, + snapshotState: SnapshotStateType, +) => { + results.testResults.forEach( + ({fullName, status}: TestResult.AssertionResult) => { + if (status === 'pending' || status === 'failed') { + // if test is skipped or failed, we don't want to mark + // its snapshots as obsolete. + snapshotState.markSnapshotsAsCheckedForTest(fullName); + } + }, + ); const uncheckedCount = snapshotState.getUncheckedCount(); const uncheckedKeys = snapshotState.getUncheckedKeys(); @@ -197,4 +206,9 @@ const addSnapshotData = (results, snapshotState) => { return results; }; -module.exports = jasmine2; +// eslint-disable-next-line no-redeclare +namespace jasmine2 { + export type Jasmine = JestJasmine; +} + +export = jasmine2; diff --git a/packages/jest-jasmine2/src/isError.js b/packages/jest-jasmine2/src/isError.ts similarity index 98% rename from packages/jest-jasmine2/src/isError.js rename to packages/jest-jasmine2/src/isError.ts index 2ffab4c3c7ff..186bff452b0e 100644 --- a/packages/jest-jasmine2/src/isError.js +++ b/packages/jest-jasmine2/src/isError.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import prettyFormat from 'pretty-format'; diff --git a/packages/jest-jasmine2/src/jasmine/CallTracker.js b/packages/jest-jasmine2/src/jasmine/CallTracker.ts similarity index 54% rename from packages/jest-jasmine2/src/jasmine/CallTracker.js rename to packages/jest-jasmine2/src/jasmine/CallTracker.ts index 3f676d118a05..02a0b767ffdf 100644 --- a/packages/jest-jasmine2/src/jasmine/CallTracker.js +++ b/packages/jest-jasmine2/src/jasmine/CallTracker.ts @@ -28,52 +28,69 @@ 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. */ -/* @flow */ -function CallTracker() { - let calls = []; +export type Context = { + object: unknown; + args: Array; + returnValue?: unknown; +}; - this.track = function(context) { - calls.push(context); - }; +class CallTracker { + track: (context: Context) => void; + any: () => boolean; + count: () => number; + argsFor: (index: number) => Array; + all: () => Array; + allArgs: () => Array; + first: () => Context; + mostRecent: () => Context; + reset: () => void; - this.any = function() { - return !!calls.length; - }; + constructor() { + let calls: Array = []; - this.count = function() { - return calls.length; - }; + this.track = function(context: Context) { + calls.push(context); + }; - this.argsFor = function(index) { - const call = calls[index]; - return call ? call.args : []; - }; + this.any = function() { + return !!calls.length; + }; - this.all = function() { - return calls; - }; + this.count = function() { + return calls.length; + }; - this.allArgs = function() { - const callArgs = []; - for (let i = 0; i < calls.length; i++) { - callArgs.push(calls[i].args); - } + this.argsFor = function(index) { + const call = calls[index]; + return call ? call.args : []; + }; - return callArgs; - }; + this.all = function() { + return calls; + }; - this.first = function() { - return calls[0]; - }; + this.allArgs = function() { + const callArgs = []; + for (let i = 0; i < calls.length; i++) { + callArgs.push(calls[i].args); + } - this.mostRecent = function() { - return calls[calls.length - 1]; - }; + return callArgs; + }; - this.reset = function() { - calls = []; - }; + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } } export default CallTracker; diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js deleted file mode 100644 index c1e6b70992b0..000000000000 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ /dev/null @@ -1,612 +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 queueRunner from '../queueRunner'; -import treeProcessor from '../treeProcessor'; -import isError from '../isError'; -import assertionErrorMessage from '../assertionErrorMessage'; -import {ErrorWithStack} 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; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - 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 new file mode 100644 index 000000000000..365c72855fbb --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Env.ts @@ -0,0 +1,666 @@ +/** + * 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 {ErrorWithStack} from 'jest-util'; +import queueRunner, { + Options as QueueRunnerOptions, + QueueableFn, +} from '../queueRunner'; +import treeProcessor, {TreeNode} from '../treeProcessor'; +import isError from '../isError'; +import assertionErrorMessage from '../assertionErrorMessage'; +import {Jasmine, AssertionErrorWithStack, Reporter, Spy} from '../types'; +import Spec, {SpecResult} from './Spec'; +import Suite from './Suite'; + +export default function(j$: Jasmine) { + return class Env { + specFilter: (spec: Spec) => boolean; + catchExceptions: (value: unknown) => boolean; + throwOnExpectationFailure: (value: unknown) => void; + catchingExceptions: () => boolean; + topSuite: () => Suite; + fail: (error: Error | AssertionErrorWithStack) => void; + pending: (message: string) => void; + afterAll: (afterAllFunction: QueueableFn['fn'], timeout?: number) => void; + fit: (description: string, fn: QueueableFn['fn'], timeout?: number) => void; + throwingExpectationFailures: () => boolean; + randomizeTests: (value: unknown) => void; + randomTests: () => boolean; + seed: (value: unknown) => unknown; + execute: ( + runnablesToRun: Array, + suiteTree?: Suite, + ) => Promise; + fdescribe: (description: string, specDefinitions: Function) => Suite; + spyOn: ( + obj: {[key: string]: any}, + methodName: string, + accessType?: keyof PropertyDescriptor, + ) => Spy; + beforeEach: ( + beforeEachFunction: QueueableFn['fn'], + timeout?: number, + ) => void; + afterEach: (afterEachFunction: QueueableFn['fn'], timeout?: number) => void; + clearReporters: () => void; + addReporter: (reporterToAdd: Reporter) => void; + it: (description: string, fn: QueueableFn['fn'], timeout?: number) => Spec; + xdescribe: (description: string, specDefinitions: Function) => Suite; + xit: (description: string, fn: QueueableFn['fn'], timeout?: number) => any; + beforeAll: (beforeAllFunction: QueueableFn['fn'], timeout?: number) => void; + todo: () => Spec; + provideFallbackReporter: (reporterToAdd: Reporter) => void; + allowRespy: (allow: boolean) => void; + describe: (description: string, specDefinitions: Function) => Suite; + + constructor(_options?: object) { + let totalSpecsDefined = 0; + + let catchExceptions = true; + + const realSetTimeout = global.setTimeout; + const realClearTimeout = global.clearTimeout; + + const runnableResources: {[key: string]: {spies: Array}} = {}; + const currentlyExecutingSuites: Array = []; + let currentSpec: Spec | null = null; + let throwOnExpectationFailure = false; + let random = false; + let seed: unknown | null = null; + let nextSpecId = 0; + let nextSuiteId = 0; + + const getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + const getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + const topSuite = new j$.Suite({ + id: getNextSuiteId(), + description: '', + getTestPath() { + return j$.testPath; + }, + }); + let currentDeclarationSuite = topSuite; + + 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; + }; + + const defaultResourcesForRunnable = function( + id: string, + _parentRunnableId?: string, + ) { + const resources = {spies: []}; + + runnableResources[id] = resources; + }; + + const clearResourcesForRunnable = function(id: string) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + const beforeAndAfterFns = function(suite: Suite) { + return function() { + let afters: Array = []; + let befores: Array = []; + + while (suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite!; + } + + return { + befores: befores.reverse(), + afters, + }; + }; + }; + + const getSpecName = function(spec: Spec, suite: 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; + }; + + const queueRunnerFactory = (options: QueueRunnerOptions) => { + options.clearTimeout = realClearTimeout; + options.fail = this.fail; + options.setTimeout = realSetTimeout; + return queueRunner(options); + }; + + this.topSuite = function() { + return topSuite; + }; + + const uncaught: NodeJS.UncaughtExceptionListener & + NodeJS.UnhandledRejectionListener = (err: any) => { + if (currentSpec) { + currentSpec.onException(err); + currentSpec.cancel(); + } else { + console.error('Unhandled error'); + console.error(err.stack); + } + }; + + let oldListenersException: Array; + let oldListenersRejection: Array; + 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 as 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 as TreeNode, + }); + + 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(...args) { + return spyRegistry.spyOn.apply(spyRegistry, args); + }; + + const suiteFactory = function(description: string) { + const suite = new j$.Suite({ + id: getNextSuiteId(), + description, + parentSuite: currentDeclarationSuite, + throwOnExpectationFailure, + getTestPath() { + return j$.testPath; + }, + }); + + return suite; + }; + + this.describe = function(description: string, 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) { + // @ts-ignore TODO Possible error: Suite does not have todo method + 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: Array = []; + + this.fdescribe = function(description, specDefinitions) { + const suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + const addSpecsToSuite = (suite: Suite, specDefinitions: Function) => { + const parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + let declarationError: null | Error = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + this.it('encountered a declaration exception', () => { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + }; + + function findFocusedAncestor(suite: 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 = ( + description: string, + fn: QueueableFn['fn'], + suite: Suite, + timeout?: number, + ): Spec => { + totalSpecsDefined++; + const spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + resultCallback: specResultCallback, + getSpecName(spec: 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 (!this.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result: SpecResult) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec: 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(...args) { + const spec = this.it.apply(this, args); + 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; + // @ts-ignore TODO Possible error: j$.Spec does not have expand property + 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, + }); + }; + } + }; +} diff --git a/packages/jest-jasmine2/src/jasmine/JsApiReporter.js b/packages/jest-jasmine2/src/jasmine/JsApiReporter.js deleted file mode 100644 index 8919b695ab44..000000000000 --- a/packages/jest-jasmine2/src/jasmine/JsApiReporter.js +++ /dev/null @@ -1,116 +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. -*/ -/* @flow */ -/* eslint-disable sort-keys */ - -const noopTimer = { - start() {}, - elapsed() { - return 0; - }, -}; - -export default function JsApiReporter(options: Object) { - const timer = options.timer || noopTimer; - let status = 'loaded'; - - this.started = false; - this.finished = false; - this.runDetails = {}; - - this.jasmineStarted = function() { - this.started = true; - status = 'started'; - timer.start(); - }; - - let executionTime; - - function validateAfterAllExceptions({failedExpectations}) { - if (failedExpectations && failedExpectations.length > 0) { - throw failedExpectations[0]; - } - } - - this.jasmineDone = function(runDetails) { - validateAfterAllExceptions(runDetails); - this.finished = true; - this.runDetails = runDetails; - executionTime = timer.elapsed(); - status = 'done'; - }; - - this.status = function() { - return status; - }; - - const suites = []; - const suites_hash = {}; - - this.suiteStarted = function(result) { - suites_hash[result.id] = result; - }; - - this.suiteDone = function(result) { - storeSuite(result); - }; - - this.suiteResults = function(index, length) { - return suites.slice(index, index + length); - }; - - function storeSuite(result) { - suites.push(result); - suites_hash[result.id] = result; - } - - this.suites = function() { - return suites_hash; - }; - - const specs = []; - - this.specDone = function(result) { - specs.push(result); - }; - - this.specResults = function(index, length) { - return specs.slice(index, index + length); - }; - - this.specs = function() { - return specs; - }; - - this.executionTime = function() { - return executionTime; - }; -} diff --git a/packages/jest-jasmine2/src/jasmine/JsApiReporter.ts b/packages/jest-jasmine2/src/jasmine/JsApiReporter.ts new file mode 100644 index 000000000000..8a726f7aa6f7 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/JsApiReporter.ts @@ -0,0 +1,141 @@ +/** + * 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 {Reporter, RunDetails} from '../types'; +import {SpecResult} from './Spec'; +import {SuiteResult} from './Suite'; +import Timer from './Timer'; + +const noopTimer = { + start() {}, + elapsed() { + return 0; + }, +}; + +export default class JsApiReporter implements Reporter { + started: boolean; + finished: boolean; + runDetails: RunDetails; + jasmineStarted: (runDetails: RunDetails) => void; + jasmineDone: (runDetails: RunDetails) => void; + status: () => unknown; + executionTime: () => unknown; + + suiteStarted: (result: SuiteResult) => void; + suiteDone: (result: SuiteResult) => void; + suiteResults: (index: number, length: number) => Array; + suites: () => {[key: string]: SuiteResult}; + + specResults: (index: number, length: number) => Array; + specDone: (result: SpecResult) => void; + specs: () => Array; + specStarted: (spec: SpecResult) => void; + + constructor(options: {timer?: Timer}) { + const timer = options.timer || noopTimer; + let status = 'loaded'; + + this.started = false; + this.finished = false; + this.runDetails = {}; + + this.jasmineStarted = () => { + this.started = true; + status = 'started'; + timer.start(); + }; + + let executionTime: number; + + function validateAfterAllExceptions({failedExpectations}: RunDetails) { + if (failedExpectations && failedExpectations.length > 0) { + throw failedExpectations[0]; + } + } + + this.jasmineDone = function(runDetails) { + validateAfterAllExceptions(runDetails); + this.finished = true; + this.runDetails = runDetails; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + const suites: Array = []; + const suites_hash: {[key: string]: SuiteResult} = {}; + + this.specStarted = function() {}; + + this.suiteStarted = function(result: SuiteResult) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result: SuiteResult) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result: SuiteResult) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + const specs: Array = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + } +} diff --git a/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js b/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js deleted file mode 100644 index aa1ed23997e7..000000000000 --- a/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js +++ /dev/null @@ -1,73 +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. -*/ -/* @flow */ - -export default function ReportDispatcher(methods: Array) { - const dispatchedMethods = methods || []; - - for (let i = 0; i < dispatchedMethods.length; i++) { - const method = dispatchedMethods[i]; - this[method] = (function(m) { - return function() { - dispatch(m, arguments); - }; - })(method); - } - - let reporters = []; - let fallbackReporter = null; - - this.addReporter = function(reporter) { - reporters.push(reporter); - }; - - this.provideFallbackReporter = function(reporter) { - fallbackReporter = reporter; - }; - - this.clearReporters = function() { - reporters = []; - }; - - return this; - - function dispatch(method, args) { - if (reporters.length === 0 && fallbackReporter !== null) { - reporters.push(fallbackReporter); - } - for (let i = 0; i < reporters.length; i++) { - const reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, args); - } - } - } -} diff --git a/packages/jest-jasmine2/src/jasmine/ReportDispatcher.ts b/packages/jest-jasmine2/src/jasmine/ReportDispatcher.ts new file mode 100644 index 000000000000..dab52c999020 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/ReportDispatcher.ts @@ -0,0 +1,95 @@ +/** + * 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. +*/ +import {Reporter, RunDetails} from '../types'; +import {SpecResult} from './Spec'; +import {SuiteResult} from './Suite'; + +export default class ReportDispatcher implements Reporter { + addReporter: (reporter: Reporter) => void; + provideFallbackReporter: (reporter: Reporter) => void; + clearReporters: () => void; + + // @ts-ignore + jasmineDone: (runDetails: RunDetails) => void; + // @ts-ignore + jasmineStarted: (runDetails: RunDetails) => void; + // @ts-ignore + specDone: (result: SpecResult) => void; + // @ts-ignore + specStarted: (spec: SpecResult) => void; + // @ts-ignore + suiteDone: (result: SuiteResult) => void; + // @ts-ignore + suiteStarted: (result: SuiteResult) => void; + + constructor(methods: Array) { + const dispatchedMethods = methods || []; + + for (let i = 0; i < dispatchedMethods.length; i++) { + const method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + })(method); + } + + let reporters: Array = []; + let fallbackReporter: Reporter | null = null; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + this.provideFallbackReporter = function(reporter) { + fallbackReporter = reporter; + }; + + this.clearReporters = function() { + reporters = []; + }; + + return this; + + function dispatch(method: keyof Reporter, args: any) { + if (reporters.length === 0 && fallbackReporter !== null) { + reporters.push(fallbackReporter); + } + for (let i = 0; i < reporters.length; i++) { + const reporter = reporters[i]; + if (reporter[method]) { + // @ts-ignore + reporter[method].apply(reporter, args); + } + } + } + } +} diff --git a/packages/jest-jasmine2/src/jasmine/Spec.js b/packages/jest-jasmine2/src/jasmine/Spec.js deleted file mode 100644 index fd729e0b2c80..000000000000 --- a/packages/jest-jasmine2/src/jasmine/Spec.js +++ /dev/null @@ -1,239 +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. -*/ -/* @flow */ -/* eslint-disable sort-keys */ - -import {AssertionError} from 'assert'; - -import ExpectationFailed from '../ExpectationFailed'; - -import expectationResultFactory from '../expectationResultFactory'; - -import assertionErrorMessage from '../assertionErrorMessage'; - -export default function Spec(attrs: Object) { - this.resultCallback = attrs.resultCallback || function() {}; - this.id = attrs.id; - this.description = attrs.description || ''; - this.queueableFn = attrs.queueableFn; - this.beforeAndAfterFns = - attrs.beforeAndAfterFns || - function() { - return {befores: [], afters: []}; - }; - this.userContext = - attrs.userContext || - function() { - return {}; - }; - this.onStart = attrs.onStart || function() {}; - this.getSpecName = - attrs.getSpecName || - function() { - return ''; - }; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - - this.initError = new Error(); - this.initError.name = ''; - - // Without this line v8 stores references to all closures - // in the stack in the Error object. This line stringifies the stack - // property to allow garbage-collecting objects on the stack - // https://crbug.com/v8/7142 - this.initError.stack = this.initError.stack; - - this.queueableFn.initError = this.initError; - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [], - pendingReason: '', - testPath: attrs.getTestPath(), - }; -} - -Spec.prototype.addExpectationResult = function(passed, data, isError) { - const expectationResult = expectationResultFactory(data, this.initError); - if (passed) { - this.result.passedExpectations.push(expectationResult); - } else { - this.result.failedExpectations.push(expectationResult); - - if (this.throwOnExpectationFailure && !isError) { - throw new ExpectationFailed(); - } - } -}; - -Spec.prototype.execute = function(onComplete, enabled) { - const self = this; - - this.onStart(this); - - if ( - !this.isExecutable() || - this.markedPending || - this.markedTodo || - enabled === false - ) { - complete(enabled); - return; - } - - const fns = this.beforeAndAfterFns(); - const allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); - - this.currentRun = this.queueRunnerFactory({ - queueableFns: allFns, - onException() { - self.onException.apply(self, arguments); - }, - userContext: this.userContext(), - }); - - this.currentRun.then(() => complete(true)); - - function complete(enabledAgain) { - self.result.status = self.status(enabledAgain); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } -}; - -Spec.prototype.cancel = function cancel() { - if (this.currentRun) { - this.currentRun.cancel(); - } -}; - -Spec.prototype.onException = function onException(error) { - if (Spec.isPendingSpecException(error)) { - this.pend(extractCustomPendingMessage(error)); - return; - } - - if (error instanceof ExpectationFailed) { - return; - } - - if (error instanceof AssertionError) { - error = assertionErrorMessage(error, {expand: this.expand}); - } - - this.addExpectationResult( - false, - { - matcherName: '', - passed: false, - expected: '', - actual: '', - error, - }, - true, - ); -}; - -Spec.prototype.disable = function() { - this.disabled = true; -}; - -Spec.prototype.pend = function(message) { - this.markedPending = true; - if (message) { - this.result.pendingReason = message; - } -}; - -Spec.prototype.todo = function() { - this.markedTodo = true; -}; - -Spec.prototype.getResult = function() { - this.result.status = this.status(); - return this.result; -}; - -Spec.prototype.status = function(enabled) { - if (this.disabled || enabled === false) { - return 'disabled'; - } - - if (this.markedTodo) { - return 'todo'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'passed'; - } -}; - -Spec.prototype.isExecutable = function() { - return !this.disabled; -}; - -Spec.prototype.getFullName = function() { - return this.getSpecName(this); -}; - -const extractCustomPendingMessage = function(e) { - const fullMessage = e.toString(); - const boilerplateStart = fullMessage.indexOf( - Spec.pendingSpecExceptionMessage, - ); - const boilerplateEnd = - boilerplateStart + Spec.pendingSpecExceptionMessage.length; - - return fullMessage.substr(boilerplateEnd); -}; - -Spec.pendingSpecExceptionMessage = '=> marked Pending'; - -Spec.isPendingSpecException = function(e) { - return !!( - e && - e.toString && - e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1 - ); -}; diff --git a/packages/jest-jasmine2/src/jasmine/Spec.ts b/packages/jest-jasmine2/src/jasmine/Spec.ts new file mode 100644 index 000000000000..7a60ac484016 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Spec.ts @@ -0,0 +1,307 @@ +/** + * 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 {Config, TestResult} from '@jest/types'; + +import ExpectationFailed from '../ExpectationFailed'; +import expectationResultFactory, { + Options as ExpectationResultFactoryOptions, +} from '../expectationResultFactory'; +import assertionErrorMessage from '../assertionErrorMessage'; +import queueRunner, {QueueableFn} from '../queueRunner'; +import {AssertionErrorWithStack} from '../types'; + +export type Attributes = { + id: string; + resultCallback: (result: Spec['result']) => void; + description: string; + throwOnExpectationFailure: unknown; + getTestPath: () => Config.Path; + queueableFn: QueueableFn; + beforeAndAfterFns: () => { + befores: Array; + afters: Array; + }; + userContext: () => unknown; + onStart: (context: Spec) => void; + getSpecName: (spec: Spec) => string; + queueRunnerFactory: typeof queueRunner; +}; + +export type SpecResult = { + id: string; + description: string; + fullName: string; + duration?: TestResult.Milliseconds; + failedExpectations: Array; + testPath: Config.Path; + passedExpectations: Array>; + pendingReason: string; + status: TestResult.Status; + __callsite?: { + getColumnNumber: () => number; + getLineNumber: () => number; + }; +}; + +export default class Spec { + id: string; + description: string; + resultCallback: (result: SpecResult) => void; + queueableFn: QueueableFn; + beforeAndAfterFns: () => { + befores: Array; + afters: Array; + }; + userContext: () => unknown; + onStart: (spec: Spec) => void; + getSpecName: (spec: Spec) => string; + queueRunnerFactory: typeof queueRunner; + throwOnExpectationFailure: boolean; + initError: Error; + result: SpecResult; + disabled?: boolean; + currentRun?: ReturnType; + markedTodo?: boolean; + markedPending?: boolean; + expand?: boolean; + + static pendingSpecExceptionMessage: string; + + static isPendingSpecException(e: Error) { + return !!( + e && + e.toString && + e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1 + ); + } + + constructor(attrs: Attributes) { + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = + attrs.beforeAndAfterFns || + function() { + return {befores: [], afters: []}; + }; + this.userContext = + attrs.userContext || + function() { + return {}; + }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = + attrs.getSpecName || + function() { + return ''; + }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.initError = new Error(); + this.initError.name = ''; + + // Without this line v8 stores references to all closures + // in the stack in the Error object. This line stringifies the stack + // property to allow garbage-collecting objects on the stack + // https://crbug.com/v8/7142 + this.initError.stack = this.initError.stack; + + this.queueableFn.initError = this.initError; + + // @ts-ignore + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '', + testPath: attrs.getTestPath(), + }; + } + + addExpectationResult( + passed: boolean, + data: ExpectationResultFactoryOptions, + isError?: boolean, + ) { + const expectationResult = expectationResultFactory(data, this.initError); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new ExpectationFailed(); + } + } + } + + execute(onComplete: Function, enabled: boolean) { + const self = this; + + this.onStart(this); + + if ( + !this.isExecutable() || + this.markedPending || + this.markedTodo || + enabled === false + ) { + complete(enabled); + return; + } + + const fns = this.beforeAndAfterFns(); + const allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.currentRun = this.queueRunnerFactory({ + queueableFns: allFns, + onException() { + // @ts-ignore + self.onException.apply(self, arguments); + }, + userContext: this.userContext(), + setTimeout, + clearTimeout, + fail: () => {}, + }); + + this.currentRun.then(() => complete(true)); + + function complete(enabledAgain: boolean) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + } + + cancel() { + if (this.currentRun) { + this.currentRun.cancel(); + } + } + + onException(error: ExpectationFailed | AssertionErrorWithStack) { + if (Spec.isPendingSpecException(error)) { + this.pend(extractCustomPendingMessage(error)); + return; + } + + if (error instanceof ExpectationFailed) { + return; + } + + this.addExpectationResult( + false, + { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: + error instanceof AssertionError + ? assertionErrorMessage(error, {expand: this.expand}) + : error, + }, + true, + ); + } + + disable() { + this.disabled = true; + } + + pend(message?: string) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } + } + + todo() { + this.markedTodo = true; + } + + getResult() { + this.result.status = this.status(); + return this.result; + } + + status(enabled?: boolean) { + if (this.disabled || enabled === false) { + return 'disabled'; + } + + if (this.markedTodo) { + return 'todo'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + } + + isExecutable() { + return !this.disabled; + } + + getFullName() { + return this.getSpecName(this); + } +} + +Spec.pendingSpecExceptionMessage = '=> marked Pending'; + +const extractCustomPendingMessage = function(e: Error) { + const fullMessage = e.toString(); + const boilerplateStart = fullMessage.indexOf( + Spec.pendingSpecExceptionMessage, + ); + const boilerplateEnd = + boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); +}; diff --git a/packages/jest-jasmine2/src/jasmine/SpyStrategy.js b/packages/jest-jasmine2/src/jasmine/SpyStrategy.js deleted file mode 100644 index 2a996f085248..000000000000 --- a/packages/jest-jasmine2/src/jasmine/SpyStrategy.js +++ /dev/null @@ -1,91 +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. -*/ -/* @flow */ - -export default function SpyStrategy(options: Object) { - options = options || {}; - - const identity = options.name || 'unknown'; - const originalFn = options.fn || function() {}; - const getSpy = options.getSpy || function() {}; - let plan = function() {}; - - this.identity = function() { - return identity; - }; - - this.exec = function() { - return plan.apply(this, arguments); - }; - - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - this.returnValues = function() { - const values = Array.prototype.slice.call(arguments); - plan = function() { - return values.shift(); - }; - return getSpy(); - }; - - this.throwError = function(something) { - const error = something instanceof Error ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - this.callFake = function(fn) { - if (typeof fn !== 'function') { - throw new Error( - 'Argument passed to callFake should be a function, got ' + fn, - ); - } - plan = fn; - return getSpy(); - }; - - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; -} diff --git a/packages/jest-jasmine2/src/jasmine/SpyStrategy.ts b/packages/jest-jasmine2/src/jasmine/SpyStrategy.ts new file mode 100644 index 000000000000..59c2faf24c05 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/SpyStrategy.ts @@ -0,0 +1,103 @@ +/** + * 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. +*/ + +export default class SpyStrategy { + identity: () => string; + exec: (...args: Array) => unknown; + callThrough: () => unknown; + returnValue: (value: unknown) => unknown; + returnValues: () => unknown; + throwError: (something: string | Error) => unknown; + callFake: (fn: Function) => unknown; + stub: (fn: Function) => unknown; + + constructor({ + name = 'unknown', + fn = function() {}, + getSpy = function() {}, + }: {name?: string; fn?: Function; getSpy?: () => unknown} = {}) { + const identity = name; + const originalFn = fn; + let plan: Function = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.returnValues = function() { + const values = Array.prototype.slice.call(arguments); + plan = function() { + return values.shift(); + }; + return getSpy(); + }; + + this.throwError = function(something) { + const error = + something instanceof Error ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + if (typeof fn !== 'function') { + throw new Error( + 'Argument passed to callFake should be a function, got ' + fn, + ); + } + plan = fn; + return getSpy(); + }; + + this.stub = function(_fn) { + plan = function() {}; + return getSpy(); + }; + } +} diff --git a/packages/jest-jasmine2/src/jasmine/Suite.js b/packages/jest-jasmine2/src/jasmine/Suite.js deleted file mode 100644 index 758a16c32882..000000000000 --- a/packages/jest-jasmine2/src/jasmine/Suite.js +++ /dev/null @@ -1,191 +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. -*/ -/* @flow */ -/* eslint-disable sort-keys */ - -import {convertDescriptorToString} from 'jest-util'; -import ExpectationFailed from '../ExpectationFailed'; -import expectationResultFactory from '../expectationResultFactory'; - -export default function Suite(attrs: Object) { - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = convertDescriptorToString(attrs.description); - this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - - this.beforeFns = []; - this.afterFns = []; - this.beforeAllFns = []; - this.afterAllFns = []; - this.disabled = false; - - this.children = []; - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - testPath: attrs.getTestPath(), - }; -} - -Suite.prototype.getFullName = function() { - const fullName = []; - for ( - let parentSuite = this; - parentSuite; - parentSuite = parentSuite.parentSuite - ) { - if (parentSuite.parentSuite) { - fullName.unshift(parentSuite.description); - } - } - return fullName.join(' '); -}; - -Suite.prototype.disable = function() { - this.disabled = true; -}; - -Suite.prototype.pend = function(message) { - this.markedPending = true; -}; - -Suite.prototype.beforeEach = function(fn) { - this.beforeFns.unshift(fn); -}; - -Suite.prototype.beforeAll = function(fn) { - this.beforeAllFns.push(fn); -}; - -Suite.prototype.afterEach = function(fn) { - this.afterFns.unshift(fn); -}; - -Suite.prototype.afterAll = function(fn) { - this.afterAllFns.unshift(fn); -}; - -Suite.prototype.addChild = function(child) { - this.children.push(child); -}; - -Suite.prototype.status = function() { - if (this.disabled) { - return 'disabled'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'finished'; - } -}; - -Suite.prototype.isExecutable = function() { - return !this.disabled; -}; - -Suite.prototype.canBeReentered = function() { - return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; -}; - -Suite.prototype.getResult = function() { - this.result.status = this.status(); - return this.result; -}; - -Suite.prototype.sharedUserContext = function() { - if (!this.sharedContext) { - this.sharedContext = {}; - } - - return this.sharedContext; -}; - -Suite.prototype.clonedSharedUserContext = function() { - return this.sharedUserContext(); -}; - -Suite.prototype.onException = function() { - if (arguments[0] instanceof ExpectationFailed) { - return; - } - - if (isAfterAll(this.children)) { - const data = { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: arguments[0], - }; - this.result.failedExpectations.push(expectationResultFactory(data)); - } else { - for (let i = 0; i < this.children.length; i++) { - const child = this.children[i]; - child.onException.apply(child, arguments); - } - } -}; - -Suite.prototype.addExpectationResult = function() { - if (isAfterAll(this.children) && isFailure(arguments)) { - const data = arguments[1]; - this.result.failedExpectations.push(expectationResultFactory(data)); - if (this.throwOnExpectationFailure) { - throw new ExpectationFailed(); - } - } else { - for (let i = 0; i < this.children.length; i++) { - const child = this.children[i]; - try { - child.addExpectationResult.apply(child, arguments); - } catch (e) { - // keep going - } - } - } -}; - -function isAfterAll(children) { - return children && children[0] && children[0].result.status; -} - -function isFailure(args) { - return !args[0]; -} diff --git a/packages/jest-jasmine2/src/jasmine/Suite.ts b/packages/jest-jasmine2/src/jasmine/Suite.ts new file mode 100644 index 000000000000..0d781b3053ca --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Suite.ts @@ -0,0 +1,226 @@ +/** + * 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 {convertDescriptorToString} from 'jest-util'; +import {Config} from '@jest/types'; +import ExpectationFailed from '../ExpectationFailed'; +import expectationResultFactory from '../expectationResultFactory'; +import {QueueableFn} from '../queueRunner'; +import Spec from './Spec'; + +export type SuiteResult = { + id: string; + description: string; + fullName: string; + failedExpectations: Array>; + testPath: Config.Path; + status?: string; +}; + +export type Attributes = { + id: string; + parentSuite?: Suite; + description: string; + throwOnExpectationFailure?: boolean; + getTestPath: () => Config.Path; +}; + +export default class Suite { + id: string; + parentSuite?: Suite; + description: string; + throwOnExpectationFailure: boolean; + beforeFns: Array; + afterFns: Array; + beforeAllFns: Array; + afterAllFns: Array; + disabled: boolean; + children: Array; + result: SuiteResult; + sharedContext?: object; + markedPending: boolean; + markedTodo: boolean; + isFocused: boolean; + + constructor(attrs: Attributes) { + this.markedPending = false; + this.markedTodo = false; + this.isFocused = false; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = convertDescriptorToString(attrs.description); + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + testPath: attrs.getTestPath(), + }; + } + getFullName() { + const fullName = []; + for ( + let parentSuite: Suite | undefined = this; + parentSuite; + parentSuite = parentSuite.parentSuite + ) { + if (parentSuite.parentSuite) { + fullName.unshift(parentSuite.description); + } + } + return fullName.join(' '); + } + disable() { + this.disabled = true; + } + pend(_message?: string) { + this.markedPending = true; + } + beforeEach(fn: QueueableFn) { + this.beforeFns.unshift(fn); + } + beforeAll(fn: QueueableFn) { + this.beforeAllFns.push(fn); + } + afterEach(fn: QueueableFn) { + this.afterFns.unshift(fn); + } + afterAll(fn: QueueableFn) { + this.afterAllFns.unshift(fn); + } + + addChild(child: Suite | Spec) { + this.children.push(child); + } + + status() { + if (this.disabled) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + } + + isExecutable() { + return !this.disabled; + } + + canBeReentered() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + } + + getResult() { + this.result.status! = this.status(); + return this.result; + } + + sharedUserContext() { + if (!this.sharedContext) { + this.sharedContext = {}; + } + + return this.sharedContext; + } + + clonedSharedUserContext() { + return this.sharedUserContext(); + } + + onException(...args: Parameters) { + if (args[0] instanceof ExpectationFailed) { + return; + } + + if (isAfterAll(this.children)) { + const data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0], + }; + this.result.failedExpectations.push(expectationResultFactory(data)); + } else { + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + child.onException.apply(child, args); + } + } + } + + addExpectationResult(...args: Parameters) { + if (isAfterAll(this.children) && isFailure(args)) { + const data = args[1]; + this.result.failedExpectations.push(expectationResultFactory(data)); + if (this.throwOnExpectationFailure) { + throw new ExpectationFailed(); + } + } else { + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + try { + child.addExpectationResult.apply(child, args); + } catch (e) { + // keep going + } + } + } + } + + execute(..._args: Array) {} +} + +function isAfterAll(children: Array) { + return children && children[0] && children[0].result.status; +} + +function isFailure(args: Array) { + return !args[0]; +} diff --git a/packages/jest-jasmine2/src/jasmine/Timer.js b/packages/jest-jasmine2/src/jasmine/Timer.ts similarity index 80% rename from packages/jest-jasmine2/src/jasmine/Timer.js rename to packages/jest-jasmine2/src/jasmine/Timer.ts index be908df48185..fe6a8f9ec9fa 100644 --- a/packages/jest-jasmine2/src/jasmine/Timer.js +++ b/packages/jest-jasmine2/src/jasmine/Timer.ts @@ -28,7 +28,6 @@ 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. */ -/* @flow */ const defaultNow = (function(Date) { return function() { @@ -36,17 +35,22 @@ const defaultNow = (function(Date) { }; })(Date); -export default function Timer(options: Object) { - options = options || {}; +export default class Timer { + start: () => void; + elapsed: () => number; - const now = options.now || defaultNow; - let startTime; + constructor(options?: {now?: () => number}) { + options = options || {}; - this.start = function() { - startTime = now(); - }; + const now = options.now || defaultNow; + let startTime: number; - this.elapsed = function() { - return now() - startTime; - }; + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } } diff --git a/packages/jest-jasmine2/src/jasmine/createSpy.js b/packages/jest-jasmine2/src/jasmine/createSpy.ts similarity index 87% rename from packages/jest-jasmine2/src/jasmine/createSpy.js rename to packages/jest-jasmine2/src/jasmine/createSpy.ts index ba1a3485171c..97be40b1c55c 100644 --- a/packages/jest-jasmine2/src/jasmine/createSpy.js +++ b/packages/jest-jasmine2/src/jasmine/createSpy.ts @@ -30,11 +30,16 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* eslint-disable sort-keys */ -import CallTracker from './CallTracker'; - +import {Spy} from '../types'; +import CallTracker, {Context} from './CallTracker'; import SpyStrategy from './SpyStrategy'; -function createSpy(name, originalFn) { +interface Fn { + (): any; + [key: string]: any; +} + +function createSpy(name: string, originalFn: Fn): Spy { const spyStrategy = new SpyStrategy({ name, fn: originalFn, @@ -43,14 +48,14 @@ function createSpy(name, originalFn) { }, }); const callTracker = new CallTracker(); - const spy = function() { - const callData = { + const spy: Spy = function(...args) { + const callData: Context = { object: this, args: Array.prototype.slice.apply(arguments), }; callTracker.track(callData); - const returnValue = spyStrategy.exec.apply(this, arguments); + const returnValue = spyStrategy.exec.apply(this, args); callData.returnValue = returnValue; return returnValue; diff --git a/packages/jest-jasmine2/src/jasmine/jasmineLight.js b/packages/jest-jasmine2/src/jasmine/jasmineLight.ts similarity index 91% rename from packages/jest-jasmine2/src/jasmine/jasmineLight.js rename to packages/jest-jasmine2/src/jasmine/jasmineLight.ts index 341e4c211647..bde9103904fb 100644 --- a/packages/jest-jasmine2/src/jasmine/jasmineLight.js +++ b/packages/jest-jasmine2/src/jasmine/jasmineLight.ts @@ -28,11 +28,9 @@ 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. */ -/* @flow */ /* eslint-disable sort-keys */ -import type {Jasmine} from 'types/Jasmine'; - +import {Jasmine} from '../types'; import createSpy from './createSpy'; import Env from './Env'; import JsApiReporter from './JsApiReporter'; @@ -42,12 +40,12 @@ import SpyRegistry from './spyRegistry'; import Suite from './Suite'; import Timer from './Timer'; -exports.create = function(createOptions: Object) { - const j$ = {...createOptions}; +const create = function(createOptions: Record): Jasmine { + const j$ = {...createOptions} as Jasmine; j$._DEFAULT_TIMEOUT_INTERVAL = 5000; - j$.getEnv = function(options: Object) { + j$.getEnv = function(options?: object) { const env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); //jasmine. singletons in here (setTimeout blah blah). return env; @@ -65,8 +63,7 @@ exports.create = function(createOptions: Object) { return j$; }; -// Interface is a reserved word in strict mode, so can't export it as ESM -exports.interface = function(jasmine: Jasmine, env: any) { +const _interface = function(jasmine: Jasmine, env: any) { const jasmineInterface = { describe(description: string, specDefinitions: Function) { return env.describe(description, specDefinitions); @@ -136,7 +133,7 @@ exports.interface = function(jasmine: Jasmine, env: any) { return env.fail.apply(env, arguments); }, - spyOn(obj: Object, methodName: string, accessType?: string) { + spyOn(obj: Record, methodName: string, accessType?: string) { return env.spyOn(obj, methodName, accessType); }, @@ -149,3 +146,6 @@ exports.interface = function(jasmine: Jasmine, env: any) { return jasmineInterface; }; + +// Interface is a reserved word in strict mode, so can't export it as ESM +export = {create, interface: _interface}; diff --git a/packages/jest-jasmine2/src/jasmine/spyRegistry.js b/packages/jest-jasmine2/src/jasmine/spyRegistry.js deleted file mode 100644 index b3e84c09cad2..000000000000 --- a/packages/jest-jasmine2/src/jasmine/spyRegistry.js +++ /dev/null @@ -1,217 +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. -*/ -/* @flow */ - -import CallTracker from './CallTracker'; - -import createSpy from './createSpy'; -import SpyStrategy from './SpyStrategy'; - -const formatErrorMsg = (domain: string, usage?: string) => { - const usageDefinition = usage ? '\nUsage: ' + usage : ''; - return msg => domain + ' : ' + msg + usageDefinition; -}; - -function isSpy(putativeSpy) { - if (!putativeSpy) { - return false; - } - return ( - putativeSpy.and instanceof SpyStrategy && - putativeSpy.calls instanceof CallTracker - ); -} - -const getErrorMsg = formatErrorMsg('', 'spyOn(, )'); - -export default function SpyRegistry(options: Object) { - options = options || {}; - const currentSpies = - options.currentSpies || - function() { - return []; - }; - - this.allowRespy = function(allow) { - this.respy = allow; - }; - - this.spyOn = function(obj, methodName, accessType?: string) { - if (accessType) { - return this._spyOnProperty(obj, methodName, accessType); - } - - if (obj === void 0) { - throw new Error( - getErrorMsg( - 'could not find an object to spy upon for ' + methodName + '()', - ), - ); - } - - if (methodName === void 0) { - throw new Error(getErrorMsg('No method name supplied')); - } - - if (obj[methodName] === void 0) { - throw new Error(getErrorMsg(methodName + '() method does not exist')); - } - - if (obj[methodName] && isSpy(obj[methodName])) { - if (this.respy) { - return obj[methodName]; - } else { - throw new Error( - getErrorMsg(methodName + ' has already been spied upon'), - ); - } - } - - let descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, methodName); - } catch (e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } - - if (descriptor && !(descriptor.writable || descriptor.set)) { - throw new Error( - getErrorMsg(methodName + ' is not declared writable or has no setter'), - ); - } - - const originalMethod = obj[methodName]; - const spiedMethod = createSpy(methodName, originalMethod); - let restoreStrategy; - - if (Object.prototype.hasOwnProperty.call(obj, methodName)) { - restoreStrategy = function() { - obj[methodName] = originalMethod; - }; - } else { - restoreStrategy = function() { - if (!delete obj[methodName]) { - obj[methodName] = originalMethod; - } - }; - } - - currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy, - }); - - obj[methodName] = spiedMethod; - - return spiedMethod; - }; - - this._spyOnProperty = function(obj, propertyName, accessType = 'get') { - if (!obj) { - throw new Error( - getErrorMsg('could not find an object to spy upon for ' + propertyName), - ); - } - - if (!propertyName) { - throw new Error(getErrorMsg('No property name supplied')); - } - - let descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); - } catch (e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } - - if (!descriptor) { - throw new Error(getErrorMsg(propertyName + ' property does not exist')); - } - - if (!descriptor.configurable) { - throw new Error( - getErrorMsg(propertyName + ' is not declared configurable'), - ); - } - - if (!descriptor[accessType]) { - throw new Error( - getErrorMsg( - 'Property ' + - propertyName + - ' does not have access type ' + - accessType, - ), - ); - } - - if (obj[propertyName] && isSpy(obj[propertyName])) { - if (this.respy) { - return obj[propertyName]; - } else { - throw new Error( - getErrorMsg(propertyName + ' has already been spied upon'), - ); - } - } - - const originalDescriptor = descriptor; - const spiedProperty = createSpy(propertyName, descriptor[accessType]); - let restoreStrategy; - - if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { - restoreStrategy = function() { - Object.defineProperty(obj, propertyName, originalDescriptor); - }; - } else { - restoreStrategy = function() { - delete obj[propertyName]; - }; - } - - currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy, - }); - - const spiedDescriptor = {...descriptor, [accessType]: spiedProperty}; - - Object.defineProperty(obj, propertyName, spiedDescriptor); - - return spiedProperty; - }; - - this.clearSpies = function() { - const spies = currentSpies(); - for (let i = spies.length - 1; i >= 0; i--) { - const spyEntry = spies[i]; - spyEntry.restoreObjectToOriginalState(); - } - }; -} diff --git a/packages/jest-jasmine2/src/jasmine/spyRegistry.ts b/packages/jest-jasmine2/src/jasmine/spyRegistry.ts new file mode 100644 index 000000000000..6e05820e8206 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/spyRegistry.ts @@ -0,0 +1,234 @@ +/** + * 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. +*/ + +import {Spy} from '../types'; +import CallTracker from './CallTracker'; +import createSpy from './createSpy'; +import SpyStrategy from './SpyStrategy'; + +const formatErrorMsg = (domain: string, usage?: string) => { + const usageDefinition = usage ? '\nUsage: ' + usage : ''; + return (msg: string) => domain + ' : ' + msg + usageDefinition; +}; + +function isSpy(putativeSpy: any) { + if (!putativeSpy) { + return false; + } + return ( + putativeSpy.and instanceof SpyStrategy && + putativeSpy.calls instanceof CallTracker + ); +} + +const getErrorMsg = formatErrorMsg('', 'spyOn(, )'); + +export default class SpyRegistry { + allowRespy: (allow: unknown) => void; + spyOn: ( + obj: {[key: string]: any}, + methodName: string, + accessType?: keyof PropertyDescriptor, + ) => Spy; + clearSpies: () => void; + respy: unknown; + + private _spyOnProperty: ( + obj: {[key: string]: any}, + propertyName: string, + accessType: keyof PropertyDescriptor, + ) => Spy; + + constructor({ + currentSpies = () => [], + }: { + currentSpies?: () => Array; + } = {}) { + this.allowRespy = function(allow) { + this.respy = allow; + }; + + this.spyOn = (obj, methodName, accessType) => { + if (accessType) { + return this._spyOnProperty(obj, methodName, accessType); + } + + if (obj === void 0) { + throw new Error( + getErrorMsg( + 'could not find an object to spy upon for ' + methodName + '()', + ), + ); + } + + if (methodName === void 0) { + throw new Error(getErrorMsg('No method name supplied')); + } + + if (obj[methodName] === void 0) { + throw new Error(getErrorMsg(methodName + '() method does not exist')); + } + + if (obj[methodName] && isSpy(obj[methodName])) { + if (this.respy) { + return obj[methodName]; + } else { + throw new Error( + getErrorMsg(methodName + ' has already been spied upon'), + ); + } + } + + let descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, methodName); + } catch (e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (descriptor && !(descriptor.writable || descriptor.set)) { + throw new Error( + getErrorMsg( + methodName + ' is not declared writable or has no setter', + ), + ); + } + + const originalMethod = obj[methodName]; + const spiedMethod = createSpy(methodName, originalMethod); + let restoreStrategy; + + if (Object.prototype.hasOwnProperty.call(obj, methodName)) { + restoreStrategy = function() { + obj[methodName] = originalMethod; + }; + } else { + restoreStrategy = function() { + if (!delete obj[methodName]) { + obj[methodName] = originalMethod; + } + }; + } + + currentSpies().push({ + restoreObjectToOriginalState: restoreStrategy, + } as Spy); + + obj[methodName] = spiedMethod; + + return spiedMethod; + }; + + this._spyOnProperty = function(obj, propertyName, accessType = 'get') { + if (!obj) { + throw new Error( + getErrorMsg( + 'could not find an object to spy upon for ' + propertyName, + ), + ); + } + + if (!propertyName) { + throw new Error(getErrorMsg('No property name supplied')); + } + + let descriptor: PropertyDescriptor | undefined; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + } catch (e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (!descriptor) { + throw new Error(getErrorMsg(propertyName + ' property does not exist')); + } + + if (!descriptor.configurable) { + throw new Error( + getErrorMsg(propertyName + ' is not declared configurable'), + ); + } + + if (!descriptor[accessType]) { + throw new Error( + getErrorMsg( + 'Property ' + + propertyName + + ' does not have access type ' + + accessType, + ), + ); + } + + if (obj[propertyName] && isSpy(obj[propertyName])) { + if (this.respy) { + return obj[propertyName]; + } else { + throw new Error( + getErrorMsg(propertyName + ' has already been spied upon'), + ); + } + } + + const originalDescriptor = descriptor; + const spiedProperty = createSpy(propertyName, descriptor[accessType]); + let restoreStrategy; + + if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { + restoreStrategy = function() { + Object.defineProperty(obj, propertyName, originalDescriptor); + }; + } else { + restoreStrategy = function() { + delete obj[propertyName]; + }; + } + + currentSpies().push({ + restoreObjectToOriginalState: restoreStrategy, + } as Spy); + + const spiedDescriptor = {...descriptor, [accessType]: spiedProperty}; + + Object.defineProperty(obj, propertyName, spiedDescriptor); + + return spiedProperty; + }; + + this.clearSpies = function() { + const spies = currentSpies(); + for (let i = spies.length - 1; i >= 0; i--) { + const spyEntry = spies[i]; + spyEntry.restoreObjectToOriginalState!(); + } + }; + } +} diff --git a/packages/jest-jasmine2/src/jasmineAsyncInstall.js b/packages/jest-jasmine2/src/jasmineAsyncInstall.ts similarity index 71% rename from packages/jest-jasmine2/src/jasmineAsyncInstall.js rename to packages/jest-jasmine2/src/jasmineAsyncInstall.ts index 1d296e53d232..cbcb31576f64 100644 --- a/packages/jest-jasmine2/src/jasmineAsyncInstall.js +++ b/packages/jest-jasmine2/src/jasmineAsyncInstall.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ /** @@ -12,25 +10,36 @@ * returning a promise from `it/test` and `before/afterEach/All` blocks. */ -import type {Global} from 'types/Global'; -import type {GlobalConfig} from 'types/Config'; - +import {Global, Config} from '@jest/types'; import co from 'co'; import isGeneratorFn from 'is-generator-fn'; import throat from 'throat'; import isError from './isError'; +import {Jasmine} from './types'; +import Spec from './jasmine/Spec'; -function isPromise(obj) { +interface DoneFn { + (): void; + fail: (error: Error) => void; +} + +function isPromise(obj: any) { return obj && typeof obj.then === 'function'; } -function promisifyLifeCycleFunction(originalFn, env) { - return function(fn, timeout) { +function promisifyLifeCycleFunction( + originalFn: Function, + env: Jasmine['currentEnv_'], +) { + return function( + fn: Function | (() => Promise) | GeneratorFunction | undefined, + timeout?: number, + ) { if (!fn) { return originalFn.call(env); } - const hasDoneCallback = fn.length > 0; + const hasDoneCallback = typeof fn === 'function' && fn.length > 0; if (hasDoneCallback) { // Jasmine will handle it @@ -47,12 +56,12 @@ function promisifyLifeCycleFunction(originalFn, env) { // We make *all* functions async and run `done` right away if they // didn't return a promise. - const asyncJestLifecycle = function(done) { + const asyncJestLifecycle = function(done: DoneFn) { const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn; - const returnValue = wrappedFn.call({}); + const returnValue = wrappedFn.call({}) as Promise; if (isPromise(returnValue)) { - returnValue.then(done.bind(null, null), error => { + returnValue.then(done.bind(null, null), (error: Error) => { const {isError: checkIsError, message} = isError(error); if (message) { @@ -71,8 +80,12 @@ function promisifyLifeCycleFunction(originalFn, env) { // Similar to promisifyLifeCycleFunction but throws an error // when the return value is neither a Promise nor `undefined` -function promisifyIt(originalFn, env, jasmine) { - return function(specName, fn, timeout) { +function promisifyIt( + originalFn: Function, + env: Jasmine['currentEnv_'], + jasmine: Jasmine, +) { + return function(specName: string, fn: Function, timeout?: number) { if (!fn) { const spec = originalFn.call(env, specName); spec.pend('not implemented'); @@ -93,12 +106,12 @@ function promisifyIt(originalFn, env, jasmine) { // https://crbug.com/v8/7142 extraError.stack = extraError.stack; - const asyncJestTest = function(done) { + const asyncJestTest = function(done: DoneFn) { const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn; const returnValue = wrappedFn.call({}); if (isPromise(returnValue)) { - returnValue.then(done.bind(null, null), error => { + returnValue.then(done.bind(null, null), (error: Error) => { const {isError: checkIsError, message} = isError(error); if (message) { @@ -106,7 +119,7 @@ function promisifyIt(originalFn, env, jasmine) { } if (jasmine.Spec.isPendingSpecException(error)) { - env.pending(message); + env.pending(message!); done(); } else { done.fail(checkIsError ? error : extraError); @@ -127,13 +140,20 @@ function promisifyIt(originalFn, env, jasmine) { }; } -function makeConcurrent(originalFn: Function, env, mutex) { +function makeConcurrent( + originalFn: Function, + env: Jasmine['currentEnv_'], + mutex: ReturnType, +): Global.ItConcurrentBase { return function(specName, fn, timeout) { - if (env != null && !env.specFilter({getFullName: () => specName || ''})) { + if ( + env != null && + !env.specFilter({getFullName: () => specName || ''} as Spec) + ) { return originalFn.call(env, specName, () => Promise.resolve(), timeout); } - let promise; + let promise: Promise; try { promise = mutex(() => { const promise = fn(); @@ -153,18 +173,25 @@ function makeConcurrent(originalFn: Function, env, mutex) { } export default function jasmineAsyncInstall( - globalConfig: GlobalConfig, - global: Global, + globalConfig: Config.GlobalConfig, + global: Global.Global, ) { - const jasmine = global.jasmine; + const jasmine = global.jasmine as Jasmine; const mutex = throat(globalConfig.maxConcurrency); const env = jasmine.getEnv(); env.it = promisifyIt(env.it, env, jasmine); env.fit = promisifyIt(env.fit, env, jasmine); - global.it.concurrent = makeConcurrent(env.it, env, mutex); - global.it.concurrent.only = makeConcurrent(env.fit, env, mutex); - global.it.concurrent.skip = makeConcurrent(env.xit, env, mutex); + global.it.concurrent = (env => { + const concurrent = makeConcurrent( + env.it, + env, + mutex, + ) as Global.ItConcurrentExtended; + concurrent.only = makeConcurrent(env.fit, env, mutex); + concurrent.skip = makeConcurrent(env.xit, env, mutex); + return concurrent; + })(env); global.fit.concurrent = makeConcurrent(env.fit, env, mutex); env.afterAll = promisifyLifeCycleFunction(env.afterAll, env); env.afterEach = promisifyLifeCycleFunction(env.afterEach, env); diff --git a/packages/jest-jasmine2/src/jestExpect.js b/packages/jest-jasmine2/src/jestExpect.ts similarity index 70% rename from packages/jest-jasmine2/src/jestExpect.js rename to packages/jest-jasmine2/src/jestExpect.ts index f6f95bc19790..9453f1731638 100644 --- a/packages/jest-jasmine2/src/jestExpect.js +++ b/packages/jest-jasmine2/src/jestExpect.ts @@ -3,13 +3,9 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {RawMatcherFn} from 'types/Matchers'; - -import expect from 'expect'; +import expect, {MatcherState} from 'expect'; import { addSerializer, toMatchSnapshot, @@ -17,12 +13,14 @@ import { toThrowErrorMatchingSnapshot, toThrowErrorMatchingInlineSnapshot, } from 'jest-snapshot'; +import {RawMatcherFn, Jasmine} from './types'; type JasmineMatcher = { - (matchersUtil: any, context: any): JasmineMatcher, - compare: () => RawMatcherFn, - negativeCompare: () => RawMatcherFn, + (matchersUtil: any, context: any): JasmineMatcher; + compare: () => RawMatcherFn; + negativeCompare: () => RawMatcherFn; }; + type JasmineMatchersObject = {[id: string]: JasmineMatcher}; export default (config: {expand: boolean}) => { @@ -34,9 +32,9 @@ export default (config: {expand: boolean}) => { toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, }); - (expect: Object).addSnapshotSerializer = addSerializer; + expect.addSnapshotSerializer = addSerializer; - const jasmine = global.jasmine; + const jasmine = global.jasmine as Jasmine; jasmine.anything = expect.anything; jasmine.any = expect.any; jasmine.objectContaining = expect.objectContaining; @@ -46,15 +44,26 @@ export default (config: {expand: boolean}) => { jasmine.addMatchers = (jasmineMatchersObject: JasmineMatchersObject) => { const jestMatchersObject = Object.create(null); Object.keys(jasmineMatchersObject).forEach(name => { - jestMatchersObject[name] = function(): RawMatcherFn { + jestMatchersObject[name] = function( + this: MatcherState, + ...args: Array + ): RawMatcherFn { // use "expect.extend" if you need to use equality testers (via this.equal) const result = jasmineMatchersObject[name](null, null); // if there is no 'negativeCompare', both should be handled by `compare` const negativeCompare = result.negativeCompare || result.compare; return this.isNot - ? negativeCompare.apply(null, arguments) - : result.compare.apply(null, arguments); + ? negativeCompare.apply( + null, + // @ts-ignore + args, + ) + : result.compare.apply( + null, + // @ts-ignore + args, + ); }; }); diff --git a/packages/jest-jasmine2/src/pTimeout.js b/packages/jest-jasmine2/src/pTimeout.ts similarity index 86% rename from packages/jest-jasmine2/src/pTimeout.js rename to packages/jest-jasmine2/src/pTimeout.ts index 6df9b31538d2..dfe96c134e8c 100644 --- a/packages/jest-jasmine2/src/pTimeout.js +++ b/packages/jest-jasmine2/src/pTimeout.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ // A specialized version of `p-timeout` that does not touch globals. @@ -12,8 +10,8 @@ export default function pTimeout( promise: Promise, ms: number, - clearTimeout: (timeoutID: number) => void, - setTimeout: (func: () => void, delay: number) => number, + clearTimeout: NodeJS.Global['clearTimeout'], + setTimeout: NodeJS.Global['setTimeout'], onTimeout: () => any, ): Promise { return new Promise((resolve, reject) => { diff --git a/packages/jest-jasmine2/src/queueRunner.js b/packages/jest-jasmine2/src/queueRunner.ts similarity index 69% rename from packages/jest-jasmine2/src/queueRunner.js rename to packages/jest-jasmine2/src/queueRunner.ts index e55108b66b18..f10f484c79a7 100644 --- a/packages/jest-jasmine2/src/queueRunner.js +++ b/packages/jest-jasmine2/src/queueRunner.ts @@ -3,44 +3,46 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ +// @ts-ignore ignore vendor file import PCancelable from './PCancelable'; import pTimeout from './pTimeout'; -type Options = { - clearTimeout: (timeoutID: number) => void, - fail: () => void, - onException: (error: Error) => void, - queueableFns: Array, - setTimeout: (func: () => void, delay: number) => number, - userContext: any, +type Global = NodeJS.Global; + +export type Options = { + clearTimeout: Global['clearTimeout']; + fail: (error: Error) => void; + onException: (error: Error) => void; + queueableFns: Array; + setTimeout: Global['setTimeout']; + userContext: any; }; -type QueueableFn = { - fn: (next: () => void) => void, - timeout?: () => number, - initError?: Error, +export type QueueableFn = { + fn: (done: (error?: any) => void) => void; + timeout?: () => number; + initError?: Error; }; export default function queueRunner(options: Options) { - const token = new PCancelable((onCancel, resolve) => { + const token = new PCancelable((onCancel: Function, resolve: Function) => { onCancel(resolve); }); const mapper = ({fn, timeout, initError = new Error()}: QueueableFn) => { let promise = new Promise(resolve => { - const next = function(err) { + const next = function(...args: [Error]) { + const err = args[0]; if (err) { - options.fail.apply(null, arguments); + options.fail.apply(null, args); } resolve(); }; - next.fail = function() { - options.fail.apply(null, arguments); + next.fail = function(...args: [Error]) { + options.fail.apply(null, args); resolve(); }; try { diff --git a/packages/jest-jasmine2/src/reporter.js b/packages/jest-jasmine2/src/reporter.ts similarity index 74% rename from packages/jest-jasmine2/src/reporter.js rename to packages/jest-jasmine2/src/reporter.ts index 88557489cff2..c6f8c6743854 100644 --- a/packages/jest-jasmine2/src/reporter.js +++ b/packages/jest-jasmine2/src/reporter.ts @@ -3,51 +3,30 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig, Path, ProjectConfig} from 'types/Config'; -import type { - AssertionResult, - FailedAssertion, - Milliseconds, - Status, - TestResult, -} from 'types/TestResult'; - +import {Config, TestResult} from '@jest/types'; import {formatResultsErrors} from 'jest-message-util'; - -type Suite = { - description: string, -}; - -type SpecResult = { - __callsite?: Object, - description: string, - duration?: Milliseconds, - failedExpectations: Array, - fullName: string, - id: string, - status: Status, -}; +import {SpecResult} from './jasmine/Spec'; +import {SuiteResult} from './jasmine/Suite'; +import {Reporter, RunDetails} from './types'; type Microseconds = number; -export default class Jasmine2Reporter { - _testResults: Array; - _globalConfig: GlobalConfig; - _config: ProjectConfig; - _currentSuites: Array; - _resolve: any; - _resultsPromise: Promise; - _startTimes: Map; - _testPath: Path; +export default class Jasmine2Reporter implements Reporter { + private _testResults: Array; + private _globalConfig: Config.GlobalConfig; + private _config: Config.ProjectConfig; + private _currentSuites: Array; + private _resolve: any; + private _resultsPromise: Promise; + private _startTimes: Map; + private _testPath: Config.Path; constructor( - globalConfig: GlobalConfig, - config: ProjectConfig, - testPath: Path, + globalConfig: Config.GlobalConfig, + config: Config.ProjectConfig, + testPath: Config.Path, ) { this._globalConfig = globalConfig; this._config = config; @@ -59,7 +38,9 @@ export default class Jasmine2Reporter { this._startTimes = new Map(); } - specStarted(spec: {id: string}) { + jasmineStarted(_runDetails: RunDetails) {} + + specStarted(spec: SpecResult) { this._startTimes.set(spec.id, Date.now()); } @@ -69,15 +50,15 @@ export default class Jasmine2Reporter { ); } - suiteStarted(suite: Suite): void { + suiteStarted(suite: SuiteResult): void { this._currentSuites.push(suite.description); } - suiteDone(): void { + suiteDone(_result: SuiteResult): void { this._currentSuites.pop(); } - jasmineDone(): void { + jasmineDone(_runDetails: RunDetails): void { let numFailingTests = 0; let numPassingTests = 0; let numPendingTests = 0; @@ -126,11 +107,11 @@ export default class Jasmine2Reporter { this._resolve(testResult); } - getResults(): Promise { + getResults(): Promise { return this._resultsPromise; } - _addMissingMessageToStack(stack: string, message: ?string) { + private _addMissingMessageToStack(stack: string, message?: string) { // Some errors (e.g. Angular injection error) don't prepend error.message // to stack, instead the first line of the stack is just plain 'Error' const ERROR_REGEX = /^Error\s*\n/; @@ -145,10 +126,10 @@ export default class Jasmine2Reporter { return stack; } - _extractSpecResults( + private _extractSpecResults( specResult: SpecResult, ancestorTitles: Array, - ): AssertionResult { + ): TestResult.AssertionResult { const start = this._startTimes.get(specResult.id); const duration = start ? Date.now() - start : undefined; const status = @@ -156,11 +137,10 @@ export default class Jasmine2Reporter { const location = specResult.__callsite ? { column: specResult.__callsite.getColumnNumber(), - // $FlowFixMe: https://github.com/facebook/flow/issues/5213 line: specResult.__callsite.getLineNumber(), } : null; - const results = { + const results: TestResult.AssertionResult = { ancestorTitles, duration, failureMessages: [], diff --git a/packages/jest-jasmine2/src/setup_jest_globals.js b/packages/jest-jasmine2/src/setup_jest_globals.ts similarity index 67% rename from packages/jest-jasmine2/src/setup_jest_globals.js rename to packages/jest-jasmine2/src/setup_jest_globals.ts index c6a3c52f2612..cff86b27cb45 100644 --- a/packages/jest-jasmine2/src/setup_jest_globals.js +++ b/packages/jest-jasmine2/src/setup_jest_globals.ts @@ -3,31 +3,30 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {GlobalConfig, Path, ProjectConfig} from 'types/Config'; -import type {Plugin} from 'types/PrettyFormat'; - +import {Config} from '@jest/types'; +import {Plugin} from 'pretty-format'; import {extractExpectedAssertionsErrors, getState, setState} from 'expect'; import { buildSnapshotResolver, SnapshotState, addSerializer, } from 'jest-snapshot'; +import JasmineSpec, {Attributes, SpecResult} from './jasmine/Spec'; +import {Jasmine} from './types'; -export type SetupOptions = {| - config: ProjectConfig, - globalConfig: GlobalConfig, - localRequire: (moduleName: string) => Plugin, - testPath: Path, -|}; +export type SetupOptions = { + config: Config.ProjectConfig; + globalConfig: Config.GlobalConfig; + localRequire: (moduleName: string) => Plugin; + testPath: Config.Path; +}; // Get suppressed errors form jest-matchers that weren't throw during // test execution and add them to the test result, potentially failing // a passing test. -const addSuppressedErrors = result => { +const addSuppressedErrors = (result: SpecResult) => { const {suppressedErrors} = getState(); setState({suppressedErrors: []}); if (suppressedErrors.length) { @@ -38,6 +37,7 @@ const addSuppressedErrors = result => { // passing error for custom test reporters error, expected: '', + matcherName: '', message: error.message, passed: false, stack: error.stack, @@ -45,7 +45,7 @@ const addSuppressedErrors = result => { } }; -const addAssertionErrors = result => { +const addAssertionErrors = (result: SpecResult) => { const assertionErrors = extractExpectedAssertionsErrors(); if (assertionErrors.length) { const jasmineErrors = assertionErrors.map(({actual, error, expected}) => ({ @@ -60,30 +60,26 @@ const addAssertionErrors = result => { }; const patchJasmine = () => { - global.jasmine.Spec = (realSpec => { - const Spec = function Spec(attr) { - const resultCallback = attr.resultCallback; - attr.resultCallback = function(result) { - addSuppressedErrors(result); - addAssertionErrors(result); - resultCallback.call(attr, result); - }; - const onStart = attr.onStart; - attr.onStart = context => { - setState({currentTestName: context.getFullName()}); - onStart && onStart.call(attr, context); - }; - realSpec.call(this, attr); - }; - - Spec.prototype = realSpec.prototype; - for (const statics in realSpec) { - if (Object.prototype.hasOwnProperty.call(realSpec, statics)) { - Spec[statics] = realSpec[statics]; + (global.jasmine as Jasmine).Spec = (realSpec => { + class Spec extends realSpec { + constructor(attr: Attributes) { + const resultCallback = attr.resultCallback; + attr.resultCallback = function(result: SpecResult) { + addSuppressedErrors(result); + addAssertionErrors(result); + resultCallback.call(attr, result); + }; + const onStart = attr.onStart; + attr.onStart = (context: JasmineSpec) => { + setState({currentTestName: context.getFullName()}); + onStart && onStart.call(attr, context); + }; + super(attr); } } + return Spec; - })(global.jasmine.Spec); + })((global.jasmine as Jasmine).Spec); }; export default ({ diff --git a/packages/jest-jasmine2/src/treeProcessor.js b/packages/jest-jasmine2/src/treeProcessor.ts similarity index 72% rename from packages/jest-jasmine2/src/treeProcessor.js rename to packages/jest-jasmine2/src/treeProcessor.ts index 27b381fa1d0c..db59f94b0b11 100644 --- a/packages/jest-jasmine2/src/treeProcessor.js +++ b/packages/jest-jasmine2/src/treeProcessor.ts @@ -3,28 +3,27 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ +import Suite from './jasmine/Suite'; type Options = { - nodeComplete: (suite: TreeNode) => void, - nodeStart: (suite: TreeNode) => void, - queueRunnerFactory: any, - runnableIds: Array, - tree: TreeNode, + nodeComplete: (suite: TreeNode) => void; + nodeStart: (suite: TreeNode) => void; + queueRunnerFactory: any; + runnableIds: Array; + tree: TreeNode; }; -type TreeNode = { - afterAllFns: Array, - beforeAllFns: Array, - disabled?: boolean, - execute: (onComplete: () => void, enabled: boolean) => void, - id: string, - onException: (error: Error) => void, - sharedUserContext: () => any, - children?: Array, -}; +export type TreeNode = { + afterAllFns: Array; + beforeAllFns: Array; + disabled?: boolean; + execute: (onComplete: () => void, enabled: boolean) => void; + id: string; + onException: (error: Error) => void; + sharedUserContext: () => any; + children?: Array; +} & Pick; export default function treeProcessor(options: Options) { const { @@ -35,7 +34,7 @@ export default function treeProcessor(options: Options) { tree, } = options; - function isEnabled(node, parentEnabled) { + function isEnabled(node: TreeNode, parentEnabled: boolean) { return parentEnabled || runnableIds.indexOf(node.id) !== -1; } @@ -56,7 +55,7 @@ export default function treeProcessor(options: Options) { return async function fn(done: (error?: any) => void = () => {}) { nodeStart(node); await queueRunnerFactory({ - onException: error => node.onException(error), + onException: (error: Error) => node.onException(error), queueableFns: wrapChildren(node, enabled), userContext: node.sharedUserContext(), }); @@ -65,7 +64,7 @@ export default function treeProcessor(options: Options) { }; } - function hasEnabledTest(node: TreeNode) { + function hasEnabledTest(node: TreeNode): boolean { if (node.children) { return node.children.some(hasEnabledTest); } diff --git a/packages/jest-jasmine2/src/types.ts b/packages/jest-jasmine2/src/types.ts new file mode 100644 index 000000000000..f4c6be31a852 --- /dev/null +++ b/packages/jest-jasmine2/src/types.ts @@ -0,0 +1,93 @@ +/** + * 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 {AssertionError} from 'assert'; +import {Config} from '@jest/types'; + +import expect from 'expect'; +import Spec, {SpecResult} from './jasmine/Spec'; +import JsApiReporter from './jasmine/JsApiReporter'; +import Timer from './jasmine/Timer'; +import Env from './jasmine/Env'; +import createSpy from './jasmine/createSpy'; +import ReportDispatcher from './jasmine/ReportDispatcher'; +import SpyRegistry from './jasmine/spyRegistry'; +import Suite, {SuiteResult} from './jasmine/Suite'; +import SpyStrategy from './jasmine/SpyStrategy'; +import CallTracker from './jasmine/CallTracker'; + +export interface AssertionErrorWithStack extends AssertionError { + stack: string; +} + +// TODO Add expect types to @jest/types or leave it here +// Borrowed from "expect" +// -------START------- +export type SyncExpectationResult = { + pass: boolean; + message: () => string; +}; + +export type AsyncExpectationResult = Promise; + +export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; + +export type RawMatcherFn = ( + expected: any, + actual: any, + options?: any, +) => ExpectationResult; +// -------END------- + +export type RunDetails = { + totalSpecsDefined?: number; + failedExpectations?: SuiteResult['failedExpectations']; +}; + +export type Reporter = { + jasmineDone: (runDetails: RunDetails) => void; + jasmineStarted: (runDetails: RunDetails) => void; + specDone: (result: SpecResult) => void; + specStarted: (spec: SpecResult) => void; + suiteDone: (result: SuiteResult) => void; + suiteStarted: (result: SuiteResult) => void; +}; + +export interface Spy extends Record { + (this: {[key: string]: any}, ...args: Array): unknown; + and: SpyStrategy; + calls: CallTracker; + restoreObjectToOriginalState?: () => void; + [key: string]: any; +} + +export type Jasmine = { + _DEFAULT_TIMEOUT_INTERVAL: number; + DEFAULT_TIMEOUT_INTERVAL: number; + currentEnv_: ReturnType['prototype']; + getEnv: (options?: object) => ReturnType['prototype']; + createSpy: typeof createSpy; + Env: ReturnType; + JsApiReporter: typeof JsApiReporter; + ReportDispatcher: typeof ReportDispatcher; + Spec: typeof Spec; + SpyRegistry: typeof SpyRegistry; + Suite: typeof Suite; + Timer: typeof Timer; + version: string; + testPath: Config.Path; + addMatchers: Function; +} & typeof expect & + NodeJS.Global; + +declare global { + module NodeJS { + interface Global { + expect: typeof expect; + } + } +} diff --git a/packages/jest-jasmine2/tsconfig.json b/packages/jest-jasmine2/tsconfig.json new file mode 100644 index 000000000000..4844e44aa790 --- /dev/null +++ b/packages/jest-jasmine2/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "references": [ + {"path": "../expect"}, + {"path": "../jest-each"}, + {"path": "../jest-environment"}, + {"path": "../jest-matcher-utils"}, + {"path": "../jest-message-util"}, + {"path": "../jest-runtime"}, + {"path": "../jest-snapshot"}, + {"path": "../jest-types"}, + {"path": "../jest-util"}, + {"path": "../pretty-format"} + ] +} diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 0f6728f4ff60..dcb35ebd6daf 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -314,6 +314,7 @@ const JestSnapshot = { /* eslint-disable-next-line no-redeclare */ namespace JestSnapshot { export type SnapshotResolver = JestSnapshotResolver; + export type SnapshotStateType = SnapshotState; } export = JestSnapshot; diff --git a/packages/jest-types/src/Global.ts b/packages/jest-types/src/Global.ts index cbd99274247a..d237eaa3ef5d 100644 --- a/packages/jest-types/src/Global.ts +++ b/packages/jest-types/src/Global.ts @@ -13,9 +13,6 @@ export type TestFn = (done?: DoneFn) => Promise | void | undefined; export type BlockFn = () => void; export type BlockName = string; -// TODO: Get rid of this at some point -type JasmineType = {_DEFAULT_TIMEOUT_INTERVAL?: number; addMatchers: Function}; - export type Col = unknown; export type Row = Array; export type Table = Array; @@ -27,6 +24,9 @@ export type EachTestFn = ( ...args: Array ) => Promise | void | undefined; +// TODO: Get rid of this at some point +type Jasmine = {_DEFAULT_TIMEOUT_INTERVAL?: number; addMatchers: Function}; + type Each = ( table: EachTable, ...taggedTemplateData: Array @@ -62,22 +62,26 @@ export interface DescribeBase { } export interface Describe extends DescribeBase { - only: ItBase; - skip: ItBase; + only: DescribeBase; + skip: DescribeBase; } // TODO: Maybe add `| Window` in the future? export interface Global extends NodeJS.Global { - it: It; + it: ItConcurrent; test: ItConcurrent; - fit: ItBase; + fit: ItBase & {concurrent?: ItConcurrentBase}; xit: ItBase; xtest: ItBase; describe: Describe; xdescribe: DescribeBase; fdescribe: DescribeBase; __coverage__: CoverageMapData; - jasmine: JasmineType; + jasmine: Jasmine; + fail: () => void; + pending: () => void; + spyOn: () => void; + spyOnProperty: () => void; } declare global { @@ -91,6 +95,7 @@ declare global { describe: Describe; xdescribe: DescribeBase; fdescribe: DescribeBase; + jasmine: Jasmine; } } } diff --git a/packages/jest-types/src/TestResult.ts b/packages/jest-types/src/TestResult.ts index 4c9940e4f705..ba001ca2becf 100644 --- a/packages/jest-types/src/TestResult.ts +++ b/packages/jest-types/src/TestResult.ts @@ -16,13 +16,15 @@ export type SerializableError = { }; export type FailedAssertion = { - matcherName: string; + matcherName?: string; message?: string; actual?: any; pass?: boolean; + passed?: boolean; expected?: any; isNot?: boolean; stack?: string; + error?: any; }; export type AssertionLocation = { diff --git a/packages/jest-util/src/convertDescriptorToString.ts b/packages/jest-util/src/convertDescriptorToString.ts index fb36658fde85..50324d218204 100644 --- a/packages/jest-util/src/convertDescriptorToString.ts +++ b/packages/jest-util/src/convertDescriptorToString.ts @@ -6,9 +6,9 @@ */ // See: https://github.com/facebook/jest/pull/5154 -export default function convertDescriptorToString( - descriptor: string | Function, -): string { +export default function convertDescriptorToString< + T extends number | string | Function | undefined +>(descriptor: T): T | string { if ( typeof descriptor === 'string' || typeof descriptor === 'number' ||