UNPKG

jasmine-core

Version:

Simple JavaScript testing framework for browsers and node.js

1,591 lines (1,425 loc) 320 kB
/* Copyright (c) 2008-2019 Pivotal Labs Copyright (c) 2008-2025 The Jasmine developers 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-next-line no-unused-vars,no-var var getJasmineRequireObj = (function(jasmineGlobal) { let jasmineRequire; if ( typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined' ) { if (typeof global !== 'undefined') { jasmineGlobal = global; } else { jasmineGlobal = {}; } jasmineRequire = exports; } else { if ( typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]' ) { jasmineGlobal = window; } jasmineRequire = jasmineGlobal.jasmineRequire = {}; } function getJasmineRequire() { return jasmineRequire; } getJasmineRequire().core = function(jRequire) { const j$ = {}; jRequire.base(j$, jasmineGlobal); j$.util = jRequire.util(j$); j$.errors = jRequire.errors(); j$.formatErrorMsg = jRequire.formatErrorMsg(); j$.Any = jRequire.Any(j$); j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); j$.MockDate = jRequire.MockDate(j$); j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$); j$.Deprecator = jRequire.Deprecator(j$); j$.Env = jRequire.Env(j$); j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); j$.Expector = jRequire.Expector(j$); j$.Expectation = jRequire.Expectation(j$); j$.buildExpectationResult = jRequire.buildExpectationResult(j$); j$.JsApiReporter = jRequire.JsApiReporter(j$); j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$); j$.basicPrettyPrinter_ = j$.makePrettyPrinter(); j$.MatchersUtil = jRequire.MatchersUtil(j$); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); j$.MapContaining = jRequire.MapContaining(j$); j$.SetContaining = jRequire.SetContaining(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.NeverSkipPolicy = jRequire.NeverSkipPolicy(j$); j$.SkipAfterBeforeAllErrorPolicy = jRequire.SkipAfterBeforeAllErrorPolicy( j$ ); j$.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy( j$ ); j$.reporterEvents = jRequire.reporterEvents(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$); j$.RunableResources = jRequire.RunableResources(j$); j$.Runner = jRequire.Runner(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); j$.StringContaining = jRequire.StringContaining(j$); j$.UserContext = jRequire.UserContext(j$); j$.Suite = jRequire.Suite(j$); j$.SuiteBuilder = jRequire.SuiteBuilder(j$); j$.Timer = jRequire.Timer(); j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.Order = jRequire.Order(); j$.DiffBuilder = jRequire.DiffBuilder(j$); j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$); j$.ObjectPath = jRequire.ObjectPath(j$); j$.MismatchTree = jRequire.MismatchTree(j$); j$.GlobalErrors = jRequire.GlobalErrors(j$); j$.Truthy = jRequire.Truthy(j$); j$.Falsy = jRequire.Falsy(j$); j$.Empty = jRequire.Empty(j$); j$.NotEmpty = jRequire.NotEmpty(j$); j$.Is = jRequire.Is(j$); j$.matchers = jRequire.requireMatchers(jRequire, j$); j$.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$); return j$; }; return getJasmineRequire; })(this); getJasmineRequireObj().requireMatchers = function(jRequire, j$) { const availableMatchers = [ 'nothing', 'toBe', 'toBeCloseTo', 'toBeDefined', 'toBeInstanceOf', 'toBeFalse', 'toBeFalsy', 'toBeGreaterThan', 'toBeGreaterThanOrEqual', 'toBeLessThan', 'toBeLessThanOrEqual', 'toBeNaN', 'toBeNegativeInfinity', 'toBeNull', 'toBePositiveInfinity', 'toBeTrue', 'toBeTruthy', 'toBeUndefined', 'toBeNullish', 'toContain', 'toEqual', 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', 'toHaveBeenCalledOnceWith', 'toHaveBeenCalledTimes', 'toHaveBeenCalledWith', 'toHaveClass', 'toHaveClasses', 'toHaveSpyInteractions', 'toHaveNoOtherSpyInteractions', 'toMatch', 'toThrow', 'toThrowError', 'toThrowMatching' ], matchers = {}; for (const name of availableMatchers) { matchers[name] = jRequire[name](j$); } return matchers; }; getJasmineRequireObj().base = function(j$, jasmineGlobal) { /** * Maximum object depth the pretty printer will print to. * Set this to a lower value to speed up pretty printing if you have large objects. * @name jasmine.MAX_PRETTY_PRINT_DEPTH * @default 8 * @since 1.3.0 */ j$.MAX_PRETTY_PRINT_DEPTH = 8; /** * Maximum number of array elements to display when pretty printing objects. * This will also limit the number of keys and values displayed for an object. * Elements past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH * @default 50 * @since 2.7.0 */ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50; /** * Maximum number of characters to display when pretty printing objects. * Characters past this number will be ellipised. * @name jasmine.MAX_PRETTY_PRINT_CHARS * @default 100 * @since 2.9.0 */ j$.MAX_PRETTY_PRINT_CHARS = 1000; /** * Default number of milliseconds Jasmine will wait for an asynchronous spec, * before, or after function to complete. This can be overridden on a case by * case basis by passing a time limit as the third argument to {@link it}, * {@link beforeEach}, {@link afterEach}, {@link beforeAll}, or * {@link afterAll}. The value must be no greater than the largest number of * milliseconds supported by setTimeout, which is usually 2147483647. * * While debugging tests, you may want to set this to a large number (or pass * a large number to one of the functions mentioned above) so that Jasmine * does not move on to after functions or the next spec while you're debugging. * @name jasmine.DEFAULT_TIMEOUT_INTERVAL * @default 5000 * @since 1.3.0 */ let DEFAULT_TIMEOUT_INTERVAL = 5000; Object.defineProperty(j$, 'DEFAULT_TIMEOUT_INTERVAL', { get: function() { return DEFAULT_TIMEOUT_INTERVAL; }, set: function(newValue) { j$.util.validateTimeout(newValue, 'jasmine.DEFAULT_TIMEOUT_INTERVAL'); DEFAULT_TIMEOUT_INTERVAL = newValue; } }); j$.getGlobal = function() { return jasmineGlobal; }; /** * Get the currently booted Jasmine Environment. * * @name jasmine.getEnv * @since 1.3.0 * @function * @return {Env} */ j$.getEnv = function(options) { const env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options)); //jasmine. singletons in here (setTimeout blah blah). return env; }; j$.isArray_ = function(value) { return j$.isA_('Array', value); }; j$.isObject_ = function(value) { return ( !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value) ); }; j$.isString_ = function(value) { return j$.isA_('String', value); }; j$.isNumber_ = function(value) { return j$.isA_('Number', value); }; j$.isFunction_ = function(value) { return j$.isA_('Function', value); }; j$.isAsyncFunction_ = function(value) { return j$.isA_('AsyncFunction', value); }; j$.isGeneratorFunction_ = function(value) { return j$.isA_('GeneratorFunction', value); }; j$.isTypedArray_ = function(value) { return ( j$.isA_('Float32Array', value) || j$.isA_('Float64Array', value) || j$.isA_('Int16Array', value) || j$.isA_('Int32Array', value) || j$.isA_('Int8Array', value) || j$.isA_('Uint16Array', value) || j$.isA_('Uint32Array', value) || j$.isA_('Uint8Array', value) || j$.isA_('Uint8ClampedArray', value) ); }; j$.isA_ = function(typeName, value) { return j$.getType_(value) === '[object ' + typeName + ']'; }; j$.isError_ = function(value) { if (!value) { return false; } if (value instanceof Error) { return true; } return typeof value.stack === 'string' && typeof value.message === 'string'; }; j$.isAsymmetricEqualityTester_ = function(obj) { return obj ? j$.isA_('Function', obj.asymmetricMatch) : false; }; j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; j$.isDomNode = function(obj) { // Node is a function, because constructors return typeof jasmineGlobal.Node !== 'undefined' ? obj instanceof jasmineGlobal.Node : obj !== null && typeof obj === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string'; // return obj.nodeType > 0; }; j$.isMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && obj.constructor === jasmineGlobal.Map ); }; j$.isSet = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && obj.constructor === jasmineGlobal.Set ); }; j$.isWeakMap = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && obj.constructor === jasmineGlobal.WeakMap ); }; j$.isURL = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && obj.constructor === jasmineGlobal.URL ); }; j$.isIterable_ = function(value) { return value && !!value[Symbol.iterator]; }; j$.isDataView = function(obj) { return ( obj !== null && typeof obj !== 'undefined' && obj.constructor === jasmineGlobal.DataView ); }; j$.isPromise = function(obj) { return !!obj && obj.constructor === jasmineGlobal.Promise; }; j$.isPromiseLike = function(obj) { return !!obj && j$.isFunction_(obj.then); }; j$.fnNameFor = function(func) { if (func.name) { return func.name; } const matches = func.toString().match(/^\s*function\s*(\w+)\s*\(/) || func.toString().match(/^\s*\[object\s*(\w+)Constructor\]/); return matches ? matches[1] : '<anonymous>'; }; j$.isPending_ = function(promise) { const sentinel = {}; return Promise.race([promise, Promise.resolve(sentinel)]).then( function(result) { return result === sentinel; }, function() { return false; } ); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is an instance of the specified class/constructor. * @name asymmetricEqualityTesters.any * @emittedName jasmine.any * @since 1.3.0 * @function * @param {Constructor} clazz - The constructor to check against. */ j$.any = function(clazz) { return new j$.Any(clazz); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is not `null` and not `undefined`. * @name asymmetricEqualityTesters.anything * @emittedName jasmine.anything * @since 2.2.0 * @function */ j$.anything = function() { return new j$.Anything(); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is `true` or anything truthy. * @name asymmetricEqualityTesters.truthy * @emittedName jasmine.truthy * @since 3.1.0 * @function */ j$.truthy = function() { return new j$.Truthy(); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is `null`, `undefined`, `0`, `false` or anything * falsy. * @name asymmetricEqualityTesters.falsy * @emittedName jasmine.falsy * @since 3.1.0 * @function */ j$.falsy = function() { return new j$.Falsy(); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is empty. * @name asymmetricEqualityTesters.empty * @emittedName jasmine.empty * @since 3.1.0 * @function */ j$.empty = function() { return new j$.Empty(); }; /** * Get an {@link AsymmetricEqualityTester} that passes if the actual value is * the same as the sample as determined by the `===` operator. * @name asymmetricEqualityTesters.is * @emittedName jasmine.is * @function * @param {Object} sample - The value to compare the actual to. */ j$.is = function(sample) { return new j$.Is(sample); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared is not empty. * @name asymmetricEqualityTesters.notEmpty * @emittedName jasmine.notEmpty * @since 3.1.0 * @function */ j$.notEmpty = function() { return new j$.NotEmpty(); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value being compared contains at least the specified keys and values. * @name asymmetricEqualityTesters.objectContaining * @emittedName jasmine.objectContaining * @since 1.3.0 * @function * @param {Object} sample - The subset of properties that _must_ be in the actual. */ j$.objectContaining = function(sample) { return new j$.ObjectContaining(sample); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value is a `String` that matches the `RegExp` or `String`. * @name asymmetricEqualityTesters.stringMatching * @emittedName jasmine.stringMatching * @since 2.2.0 * @function * @param {RegExp|String} expected */ j$.stringMatching = function(expected) { return new j$.StringMatching(expected); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value is a `String` that contains the specified `String`. * @name asymmetricEqualityTesters.stringContaining * @emittedName jasmine.stringContaining * @since 3.10.0 * @function * @param {String} expected */ j$.stringContaining = function(expected) { return new j$.StringContaining(expected); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value is an `Array` that contains at least the elements in the sample. * @name asymmetricEqualityTesters.arrayContaining * @emittedName jasmine.arrayContaining * @since 2.2.0 * @function * @param {Array} sample */ j$.arrayContaining = function(sample) { return new j$.ArrayContaining(sample); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if the actual * value is an `Array` that contains all of the elements in the sample in * any order. * @name asymmetricEqualityTesters.arrayWithExactContents * @emittedName jasmine.arrayWithExactContents * @since 2.8.0 * @function * @param {Array} sample */ j$.arrayWithExactContents = function(sample) { return new j$.ArrayWithExactContents(sample); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if every * key/value pair in the sample passes the deep equality comparison * with at least one key/value pair in the actual value being compared * @name asymmetricEqualityTesters.mapContaining * @emittedName jasmine.mapContaining * @since 3.5.0 * @function * @param {Map} sample - The subset of items that _must_ be in the actual. */ j$.mapContaining = function(sample) { return new j$.MapContaining(sample); }; /** * Get an {@link AsymmetricEqualityTester} that will succeed if every item * in the sample passes the deep equality comparison * with at least one item in the actual value being compared * @name asymmetricEqualityTesters.setContaining * @emittedName jasmine.setContaining * @since 3.5.0 * @function * @param {Set} sample - The subset of items that _must_ be in the actual. */ j$.setContaining = function(sample) { return new j$.SetContaining(sample); }; /** * Determines whether the provided function is a Jasmine spy. * @name jasmine.isSpy * @since 2.0.0 * @function * @param {Function} putativeSpy - The function to check. * @return {Boolean} */ j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; } return ( putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker ); }; /** * Logs a message for use in debugging. If the spec fails, trace messages * will be included in the {@link SpecResult|result} passed to the * reporter's specDone method. * * This method should be called only when a spec (including any associated * beforeEach or afterEach functions) is running. * @function * @name jasmine.debugLog * @since 4.0.0 * @param {String} msg - The message to log */ j$.debugLog = function(msg) { j$.getEnv().debugLog(msg); }; /** * Replaces Jasmine's global error handling with a spy. This prevents Jasmine * from treating uncaught exceptions and unhandled promise rejections * as spec failures and allows them to be inspected using the spy's * {@link Spy#calls|calls property} and related matchers such as * {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}. * * After installing the spy, spyOnGlobalErrorsAsync immediately calls its * argument, which must be an async or promise-returning function. The spy * will be passed as the first argument to that callback. Normal error * handling will be restored when the promise returned from the callback is * settled. * * When the JavaScript runtime reports an uncaught error or unhandled rejection, * the spy will be called with a single parameter representing Jasmine's best * effort at describing the error. This parameter may be of any type, because * JavaScript allows anything to be thrown or used as the reason for a * rejected promise, but Error instances and strings are most common. * * Note: The JavaScript runtime may deliver uncaught error events and unhandled * rejection events asynchronously, especially in browsers. If the event * occurs after the promise returned from the callback is settled, it won't * be routed to the spy even if the underlying error occurred previously. * It's up to you to ensure that all of the error/rejection events that you * want to handle have occurred before you resolve the promise returned from * the callback. * * You must ensure that the `it`/`beforeEach`/etc fn that called * `spyOnGlobalErrorsAsync` does not signal completion until after the * promise returned by `spyOnGlobalErrorsAsync` is resolved. Normally this is * done by `await`ing the returned promise. Leaving the global error spy * installed after the `it`/`beforeEach`/etc fn that installed it signals * completion is likely to cause problems and is not supported. * @name jasmine.spyOnGlobalErrorsAsync * @function * @async * @param {AsyncFunction} fn - A function to run, during which the global error spy will be effective * @example * it('demonstrates global error spies', async function() { * await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) { * setTimeout(function() { * throw new Error('the expected error'); * }); * await new Promise(function(resolve) { * setTimeout(resolve); * }); * const expected = new Error('the expected error'); * expect(globalErrorSpy).toHaveBeenCalledWith(expected); * }); * }); */ j$.spyOnGlobalErrorsAsync = async function(fn) { await jasmine.getEnv().spyOnGlobalErrorsAsync(fn); }; }; getJasmineRequireObj().util = function(j$) { const util = {}; util.isUndefined = function(obj) { return obj === void 0; }; util.clone = function(obj) { if (Object.prototype.toString.apply(obj) === '[object Array]') { return obj.slice(); } const cloned = {}; for (const prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; }; util.cloneArgs = function(args) { return Array.from(args).map(function(arg) { const str = Object.prototype.toString.apply(arg), primitives = /^\[object (Boolean|String|RegExp|Number)/; // All falsey values are either primitives, `null`, or `undefined. if (!arg || str.match(primitives)) { return arg; } else if (str === '[object Date]') { return new Date(arg.valueOf()); } else { return j$.util.clone(arg); } }); }; util.getPropertyDescriptor = function(obj, methodName) { let descriptor, proto = obj; do { descriptor = Object.getOwnPropertyDescriptor(proto, methodName); proto = Object.getPrototypeOf(proto); } while (!descriptor && proto); return descriptor; }; util.has = function(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key); }; util.errorWithStack = function errorWithStack() { // Don't throw and catch. That makes it harder for users to debug their // code with exception breakpoints, and it's unnecessary since all // supported environments populate new Error().stack return new Error(); }; function callerFile() { const trace = new j$.StackTrace(util.errorWithStack()); return trace.frames[2].file; } util.jasmineFile = (function() { let result; return function() { if (!result) { result = callerFile(); } return result; }; })(); util.validateTimeout = function(timeout, msgPrefix) { // Timeouts are implemented with setTimeout, which only supports a limited // range of values. The limit is unspecified, as is the behavior when it's // exceeded. But on all currently supported JS runtimes, setTimeout calls // the callback immediately when the timeout is greater than 2147483647 // (the maximum value of a signed 32 bit integer). const max = 2147483647; if (timeout > max) { throw new Error( (msgPrefix || 'Timeout value') + ' cannot be greater than ' + max ); } }; return util; }; getJasmineRequireObj().Spec = function(j$) { function Spec(attrs) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.filename = attrs.filename; this.parentSuiteId = attrs.parentSuiteId; 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.autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.onLateError = attrs.onLateError || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.timer = attrs.timer || new j$.Timer(); if (!this.queueableFn.fn) { this.exclude(); } this.reset(); } Spec.prototype.addExpectationResult = function(passed, data, isError) { const expectationResult = j$.buildExpectationResult(data); if (passed) { this.result.passedExpectations.push(expectationResult); } else { if (this.reportedDone) { this.onLateError(expectationResult); } else { this.result.failedExpectations.push(expectationResult); // TODO: refactor so that we don't need to override cached status if (this.result.status) { this.result.status = 'failed'; } } if (this.throwOnExpectationFailure && !isError) { throw new j$.errors.ExpectationFailed(); } } }; Spec.prototype.setSpecProperty = function(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; }; Spec.prototype.execute = function( queueRunnerFactory, onComplete, excluded, failSpecWithNoExp ) { const onStart = { fn: done => { this.timer.start(); this.onStart(this, done); } }; const complete = { fn: done => { if (this.autoCleanClosures) { this.queueableFn.fn = null; } this.result.status = this.status(excluded, failSpecWithNoExp); this.result.duration = this.timer.elapsed(); if (this.result.status !== 'failed') { this.result.debugLogs = null; } this.resultCallback(this.result, done); }, type: 'specCleanup' }; const fns = this.beforeAndAfterFns(); const runnerConfig = { isLeaf: true, queueableFns: [...fns.befores, this.queueableFn, ...fns.afters], onException: e => this.handleException(e), onMultipleDone: () => { // Issue a deprecation. Include the context ourselves and pass // ignoreRunnable: true, since getting here always means that we've already // moved on and the current runnable isn't the one that caused the problem. this.onLateError( new Error( 'An asynchronous spec, beforeEach, or afterEach function called its ' + "'done' callback more than once.\n(in spec: " + this.getFullName() + ')' ) ); }, onComplete: () => { if (this.result.status === 'failed') { onComplete(new j$.StopExecutionError('spec failed')); } else { onComplete(); } }, userContext: this.userContext(), runnableName: this.getFullName.bind(this) }; if (this.markedPending || excluded === true) { runnerConfig.queueableFns = []; } runnerConfig.queueableFns.unshift(onStart); runnerConfig.queueableFns.push(complete); queueRunnerFactory(runnerConfig); }; Spec.prototype.reset = function() { /** * @typedef SpecResult * @property {String} id - The unique id of this spec. * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). * @property {String} filename - The name of the file the spec was defined in. * @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec. * @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec. * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} * @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec. * @since 2.0.0 */ this.result = { id: this.id, description: this.description, fullName: this.getFullName(), parentSuiteId: this.parentSuiteId, filename: this.filename, failedExpectations: [], passedExpectations: [], deprecationWarnings: [], pendingReason: this.excludeMessage || '', duration: null, properties: null, debugLogs: null }; this.markedPending = this.markedExcluding; this.reportedDone = false; }; Spec.prototype.handleException = function handleException(e) { if (Spec.isPendingSpecException(e)) { this.pend(extractCustomPendingMessage(e)); return; } if (e instanceof j$.errors.ExpectationFailed) { return; } this.addExpectationResult( false, { matcherName: '', passed: false, expected: '', actual: '', error: e }, true ); }; /* * Marks state as pending * @param {string} [message] An optional reason message */ Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { this.result.pendingReason = message; } }; /* * Like {@link Spec#pend}, but pending state will survive {@link Spec#reset} * Useful for fit, xit, where pending state remains. * @param {string} [message] An optional reason message */ Spec.prototype.exclude = function(message) { this.markedExcluding = true; if (this.message) { this.excludeMessage = message; } this.pend(message); }; Spec.prototype.getResult = function() { this.result.status = this.status(); return this.result; }; Spec.prototype.status = function(excluded, failSpecWithNoExpectations) { if (excluded === true) { return 'excluded'; } if (this.markedPending) { return 'pending'; } if ( this.result.failedExpectations.length > 0 || (failSpecWithNoExpectations && this.result.failedExpectations.length + this.result.passedExpectations.length === 0) ) { return 'failed'; } return 'passed'; }; Spec.prototype.getFullName = function() { return this.getSpecName(this); }; Spec.prototype.addDeprecationWarning = function(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( j$.buildExpectationResult(deprecation) ); }; Spec.prototype.debugLog = function(msg) { if (!this.result.debugLogs) { this.result.debugLogs = []; } /** * @typedef DebugLogEntry * @property {String} message - The message that was passed to {@link jasmine.debugLog}. * @property {number} timestamp - The time when the entry was added, in * milliseconds from the spec's start time */ this.result.debugLogs.push({ message: msg, timestamp: this.timer.elapsed() }); }; const extractCustomPendingMessage = function(e) { const fullMessage = e.toString(), boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; return fullMessage.slice(boilerplateEnd); }; Spec.pendingSpecExceptionMessage = '=> marked Pending'; Spec.isPendingSpecException = function(e) { return !!( e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1 ); }; /** * @interface Spec * @see Configuration#specFilter * @since 2.0.0 */ Object.defineProperty(Spec.prototype, 'metadata', { get: function() { if (!this.metadata_) { this.metadata_ = { /** * The unique ID of this spec. * @name Spec#id * @readonly * @type {string} * @since 2.0.0 */ id: this.id, /** * The description passed to the {@link it} that created this spec. * @name Spec#description * @readonly * @type {string} * @since 2.0.0 */ description: this.description, /** * The full description including all ancestors of this spec. * @name Spec#getFullName * @function * @returns {string} * @since 2.0.0 */ getFullName: this.getFullName.bind(this) }; } return this.metadata_; } }); return Spec; }; getJasmineRequireObj().Order = function() { function Order(options) { this.random = 'random' in options ? options.random : true; const seed = (this.seed = options.seed || generateSeed()); this.sort = this.random ? randomOrder : naturalOrder; function naturalOrder(items) { return items; } function randomOrder(items) { const copy = items.slice(); copy.sort(function(a, b) { return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); }); return copy; } function generateSeed() { return String(Math.random()).slice(-5); } // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function // used to get a different output when the key changes slightly. // We use your return to sort the children randomly in a consistent way when // used in conjunction with a seed function jenkinsHash(key) { let hash, i; for (hash = i = 0; i < key.length; ++i) { hash += key.charCodeAt(i); hash += hash << 10; hash ^= hash >> 6; } hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; return hash; } } return Order; }; getJasmineRequireObj().Env = function(j$) { /** * @class Env * @since 2.0.0 * @classdesc The Jasmine environment.<br> * _Note:_ Do not construct this directly. You can obtain the Env instance by * calling {@link jasmine.getEnv}. * @hideconstructor */ function Env(options) { options = options || {}; const self = this; const global = options.global || j$.getGlobal(); const realSetTimeout = global.setTimeout; const realClearTimeout = global.clearTimeout; const clearStack = j$.getClearStack(global); this.clock = new j$.Clock( global, function() { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global) ); const globalErrors = new j$.GlobalErrors(); const installGlobalErrors = (function() { let installed = false; return function() { if (!installed) { globalErrors.install(); installed = true; } }; })(); const runableResources = new j$.RunableResources({ getCurrentRunableId: function() { const r = runner.currentRunable(); return r ? r.id : null; }, globalErrors }); let reporter; let topSuite; let runner; let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel /** * This represents the available options to configure Jasmine. * Options that are not provided will use their default values. * @see Env#configure * @interface Configuration * @since 3.3.0 */ const config = { /** * Whether to randomize spec execution order * @name Configuration#random * @since 3.3.0 * @type Boolean * @default true */ random: true, /** * Seed to use as the basis of randomization. * Null causes the seed to be determined randomly at the start of execution. * @name Configuration#seed * @since 3.3.0 * @type (number|string) * @default null */ seed: null, /** * Whether to stop execution of the suite after the first spec failure * * <p>In parallel mode, `stopOnSpecFailure` works on a "best effort" * basis. Jasmine will stop execution as soon as practical after a failure * but it might not be immediate.</p> * @name Configuration#stopOnSpecFailure * @since 3.9.0 * @type Boolean * @default false */ stopOnSpecFailure: false, /** * Whether to fail the spec if it ran no expectations. By default * a spec that ran no expectations is reported as passed. Setting this * to true will report such spec as a failure. * @name Configuration#failSpecWithNoExpectations * @since 3.5.0 * @type Boolean * @default false */ failSpecWithNoExpectations: false, /** * Whether to cause specs to only have one expectation failure. * @name Configuration#stopSpecOnExpectationFailure * @since 3.3.0 * @type Boolean * @default false */ stopSpecOnExpectationFailure: false, /** * A function that takes a spec and returns true if it should be executed * or false if it should be skipped. * @callback SpecFilter * @param {Spec} spec - The spec that the filter is being applied to. * @return boolean */ /** * Function to use to filter specs * @name Configuration#specFilter * @since 3.3.0 * @type SpecFilter * @default A function that always returns true. */ specFilter: function() { return true; }, /** * Whether or not reporters should hide disabled specs from their output. * Currently only supported by Jasmine's HTMLReporter * @name Configuration#hideDisabled * @since 3.3.0 * @type Boolean * @default false */ hideDisabled: false, /** * Clean closures when a suite is done running (done by clearing the stored function reference). * This prevents memory leaks, but you won't be able to run jasmine multiple times. * @name Configuration#autoCleanClosures * @since 3.10.0 * @type boolean * @default true */ autoCleanClosures: true, /** * Whether to forbid duplicate spec or suite names. If set to true, using * the same name multiple times in the same immediate parent suite is an * error. * @name Configuration#forbidDuplicateNames * @type boolean * @default false */ forbidDuplicateNames: false, /** * Whether or not to issue warnings for certain deprecated functionality * every time it's used. If not set or set to false, deprecation warnings * for methods that tend to be called frequently will be issued only once * or otherwise throttled to to prevent the suite output from being flooded * with warnings. * @name Configuration#verboseDeprecations * @since 3.6.0 * @type Boolean * @default false */ verboseDeprecations: false }; if (!options.suppressLoadErrors) { installGlobalErrors(); globalErrors.pushListener(function loadtimeErrorHandler(error, event) { topSuite.result.failedExpectations.push({ passed: false, globalErrorType: 'load', message: error ? error.message : event.message, stack: error && error.stack, filename: event && event.filename, lineno: event && event.lineno }); }); } /** * Configure your jasmine environment * @name Env#configure * @since 3.3.0 * @argument {Configuration} configuration * @function */ this.configure = function(configuration) { if (parallelLoadingState) { throw new Error( 'Jasmine cannot be configured via Env in parallel mode' ); } const booleanProps = [ 'random', 'failSpecWithNoExpectations', 'hideDisabled', 'stopOnSpecFailure', 'stopSpecOnExpectationFailure', 'autoCleanClosures', 'forbidDuplicateNames' ]; booleanProps.forEach(function(prop) { if (typeof configuration[prop] !== 'undefined') { config[prop] = !!configuration[prop]; } }); if (configuration.specFilter) { config.specFilter = configuration.specFilter; } if (typeof configuration.seed !== 'undefined') { config.seed = configuration.seed; } if (configuration.hasOwnProperty('verboseDeprecations')) { config.verboseDeprecations = configuration.verboseDeprecations; deprecator.verboseDeprecations(config.verboseDeprecations); } }; /** * Get the current configuration for your jasmine environment * @name Env#configuration * @since 3.3.0 * @function * @returns {Configuration} */ this.configuration = function() { const result = {}; for (const property in config) { result[property] = config[property]; } return result; }; this.setDefaultSpyStrategy = function(defaultStrategyFn) { runableResources.setDefaultSpyStrategy(defaultStrategyFn); }; this.addSpyStrategy = function(name, fn) { runableResources.customSpyStrategies()[name] = fn; }; this.addCustomEqualityTester = function(tester) { runableResources.customEqualityTesters().push(tester); }; this.addMatchers = function(matchersToAdd) { runableResources.addCustomMatchers(matchersToAdd); }; this.addAsyncMatchers = function(matchersToAdd) { runableResources.addCustomAsyncMatchers(matchersToAdd); }; this.addCustomObjectFormatter = function(formatter) { runableResources.customObjectFormatters().push(formatter); }; j$.Expectation.addCoreMatchers(j$.matchers); j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); const expectationFactory = function(actual, spec) { return j$.Expectation.factory({ matchersUtil: runableResources.makeMatchersUtil(), customMatchers: runableResources.customMatchers(), actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { return spec.addExpectationResult(passed, result); } }; const handleThrowUnlessFailure = function(passed, result) { if (!passed) { /** * @interface * @name ThrowUnlessFailure * @extends Error * @description Represents a failure of an expectation evaluated with * {@link throwUnless}. Properties of this error are a subset of the * properties of {@link ExpectationResult} and have the same values. * * Note: The expected and actual properties are deprecated and may be removed * in a future release. In many Jasmine configurations they are passed * through JSON serialization and deserialization, which is inherently * lossy. In such cases, the expected and actual values may be placeholders * or approximations of the original objects. * * @property {String} matcherName - The name of the matcher that was executed for this expectation. * @property {String} message - The failure message for the expectation. * @property {Boolean} passed - Whether the expectation passed or failed. * @property {Object} expected - Deprecated. If the expectation failed, what was the expected value. * @property {Object} actual - Deprecated. If the expectation failed, what actual value was produced. */ const error = new Error(result.message); error.passed = result.passed; error.message = result.message; error.expected = result.expected; error.actual = result.actual; error.matcherName = result.matcherName; throw error; } }; const throwUnlessFactory = function(actual, spec) { return j$.Expectation.factory({ matchersUtil: runableResources.makeMatchersUtil(), customMatchers: runableResources.customMatchers(), actual: actual, addExpectationResult: handleThrowUnlessFailure }); }; const throwUnlessAsyncFactory = function(actual, spec) { return j$.Expectation.asyncFactory({ matchersUtil: runableResources.makeMatchersUtil(), customAsyncMatchers: runableResources.customAsyncMatchers(), actual: actual, addExpectationResult: handleThrowUnlessFailure }); }; // TODO: Unify recordLateError with recordLateExpectation? The extra // diagnostic info added by the latter is probably useful in most cases. function recordLateError(error) { const isExpectationResult = error.matcherName !== undefined && error.passed !== undefined; const result = isExpectationResult ? error : j$.buildExpectationResult({ error, passed: false, matcherName: '', expected: '', actual: '' }); routeLateFailure(result); } function recordLateExpectation(runable, runableType, result) { const delayedExpectationResult = {}; Object.keys(result).forEach(function(k) { delayedExpectationResult[k] = result[k]; }); delayedExpectationResult.passed = false; delayedExpectationResult.globalErrorType = 'lateExpectation'; delayedExpectationResult.message = runableType + ' "' + runable.getFullName() + '" ran a "' + result.matcherName + '" expectation after it finished.\n'; if (result.message) { delayedExpectationResult.message += 'Message: "' + result.message + '"\n'; } delayedExpectationResult.message += '1. Did you forget to return or await the result of expectAsync?\n' + '2. Was done() invoked before an async operation completed?\n' + '3. Did an expectation follow a call to done()?'; topSuite.result.failedExpectations.push(delayedExpectationResult); } function routeLateFailure(expectationResult) { // Report the result on the nearest ancestor suite that hasn't already // been reported done. for (let r = runner.currentRunable(); r; r = r.parentSuite) { if (!r.reportedDone) { if (r === topSuite) { expectationResult.globalErrorType = 'lateError'; } r.result.failedExpectations.push(expectationResult); return; } } // If we get here, all results have been reported and there's nothing we // can do except log the result and hope the user sees it. console.error('Jasmine received a result after the suite finished:'); console.error(expectationResult); } const asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ matchersUtil: runableResources.makeMatchersUtil(), customAsyncMatchers: runableResources.customAsyncMatchers(), actual: actual, addExpectationResult: addExpectationResult }); function addExpectationResult(passed, result) { if (runner.currentRunable() !== spec) { recordLateExpectation(spec, runableType, result); } return spec.addExpectationResult(passed, result); } }; /** * Causes a deprecation warning to be logged to the console and reported to * reporters. * * The optional second parameter is an object that can have either of the * following properties: * * omitStackTrace: Whether to omit the stack trace. Optional. Defaults to * false. This option is ignored if the deprecation is an Error. Set this * when the stack trace will not contain anything that helps the user find * the source of the deprecation. * * ignoreRunnable: Whether to log the deprecation on the root suite, ignoring * the spec or suite that's running when it happens. Optional. Defaults to * false. * * @name Env#deprecated * @since 2.99 * @function * @param {String|Error} deprecation The deprecation message * @param {Object} [options] Optional extra options, as described above */ this.deprecated = function(deprecation, options) { const runable = runner.currentRunable() || top