From 84b3457b965d822491e1895d74077624861a0a50 Mon Sep 17 00:00:00 2001 From: wtgtybhertgeghgtwtg Date: Thu, 16 Mar 2017 18:51:59 -0700 Subject: [PATCH] [jest-jasmine2] Split `jasmine-light` over multiple files. (#3159) * [jest-jasmine2] Split `jasmine-light` over multiple files. * Don't wrap things that don't need to be wrapped. * Don't wrap things that don't need to be wrapped (again). * Lint. * Move source to `jasmine`, alter license. --- packages/jest-jasmine2/src/index.js | 2 +- packages/jest-jasmine2/src/jasmine-light.js | 1841 ----------------- .../jest-jasmine2/src/jasmine/CallTracker.js | 82 + packages/jest-jasmine2/src/jasmine/Env.js | 537 +++++ .../src/jasmine/ExceptionFormatter.js | 62 + .../src/jasmine/ExpectationFailed.js | 24 + .../src/jasmine/JsApiReporter.js | 113 + .../jest-jasmine2/src/jasmine/QueueRunner.js | 159 ++ .../src/jasmine/ReportDispatcher.js | 78 + packages/jest-jasmine2/src/jasmine/Spec.js | 207 ++ .../jest-jasmine2/src/jasmine/SpyRegistry.js | 142 ++ .../jest-jasmine2/src/jasmine/SpyStrategy.js | 98 + packages/jest-jasmine2/src/jasmine/Suite.js | 195 ++ packages/jest-jasmine2/src/jasmine/Timer.js | 57 + .../src/jasmine/TreeProcessor.js | 264 +++ .../src/jasmine/buildExpectationResult.js | 84 + .../src/jasmine/jasmine-light.js | 210 ++ 17 files changed, 2313 insertions(+), 1842 deletions(-) delete mode 100644 packages/jest-jasmine2/src/jasmine-light.js create mode 100644 packages/jest-jasmine2/src/jasmine/CallTracker.js create mode 100644 packages/jest-jasmine2/src/jasmine/Env.js create mode 100644 packages/jest-jasmine2/src/jasmine/ExceptionFormatter.js create mode 100644 packages/jest-jasmine2/src/jasmine/ExpectationFailed.js create mode 100644 packages/jest-jasmine2/src/jasmine/JsApiReporter.js create mode 100644 packages/jest-jasmine2/src/jasmine/QueueRunner.js create mode 100644 packages/jest-jasmine2/src/jasmine/ReportDispatcher.js create mode 100644 packages/jest-jasmine2/src/jasmine/Spec.js create mode 100644 packages/jest-jasmine2/src/jasmine/SpyRegistry.js create mode 100644 packages/jest-jasmine2/src/jasmine/SpyStrategy.js create mode 100644 packages/jest-jasmine2/src/jasmine/Suite.js create mode 100644 packages/jest-jasmine2/src/jasmine/Timer.js create mode 100644 packages/jest-jasmine2/src/jasmine/TreeProcessor.js create mode 100644 packages/jest-jasmine2/src/jasmine/buildExpectationResult.js create mode 100644 packages/jest-jasmine2/src/jasmine/jasmine-light.js diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index 9b8146ddd207..4a345a5649aa 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -19,7 +19,7 @@ const JasmineReporter = require('./reporter'); const jasmineAsync = require('./jasmine-async'); const path = require('path'); -const JASMINE = require.resolve('./jasmine-light.js'); +const JASMINE = require.resolve('./jasmine/jasmine-light.js'); function jasmine2( config: Config, diff --git a/packages/jest-jasmine2/src/jasmine-light.js b/packages/jest-jasmine2/src/jasmine-light.js deleted file mode 100644 index f93b2e7233a2..000000000000 --- a/packages/jest-jasmine2/src/jasmine-light.js +++ /dev/null @@ -1,1841 +0,0 @@ -/* -Copyright (c) 2008-2016 Pivotal Labs - -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 */ -'use strict'; - -const formatErrorMsg = function() { - function generateErrorMsg(domain, usage) { - const usageDefinition = usage ? '\nUsage: ' + usage : ''; - - return function errorMsg(msg) { - return domain + ' : ' + msg + usageDefinition; - }; - } - - return generateErrorMsg; -}; - -class ExpectationFailed extends Error {} - -exports.create = function() { - const j$ = {}; - - exports.base(j$); - j$.CallTracker = exports.CallTracker(j$); - j$.Env = exports.Env(j$); - j$.ExceptionFormatter = exports.ExceptionFormatter(); - j$.buildExpectationResult = exports.buildExpectationResult(); - j$.JsApiReporter = exports.JsApiReporter(); - j$.QueueRunner = exports.QueueRunner(j$); - j$.ReportDispatcher = exports.ReportDispatcher(); - j$.Spec = exports.Spec(j$); - j$.SpyRegistry = exports.SpyRegistry(j$); - j$.SpyStrategy = exports.SpyStrategy(j$); - j$.Suite = exports.Suite(j$); - j$.Timer = exports.Timer(); - j$.TreeProcessor = exports.TreeProcessor(); - j$.version = exports.version(); - - return j$; -}; - -exports.base = function(j$) { - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - - j$.getEnv = function(options) { - const env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); - //jasmine. singletons in here (setTimeout blah blah). - return env; - }; - - j$.createSpy = function(name, originalFn) { - const spyStrategy = new j$.SpyStrategy({ - name, - fn: originalFn, - getSpy() { - return spy; - }, - }); - const callTracker = new j$.CallTracker(); - const spy = function() { - const callData = { - object: this, - args: Array.prototype.slice.apply(arguments), - }; - - callTracker.track(callData); - const returnValue = spyStrategy.exec.apply(this, arguments); - callData.returnValue = returnValue; - - return returnValue; - }; - - for (const prop in originalFn) { - if (prop === 'and' || prop === 'calls') { - throw new Error( - "Jasmine spies would overwrite the 'and' and 'calls' properties " + - 'on the object being spied upon', - ); - } - - spy[prop] = originalFn[prop]; - } - - spy.and = spyStrategy; - spy.calls = callTracker; - - return spy; - }; - - j$.isSpy = function(putativeSpy) { - if (!putativeSpy) { - return false; - } - return putativeSpy.and instanceof j$.SpyStrategy && - putativeSpy.calls instanceof j$.CallTracker; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (Array.isArray(baseName) && methodNames === void 0) { - methodNames = baseName; - baseName = 'unknown'; - } - - if (!Array.isArray(methodNames) || methodNames.length === 0) { - throw new Error( - 'createSpyObj requires a non-empty array of method names to ' + - 'create spies for', - ); - } - const obj = {}; - for (let i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; -}; - -exports.Spec = function(j$) { - function Spec(attrs) { - this.expectationFactory = attrs.expectationFactory; - 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.expectationResultFactory = attrs.expectationResultFactory || - function() {}; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.catchingExceptions = attrs.catchingExceptions || - function() { - return true; - }; - this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - - if (!this.queueableFn.fn) { - this.pend(); - } - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [], - pendingReason: '', - }; - } - - Spec.prototype.addExpectationResult = function(passed, data, isError) { - const expectationResult = this.expectationResultFactory(data); - 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 || enabled === false) { - complete(enabled); - return; - } - - const fns = this.beforeAndAfterFns(); - const allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); - - this.queueRunnerFactory({ - queueableFns: allFns, - onException() { - self.onException.apply(self, arguments); - }, - onComplete: complete, - userContext: this.userContext(), - }); - - function complete(enabledAgain) { - self.result.status = self.status(enabledAgain); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - }; - - Spec.prototype.onException = function onException(e) { - if (Spec.isPendingSpecException(e)) { - this.pend(extractCustomPendingMessage(e)); - return; - } - - if (e instanceof ExpectationFailed) { - return; - } - - this.addExpectationResult( - false, - { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e, - }, - true, - ); - }; - - Spec.prototype.disable = function() { - this.disabled = true; - }; - - Spec.prototype.pend = function(message) { - this.markedPending = true; - if (message) { - this.result.pendingReason = message; - } - }; - - 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.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); - }; - - return Spec; -}; - -exports.Env = 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 expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ - actual, - addExpectationResult, - }); - - function addExpectationResult(passed, result) { - return spec.addExpectationResult(passed, result); - } - }; - - 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(' '); - }; - - const buildExpectationResult = j$.buildExpectationResult; - const exceptionFormatter = new j$.ExceptionFormatter(); - const expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; - - return buildExpectationResult(attrs); - }; - - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - const maximumSpecCallbackDepth = 20; - let currentSpecCallbackDepth = 0; - - function clearStack(fn) { - currentSpecCallbackDepth++; - if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { - currentSpecCallbackDepth = 0; - realSetTimeout(fn, 0); - } else { - fn(); - } - } - - const catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || 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 = function(options) { - options.catchException = catchException; - options.clearStack = options.clearStack || clearStack; - options.timeout = { - setTimeout: realSetTimeout, - clearTimeout: realClearTimeout, - }; - options.fail = self.fail; - - new j$.QueueRunner(options).execute(); - }; - - const topSuite = new j$.Suite({ - env: this, - id: getNextSuiteId(), - description: 'test', - expectationFactory, - expectationResultFactory, - }); - defaultResourcesForRunnable(topSuite.id); - currentDeclarationSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - this.execute = function(runnablesToRun) { - if (!runnablesToRun) { - if (focusedRunnables.length) { - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [topSuite.id]; - } - } - - const processor = new j$.TreeProcessor({ - tree: topSuite, - runnableIds: runnablesToRun, - queueRunnerFactory, - nodeStart(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); - }, - nodeComplete(suite, result) { - if (!suite.disabled) { - clearResourcesForRunnable(suite.id); - } - currentlyExecutingSuites.pop(); - reporter.suiteDone(result); - }, - }); - - if (!processor.processTree().valid) { - throw new Error( - 'Invalid order: would cause a beforeAll or afterAll to be ' + - 'run multiple times', - ); - } - - reporter.jasmineStarted({ - totalSpecsDefined, - }); - - currentlyExecutingSuites.push(topSuite); - - processor.execute(() => { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - - reporter.jasmineDone({ - failedExpectations: topSuite.result.failedExpectations, - }); - }); - }; - - 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({ - env: self, - id: getNextSuiteId(), - description, - parentSuite: currentDeclarationSuite, - expectationFactory, - expectationResultFactory, - throwOnExpectationFailure, - }); - - return suite; - }; - - this.describe = function(description, specDefinitions) { - const suite = suiteFactory(description); - if (specDefinitions.length > 0) { - throw new Error('describe does not expect any arguments'); - } - if (currentDeclarationSuite.markedPending) { - suite.pend(); - } - 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), - expectationFactory, - resultCallback: specResultCallback, - getSpecName(spec) { - return getSpecName(spec, suite); - }, - onStart: specStarted, - description, - expectationResultFactory, - 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) { - const spec = specFactory( - description, - fn, - currentDeclarationSuite, - timeout, - ); - if (currentDeclarationSuite.markedPending) { - spec.pend(); - } - currentDeclarationSuite.addChild(spec); - return spec; - }; - - this.xit = function() { - const spec = this.it.apply(this, arguments); - spec.pend('Temporarily disabled with xit'); - 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 message = 'Failed'; - if (error) { - message += ': '; - message += error.message || error; - } - - currentRunnable().addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - message, - error: error && error.message ? error : null, - }); - }; - } - - return Env; -}; - -exports.JsApiReporter = function() { - const noopTimer = { - start() {}, - elapsed() { - return 0; - }, - }; - - function JsApiReporter(options) { - 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; - - this.jasmineDone = function(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; - }; - } - - return JsApiReporter; -}; - -exports.CallTracker = function(j$) { - function CallTracker() { - let calls = []; - - this.track = function(context) { - calls.push(context); - }; - - this.any = function() { - return !!calls.length; - }; - - this.count = function() { - return calls.length; - }; - - this.argsFor = function(index) { - const call = calls[index]; - return call ? call.args : []; - }; - - this.all = function() { - return calls; - }; - - this.allArgs = function() { - const callArgs = []; - for (let i = 0; i < calls.length; i++) { - callArgs.push(calls[i].args); - } - - return callArgs; - }; - - this.first = function() { - return calls[0]; - }; - - this.mostRecent = function() { - return calls[calls.length - 1]; - }; - - this.reset = function() { - calls = []; - }; - } - - return CallTracker; -}; - -exports.ExceptionFormatter = function() { - function ExceptionFormatter() { - this.message = function(error) { - let message = ''; - - if (error.name && error.message) { - message += error.name + ': ' + error.message; - } else { - message += error.toString() + ' thrown'; - } - - if (error.fileName || error.sourceURL) { - message += ' in ' + (error.fileName || error.sourceURL); - } - - if (error.line || error.lineNumber) { - message += ' (line ' + (error.line || error.lineNumber) + ')'; - } - - return message; - }; - - this.stack = function(error) { - return error ? error.stack : null; - }; - } - - return ExceptionFormatter; -}; - -exports.buildExpectationResult = function() { - function buildExpectationResult(options) { - const messageFormatter = options.messageFormatter || function() {}; - const stackFormatter = options.stackFormatter || function() {}; - - const result = { - matcherName: options.matcherName, - message: message(), - stack: stack(), - passed: options.passed, - // CUSTOM JEST CHANGE: we pass error message to the result. - error: options.error, - }; - - if (!result.passed) { - result.expected = options.expected; - result.actual = options.actual; - } - - return result; - - function message() { - if (options.passed) { - return 'Passed.'; - } else if (options.message) { - return options.message; - } else if (options.error) { - return messageFormatter(options.error); - } - return ''; - } - - function stack() { - if (options.passed) { - return ''; - } - - let error = options.error; - if (!error) { - try { - throw new Error(message()); - } catch (e) { - error = e; - } - } - return stackFormatter(error); - } - } - - return buildExpectationResult; -}; - -exports.QueueRunner = function(j$) { - function once(fn) { - let called = false; - return function() { - if (!called) { - called = true; - fn(); - } - return null; - }; - } - - function QueueRunner(attrs) { - this.queueableFns = attrs.queueableFns || []; - this.onComplete = attrs.onComplete || function() {}; - this.clearStack = attrs.clearStack || - function(fn) { - fn(); - }; - this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || - function() { - return true; - }; - this.userContext = attrs.userContext || {}; - this.timeout = attrs.timeout || { - setTimeout, - clearTimeout, - }; - this.fail = attrs.fail || function() {}; - } - - QueueRunner.prototype.execute = function() { - this.run(this.queueableFns, 0); - }; - - QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - const length = queueableFns.length; - const self = this; - let iterativeIndex; - - for ( - iterativeIndex = recursiveIndex; - iterativeIndex < length; - iterativeIndex++ - ) { - const queueableFn = queueableFns[iterativeIndex]; - if (queueableFn.fn.length > 0) { - attemptAsync(queueableFn); - return; - } else { - attemptSync(queueableFn); - } - } - - const runnerDone = iterativeIndex >= length; - - if (runnerDone) { - this.clearStack(this.onComplete); - } - - function attemptSync(queueableFn) { - try { - queueableFn.fn.call(self.userContext); - } catch (e) { - handleException(e, queueableFn); - } - } - - function attemptAsync(queueableFn) { - const clearTimeout = function() { - Function.prototype.apply.apply(self.timeout.clearTimeout, [ - global, - [timeoutId], - ]); - }; - const next = once(() => { - clearTimeout(timeoutId); - self.run(queueableFns, iterativeIndex + 1); - }); - let timeoutId; - - next.fail = function() { - self.fail.apply(null, arguments); - next(); - }; - - if (queueableFn.timeout) { - timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [ - global, - [ - function() { - const error = new Error( - 'Timeout - Async callback was not invoked within ' + - 'timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.', - ); - onException(error); - next(); - }, - queueableFn.timeout(), - ], - ]); - } - - try { - queueableFn.fn.call(self.userContext, next); - } catch (e) { - handleException(e, queueableFn); - next(); - } - } - - function onException(e) { - self.onException(e); - } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - throw e; - } - } - }; - - return QueueRunner; -}; - -exports.ReportDispatcher = function() { - function ReportDispatcher(methods) { - 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); - } - } - } - } - - return ReportDispatcher; -}; - -exports.SpyRegistry = function(j$) { - const getErrorMsg = formatErrorMsg( - '', - 'spyOn(, )', - ); - - function SpyRegistry(options) { - options = options || {}; - const currentSpies = options.currentSpies || - function() { - return []; - }; - - this.allowRespy = function(allow) { - this.respy = allow; - }; - - this.spyOn = function(obj, methodName) { - 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] && j$.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 = j$.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.clearSpies = function() { - const spies = currentSpies(); - for (let i = spies.length - 1; i >= 0; i--) { - const spyEntry = spies[i]; - spyEntry.restoreObjectToOriginalState(); - } - }; - } - - return SpyRegistry; -}; - -exports.SpyStrategy = function(j$) { - function SpyStrategy(options) { - 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(); - }; - } - - return SpyStrategy; -}; - -exports.Suite = function(j$) { - function Suite(attrs) { - this.env = attrs.env; - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = attrs.description; - this.expectationFactory = attrs.expectationFactory; - this.expectationResultFactory = attrs.expectationResultFactory; - 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: [], - }; - } - - 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.push(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(this.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(this.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].result.status; - } - - function isFailure(args) { - return !args[0]; - } - - return Suite; -}; - -exports.Timer = function() { - const defaultNow = (function(Date) { - return function() { - return new Date().getTime(); - }; - })(Date); - - function Timer(options) { - options = options || {}; - - const now = options.now || defaultNow; - let startTime; - - this.start = function() { - startTime = now(); - }; - - this.elapsed = function() { - return now() - startTime; - }; - } - - return Timer; -}; - -exports.TreeProcessor = function() { - function TreeProcessor(attrs) { - const tree = attrs.tree; - const runnableIds = attrs.runnableIds; - const queueRunnerFactory = attrs.queueRunnerFactory; - const nodeStart = attrs.nodeStart || function() {}; - const nodeComplete = attrs.nodeComplete || function() {}; - const defaultMin = Infinity; - const defaultMax = 1 - Infinity; - let processed = false; - let stats = {valid: true}; - - this.processTree = function() { - processNode(tree, false); - processed = true; - return stats; - }; - - this.execute = function(done) { - if (!processed) { - this.processTree(); - } - - if (!stats.valid) { - throw new Error('invalid order'); - } - - const childFns = wrapChildren(tree, 0); - - queueRunnerFactory({ - queueableFns: childFns, - userContext: tree.sharedUserContext(), - onException() { - tree.onException.apply(tree, arguments); - }, - onComplete: done, - }); - }; - - function runnableIndex(id) { - for (let i = 0; i < runnableIds.length; i++) { - if (runnableIds[i] === id) { - return i; - } - } - return void 0; - } - - function processNode(node, parentEnabled) { - const executableIndex = runnableIndex(node.id); - - if (executableIndex !== undefined) { - parentEnabled = true; - } - - parentEnabled = parentEnabled && node.isExecutable(); - - if (!node.children) { - stats[node.id] = { - executable: parentEnabled && node.isExecutable(), - segments: [ - { - index: 0, - owner: node, - nodes: [node], - min: startingMin(executableIndex), - max: startingMax(executableIndex), - }, - ], - }; - } else { - let hasExecutableChild = false; - - const children = node.children; - - for (let i = 0; i < children.length; i++) { - const child = children[i]; - - processNode(child, parentEnabled); - - if (!stats.valid) { - return; - } - - const childStats = stats[child.id]; - - hasExecutableChild = hasExecutableChild || childStats.executable; - } - - stats[node.id] = { - executable: hasExecutableChild, - }; - - segmentChildren(node, children, stats[node.id], executableIndex); - - if (!node.canBeReentered() && stats[node.id].segments.length > 1) { - stats = {valid: false}; - } - } - } - - function startingMin(executableIndex) { - return executableIndex === undefined ? defaultMin : executableIndex; - } - - function startingMax(executableIndex) { - return executableIndex === undefined ? defaultMax : executableIndex; - } - - function segmentChildren(node, children, nodeStats, executableIndex) { - let currentSegment = { - index: 0, - owner: node, - nodes: [], - min: startingMin(executableIndex), - max: startingMax(executableIndex), - }; - const result = [currentSegment]; - const orderedChildSegments = orderChildSegments(children); - let lastMax = defaultMax; - - function isSegmentBoundary(minIndex) { - return lastMax !== defaultMax && - minIndex !== defaultMin && - lastMax < minIndex - 1; - } - - for (let i = 0; i < orderedChildSegments.length; i++) { - const childSegment = orderedChildSegments[i]; - const maxIndex = childSegment.max; - const minIndex = childSegment.min; - - if (isSegmentBoundary(minIndex)) { - currentSegment = { - index: result.length, - owner: node, - nodes: [], - min: defaultMin, - max: defaultMax, - }; - result.push(currentSegment); - } - - currentSegment.nodes.push(childSegment); - currentSegment.min = Math.min(currentSegment.min, minIndex); - currentSegment.max = Math.max(currentSegment.max, maxIndex); - lastMax = maxIndex; - } - - nodeStats.segments = result; - } - - function orderChildSegments(children) { - const specifiedOrder = []; - const unspecifiedOrder = []; - - for (let i = 0; i < children.length; i++) { - const child = children[i]; - const segments = stats[child.id].segments; - - for (let j = 0; j < segments.length; j++) { - const seg = segments[j]; - - if (seg.min === defaultMin) { - unspecifiedOrder.push(seg); - } else { - specifiedOrder.push(seg); - } - } - } - - specifiedOrder.sort((a, b) => { - return a.min - b.min; - }); - - return specifiedOrder.concat(unspecifiedOrder); - } - - function executeNode(node, segmentNumber) { - if (node.children) { - return { - fn(done) { - nodeStart(node); - - queueRunnerFactory({ - onComplete() { - nodeComplete(node, node.getResult()); - done(); - }, - queueableFns: wrapChildren(node, segmentNumber), - userContext: node.sharedUserContext(), - onException() { - node.onException.apply(node, arguments); - }, - }); - }, - }; - } else { - return { - fn(done) { - node.execute(done, stats[node.id].executable); - }, - }; - } - } - - function wrapChildren(node, segmentNumber) { - const result = []; - const segmentChildren = stats[node.id].segments[segmentNumber].nodes; - - for (let i = 0; i < segmentChildren.length; i++) { - result.push( - executeNode(segmentChildren[i].owner, segmentChildren[i].index), - ); - } - - if (!stats[node.id].executable) { - return result; - } - - return node.beforeAllFns.concat(result).concat(node.afterAllFns); - } - } - - return TreeProcessor; -}; - -exports.interface = function(jasmine, env) { - const jasmineInterface = { - describe(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - xdescribe(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - fdescribe(description, specDefinitions) { - return env.fdescribe(description, specDefinitions); - }, - - it() { - return env.it.apply(env, arguments); - }, - - xit() { - return env.xit.apply(env, arguments); - }, - - fit() { - return env.fit.apply(env, arguments); - }, - - beforeEach() { - return env.beforeEach.apply(env, arguments); - }, - - afterEach() { - return env.afterEach.apply(env, arguments); - }, - - beforeAll() { - return env.beforeAll.apply(env, arguments); - }, - - afterAll() { - return env.afterAll.apply(env, arguments); - }, - - pending() { - return env.pending.apply(env, arguments); - }, - - fail() { - return env.fail.apply(env, arguments); - }, - - spyOn(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer(), - }), - - jasmine, - }; - - return jasmineInterface; -}; - -exports.version = function() { - return '2.5.2-light'; -}; diff --git a/packages/jest-jasmine2/src/jasmine/CallTracker.js b/packages/jest-jasmine2/src/jasmine/CallTracker.js new file mode 100644 index 000000000000..8a16d12f072b --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/CallTracker.js @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function CallTracker() { + let calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + const call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + const callArgs = []; + for (let i = 0; i < calls.length; i++) { + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; +} + +module.exports = CallTracker; diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js new file mode 100644 index 000000000000..b522c523f7df --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -0,0 +1,537 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +module.exports = 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 expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + actual, + addExpectationResult, + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + 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(' '); + }; + + const buildExpectationResult = j$.buildExpectationResult; + const exceptionFormatter = new j$.ExceptionFormatter(); + const expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + const maximumSpecCallbackDepth = 20; + let currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + const catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || 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 = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timeout = { + setTimeout: realSetTimeout, + clearTimeout: realClearTimeout, + }; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + const topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'test', + expectationFactory, + expectationResultFactory, + }); + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if (!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } + + const processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory, + nodeStart(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + }, + }); + + if (!processor.processTree().valid) { + throw new Error( + 'Invalid order: would cause a beforeAll or afterAll to be ' + + 'run multiple times', + ); + } + + reporter.jasmineStarted({ + totalSpecsDefined, + }); + + currentlyExecutingSuites.push(topSuite); + + processor.execute(() => { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + + reporter.jasmineDone({ + failedExpectations: topSuite.result.failedExpectations, + }); + }); + }; + + 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({ + env: self, + id: getNextSuiteId(), + description, + parentSuite: currentDeclarationSuite, + expectationFactory, + expectationResultFactory, + throwOnExpectationFailure, + }); + + return suite; + }; + + this.describe = function(description, specDefinitions) { + const suite = suiteFactory(description); + if (specDefinitions.length > 0) { + throw new Error('describe does not expect any arguments'); + } + if (currentDeclarationSuite.markedPending) { + suite.pend(); + } + 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), + expectationFactory, + resultCallback: specResultCallback, + getSpecName(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description, + expectationResultFactory, + 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) { + const spec = specFactory( + description, + fn, + currentDeclarationSuite, + timeout, + ); + if (currentDeclarationSuite.markedPending) { + spec.pend(); + } + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + const spec = this.it.apply(this, arguments); + spec.pend('Temporarily disabled with xit'); + 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 message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message, + error: error && error.message ? error : null, + }); + }; + } + + return Env; +}; diff --git a/packages/jest-jasmine2/src/jasmine/ExceptionFormatter.js b/packages/jest-jasmine2/src/jasmine/ExceptionFormatter.js new file mode 100644 index 000000000000..c604536332b2 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/ExceptionFormatter.js @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function ExceptionFormatter() { + this.message = function(error) { + let message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + +module.exports = ExceptionFormatter; diff --git a/packages/jest-jasmine2/src/jasmine/ExpectationFailed.js b/packages/jest-jasmine2/src/jasmine/ExpectationFailed.js new file mode 100644 index 000000000000..245876d89b01 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/ExpectationFailed.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; + +class ExpectationFailed extends Error {} + +module.exports = ExpectationFailed; diff --git a/packages/jest-jasmine2/src/jasmine/JsApiReporter.js b/packages/jest-jasmine2/src/jasmine/JsApiReporter.js new file mode 100644 index 000000000000..6254180e01ef --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/JsApiReporter.js @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const noopTimer = { + start() {}, + elapsed() { + return 0; + }, +}; + +function JsApiReporter(options) { + 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; + + this.jasmineDone = function(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; + }; +} + +module.exports = JsApiReporter; diff --git a/packages/jest-jasmine2/src/jasmine/QueueRunner.js b/packages/jest-jasmine2/src/jasmine/QueueRunner.js new file mode 100644 index 000000000000..9959cc852259 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/QueueRunner.js @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function once(fn) { + let called = false; + return function() { + if (!called) { + called = true; + fn(); + } + return null; + }; +} + +function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || + function(fn) { + fn(); + }; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || + function() { + return true; + }; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || { + setTimeout, + clearTimeout, + }; + this.fail = attrs.fail || function() {}; +} + +QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); +}; + +QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + const length = queueableFns.length; + const self = this; + let iterativeIndex; + + for ( + iterativeIndex = recursiveIndex; + iterativeIndex < length; + iterativeIndex++ + ) { + const queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; + } else { + attemptSync(queueableFn); + } + } + + const runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + const clearTimeout = function() { + Function.prototype.apply.apply(self.timeout.clearTimeout, [ + global, + [timeoutId], + ]); + }; + const next = once(() => { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }); + let timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [ + global, + [ + function() { + const error = new Error( + 'Timeout - Async callback was not invoked within ' + + 'timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.', + ); + onException(error); + next(); + }, + queueableFn.timeout(), + ], + ]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e); + if (!self.catchException(e)) { + throw e; + } + } +}; + +module.exports = QueueRunner; diff --git a/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js b/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js new file mode 100644 index 000000000000..ca24f9277a8c --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/ReportDispatcher.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function ReportDispatcher(methods) { + 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); + } + } + } + } + +module.exports = ReportDispatcher; diff --git a/packages/jest-jasmine2/src/jasmine/Spec.js b/packages/jest-jasmine2/src/jasmine/Spec.js new file mode 100644 index 000000000000..c11e120ea8b7 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Spec.js @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const ExpectationFailed = require('./ExpectationFailed'); + +function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + 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.expectationResultFactory = attrs.expectationResultFactory || + function() {}; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || + function() { + return true; + }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '', + }; +} + +Spec.prototype.addExpectationResult = function(passed, data, isError) { + const expectationResult = this.expectationResultFactory(data); + 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 || enabled === false) { + complete(enabled); + return; + } + + const fns = this.beforeAndAfterFns(); + const allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException() { + self.onException.apply(self, arguments); + }, + onComplete: complete, + userContext: this.userContext(), + }); + + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } +}; + +Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof ExpectationFailed) { + return; + } + + this.addExpectationResult( + false, + { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e, + }, + true, + ); +}; + +Spec.prototype.disable = function() { + this.disabled = true; +}; + +Spec.prototype.pend = function(message) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } +}; + +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.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); +}; + +module.exports = Spec; diff --git a/packages/jest-jasmine2/src/jasmine/SpyRegistry.js b/packages/jest-jasmine2/src/jasmine/SpyRegistry.js new file mode 100644 index 000000000000..5dc4ae019011 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/SpyRegistry.js @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const formatErrorMsg = function() { + function generateErrorMsg(domain, usage) { + const usageDefinition = usage ? '\nUsage: ' + usage : ''; + + return function errorMsg(msg) { + return domain + ' : ' + msg + usageDefinition; + }; + } + + return generateErrorMsg; +}; + +module.exports = function(j$) { + const getErrorMsg = formatErrorMsg( + '', + 'spyOn(, )', + ); + + function SpyRegistry(options) { + options = options || {}; + const currentSpies = options.currentSpies || + function() { + return []; + }; + + this.allowRespy = function(allow) { + this.respy = allow; + }; + + this.spyOn = function(obj, methodName) { + 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] && j$.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 = j$.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.clearSpies = function() { + const spies = currentSpies(); + for (let i = spies.length - 1; i >= 0; i--) { + const spyEntry = spies[i]; + spyEntry.restoreObjectToOriginalState(); + } + }; + } + + return SpyRegistry; +}; diff --git a/packages/jest-jasmine2/src/jasmine/SpyStrategy.js b/packages/jest-jasmine2/src/jasmine/SpyStrategy.js new file mode 100644 index 000000000000..4d238a729524 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/SpyStrategy.js @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function SpyStrategy(options) { + 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(); + }; + } + +module.exports = SpyStrategy; diff --git a/packages/jest-jasmine2/src/jasmine/Suite.js b/packages/jest-jasmine2/src/jasmine/Suite.js new file mode 100644 index 000000000000..1977694d1364 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Suite.js @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const ExpectationFailed = require('./ExpectationFailed'); + +function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + 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: [], + }; +} + +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.push(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(this.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(this.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].result.status; +} + +function isFailure(args) { + return !args[0]; +} + +module.exports = Suite; diff --git a/packages/jest-jasmine2/src/jasmine/Timer.js b/packages/jest-jasmine2/src/jasmine/Timer.js new file mode 100644 index 000000000000..f1acb529b6a8 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/Timer.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const defaultNow = (function(Date) { + return function() { + return new Date().getTime(); + }; +})(Date); + +function Timer(options) { + options = options || {}; + + const now = options.now || defaultNow; + let startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; +} + +module.exports = Timer; diff --git a/packages/jest-jasmine2/src/jasmine/TreeProcessor.js b/packages/jest-jasmine2/src/jasmine/TreeProcessor.js new file mode 100644 index 000000000000..663d7f703edb --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/TreeProcessor.js @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function TreeProcessor(attrs) { + const tree = attrs.tree; + const runnableIds = attrs.runnableIds; + const queueRunnerFactory = attrs.queueRunnerFactory; + const nodeStart = attrs.nodeStart || function() {}; + const nodeComplete = attrs.nodeComplete || function() {}; + const defaultMin = Infinity; + const defaultMax = 1 - Infinity; + let processed = false; + let stats = {valid: true}; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw new Error('invalid order'); + } + + const childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException() { + tree.onException.apply(tree, arguments); + }, + onComplete: done, + }); + }; + + function runnableIndex(id) { + for (let i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + return void 0; + } + + function processNode(node, parentEnabled) { + const executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [ + { + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex), + }, + ], + }; + } else { + let hasExecutableChild = false; + + const children = node.children; + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + const childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild, + }; + + segmentChildren(node, children, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = {valid: false}; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren( + node, + children, + nodeStats, + executableIndex, + ) { + let currentSegment = { + index: 0, + owner: node, + nodes: [], + min: startingMin(executableIndex), + max: startingMax(executableIndex), + }; + const result = [currentSegment]; + const orderedChildSegments = orderChildSegments(children); + let lastMax = defaultMax; + + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && + minIndex !== defaultMin && + lastMax < minIndex - 1; + } + + for (let i = 0; i < orderedChildSegments.length; i++) { + const childSegment = orderedChildSegments[i]; + const maxIndex = childSegment.max; + const minIndex = childSegment.min; + + if (isSegmentBoundary(minIndex)) { + currentSegment = { + index: result.length, + owner: node, + nodes: [], + min: defaultMin, + max: defaultMax, + }; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(children) { + const specifiedOrder = []; + const unspecifiedOrder = []; + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const segments = stats[child.id].segments; + + for (let j = 0; j < segments.length; j++) { + const seg = segments[j]; + + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } + + specifiedOrder.sort((a, b) => { + return a.min - b.min; + }); + + return specifiedOrder.concat(unspecifiedOrder); + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException() { + node.onException.apply(node, arguments); + }, + }); + }, + }; + } else { + return { + fn(done) { + node.execute(done, stats[node.id].executable); + }, + }; + } + } + + function wrapChildren(node, segmentNumber) { + const result = []; + const segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (let i = 0; i < segmentChildren.length; i++) { + result.push( + executeNode(segmentChildren[i].owner, segmentChildren[i].index), + ); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + +module.exports = TreeProcessor; diff --git a/packages/jest-jasmine2/src/jasmine/buildExpectationResult.js b/packages/jest-jasmine2/src/jasmine/buildExpectationResult.js new file mode 100644 index 000000000000..f099207ec9b8 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/buildExpectationResult.js @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +function buildExpectationResult(options) { + const messageFormatter = options.messageFormatter || function() {}; + const stackFormatter = options.stackFormatter || function() {}; + + const result = { + matcherName: options.matcherName, + message: message(), + stack: stack(), + passed: options.passed, + // CUSTOM JEST CHANGE: we pass error message to the result. + error: options.error, + }; + + if (!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + let error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } +} + +module.exports = buildExpectationResult; diff --git a/packages/jest-jasmine2/src/jasmine/jasmine-light.js b/packages/jest-jasmine2/src/jasmine/jasmine-light.js new file mode 100644 index 000000000000..c28127468287 --- /dev/null +++ b/packages/jest-jasmine2/src/jasmine/jasmine-light.js @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +// This file is a heavily modified fork of Jasmine. The original license of the code: +/* +Copyright (c) 2008-2016 Pivotal Labs + +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 */ +'use strict'; + +const buildExpectationResult = require('./buildExpectationResult'); +const CallTracker = require('./CallTracker'); +const Env = require('./Env'); +const ExceptionFormatter = require('./ExceptionFormatter'); +const JsApiReporter = require('./JsApiReporter'); +const QueueRunner = require('./QueueRunner'); +const ReportDispatcher = require('./ReportDispatcher'); +const Spec = require('./Spec'); +const SpyRegistry = require('./SpyRegistry'); +const SpyStrategy = require('./SpyStrategy'); +const Suite = require('./Suite'); +const Timer = require('./Timer'); +const TreeProcessor = require('./TreeProcessor'); + +exports.create = function() { + const j$ = {}; + + exports.base(j$); + j$.buildExpectationResult = buildExpectationResult; + j$.CallTracker = CallTracker; + j$.Env = Env(j$); + j$.ExceptionFormatter = ExceptionFormatter; + j$.JsApiReporter = JsApiReporter; + j$.QueueRunner = QueueRunner; + j$.ReportDispatcher = ReportDispatcher; + j$.Spec = Spec; + j$.SpyRegistry = SpyRegistry(j$); + j$.SpyStrategy = SpyStrategy; + j$.Suite = Suite; + j$.Timer = Timer; + j$.TreeProcessor = TreeProcessor; + j$.version = '2.5.2-light'; + + return j$; +}; + +exports.base = function(j$) { + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getEnv = function(options) { + const env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.createSpy = function(name, originalFn) { + const spyStrategy = new j$.SpyStrategy({ + name, + fn: originalFn, + getSpy() { + return spy; + }, + }); + const callTracker = new j$.CallTracker(); + const spy = function() { + const callData = { + object: this, + args: Array.prototype.slice.apply(arguments), + }; + + callTracker.track(callData); + const returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (const prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error( + "Jasmine spies would overwrite the 'and' and 'calls' properties " + + 'on the object being spied upon', + ); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (Array.isArray(baseName) && methodNames === void 0) { + methodNames = baseName; + baseName = 'unknown'; + } + + if (!Array.isArray(methodNames) || methodNames.length === 0) { + throw new Error( + 'createSpyObj requires a non-empty array of method names to ' + + 'create spies for', + ); + } + const obj = {}; + for (let i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +exports.interface = function(jasmine, env) { + const jasmineInterface = { + describe(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + fdescribe(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + + it() { + return env.it.apply(env, arguments); + }, + + xit() { + return env.xit.apply(env, arguments); + }, + + fit() { + return env.fit.apply(env, arguments); + }, + + beforeEach() { + return env.beforeEach.apply(env, arguments); + }, + + afterEach() { + return env.afterEach.apply(env, arguments); + }, + + beforeAll() { + return env.beforeAll.apply(env, arguments); + }, + + afterAll() { + return env.afterAll.apply(env, arguments); + }, + + pending() { + return env.pending.apply(env, arguments); + }, + + fail() { + return env.fail.apply(env, arguments); + }, + + spyOn(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer(), + }), + + jasmine, + }; + + return jasmineInterface; +};