UNPKG

jasmine-core

Version:

Simple JavaScript testing framework for browsers and node.js

1,584 lines (1,407 loc) 341 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$.AllOf = jRequire.AllOf(j$); 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$.Configuration = jRequire.Configuration(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$.CurrentRunableTracker = jRequire.CurrentRunableTracker(); 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$); j$.TreeRunner = jRequire.TreeRunner(j$); 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$); // zone.js tries to monkey patch GlobalErrors in a way that is either a // no-op or causes Jasmine to crash, depending on whether it's done before // or after env creation. Prevent that. const GlobalErrors = jRequire.GlobalErrors(j$); Object.defineProperty(j$, 'GlobalErrors', { enumerable: true, configurable: false, get() { return GlobalErrors; }, set() {} }); 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 value !== undefined && 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 matches every provided equality tester. * @name asymmetricEqualityTesters.allOf * @emittedName jasmine.allOf * @since 5.13.0 * @function * @param {...*} arguments - The asymmetric equality checkers to compare. */ j$.allOf = function() { return new j$.AllOf(...arguments); }; /** * 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.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); }; function callerFile() { const trace = new j$.StackTrace(new Error()); return trace.frames[1].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$) { class Spec { #autoCleanClosures; #throwOnExpectationFailure; #timer; #metadata; constructor(attrs) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; 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.getPath = function() { return attrs.getPath ? attrs.getPath(this) : []; }; this.#autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; this.onLateError = attrs.onLateError || function() {}; this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; this.#timer = attrs.timer || new j$.Timer(); if (!this.queueableFn.fn) { this.exclude(); } this.reset(); } addExpectationResult(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(); } } } getSpecProperty(key) { this.result.properties = this.result.properties || {}; return this.result.properties[key]; } setSpecProperty(key, value) { this.result.properties = this.result.properties || {}; this.result.properties[key] = value; } executionStarted() { this.#timer.start(); } executionFinished(excluded, failSpecWithNoExp) { 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; } } reset() { /** * @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. * Note: The value may be incorrect if zone.js is installed or * `it`/`fit`/`xit` have been replaced with versions that don't maintain the * same call stack height as the originals. You can fix that by setting * {@link Configuration#extraItStackFrames}. * @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; } 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 ); } pend(message) { this.markedPending = true; if (message) { this.result.pendingReason = message; } } // Like pend(), but pending state will survive reset(). // Useful for fit, xit, where pending state remains. exclude(message) { this.markedExcluding = true; if (this.message) { this.excludeMessage = message; } this.pend(message); } // TODO: ensure that all access to result goes through .getResult() // so that the status is correct. getResult() { this.result.status = this.#status(); return this.result; } #status(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'; } getFullName() { return this.getPath().join(' '); } addDeprecationWarning(deprecation) { if (typeof deprecation === 'string') { deprecation = { message: deprecation }; } this.result.deprecationWarnings.push( j$.buildExpectationResult(deprecation) ); } debugLog(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() }); } /** * @interface Spec * @see Configuration#specFilter * @since 2.0.0 */ get metadata() { // NOTE: Although most of jasmine-core only exposes these metadata objects, // actual Spec instances are still passed to Configuration#specFilter. Until // that is fixed, it's important to make sure that all metadata properties // also exist in compatible form on the underlying Spec. 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), /** * The full path of the spec, as an array of names. * @name Spec#getPath * @function * @returns {Array.<string>} * @since 5.7.0 */ getPath: this.getPath.bind(this), /** * The name of the file the spec was defined in. * Note: The value may be incorrect if zone.js is installed or * `it`/`fit`/`xit` have been replaced with versions that don't maintain the * same call stack height as the originals. You can fix that by setting * {@link Configuration#extraItStackFrames}. * @name Spec#filename * @readonly * @type {string} * @since 5.13.0 */ filename: this.filename }; } return this.#metadata; } } 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 ); }; 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$) { const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3; /** * @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(envOptions) { envOptions = envOptions || {}; const self = this; const GlobalErrors = envOptions.GlobalErrors || j$.GlobalErrors; const global = envOptions.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 GlobalErrors( undefined, // Configuration is late-bound because GlobalErrors needs to be constructed // before it's set to detect load-time errors in browsers () => this.configuration() ); const { installGlobalErrors, uninstallGlobalErrors } = (function() { let installed = false; return { installGlobalErrors() { if (!installed) { globalErrors.install(); installed = true; } }, uninstallGlobalErrors() { if (installed) { globalErrors.uninstall(); installed = false; } } }; })(); const runableResources = new j$.RunableResources({ getCurrentRunableId: function() { const r = runner.currentRunable(); return r ? r.id : null; }, globalErrors }); let reportDispatcher; let topSuite; let runner; let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel const config = new j$.Configuration(); if (!envOptions.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(changes) { if (parallelLoadingState) { throw new Error( 'Jasmine cannot be configured via Env in parallel mode' ); } config.update(changes); 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() { return config.copy(); }; 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. // eslint-disable-next-line no-console console.error('Jasmine received a result after the suite finished:'); // eslint-disable-next-line no-console 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() || topSuite; deprecator.addDeprecationWarning(runable, deprecation, options); }; function runQueue(options) { options.clearStack = options.clearStack || clearStack; options.timeout = { setTimeout: realSetTimeout, clearTimeout: realClearTimeout }; options.fail = self.fail; options.globalErrors = globalErrors; options.onException = options.onException || function(e) { (runner.currentRunable() || topSuite).handleException(e); }; new j$.QueueRunner(options).execute(); } const suiteBuilder = new j$.SuiteBuilder({ env: this, expectationFactory, asyncExpectationFactory, onLateError: recordLateError, runQueue }); topSuite = suiteBuilder.topSuite; const deprecator = new j$.Deprecator(topSuite); /** * Provides the root suite, through which all suites and specs can be * accessed. * @function * @name Env#topSuite * @return {Suite} the root suite * @since 2.0.0 */ this.topSuite = function() { ensureNonParallel('topSuite'); return topSuite.metadata; }; /** * This represents the available reporter callback for an object passed to {@link Env#addReporter}. * @interface Reporter * @see custom_reporter */ reportDispatcher = new j$.ReportDispatcher( j$.reporterEvents, function(options) { options.SkipPolicy = j$.NeverSkipPolicy; return runQueue(options); }, recordLateError ); runner = new j$.Runner({ topSuite, totalSpecsDefined: () => suiteBuilder.totalSpecsDefined, focusedRunables: () => suiteBuilder.focusedRunables, runableResources, reportDispatcher, runQueue, TreeProcessor: j$.TreeProcessor, globalErrors, getConfig: () => config }); this.setParallelLoadingState = function(state) { parallelLoadingState = state; }; this.parallelReset = function() { suiteBuilder.parallelReset(); runner.parallelReset(); }; /** * Executes the specs. * * If called with no parameter or with a falsy parameter, * all specs will be executed except those that are excluded by a * [spec filter]{@link Configuration#specFilter} or other mechanism. If the * parameter is a list of spec/suite IDs, only those specs/suites will * be run. * * execute should not be called more than once unless the env has been * configured with `{autoCleanClosures: false}`. * * execute returns a promise. The promise will be resolved to the same * {@link JasmineDoneInfo|overall result} that's passed to a reporter's * `jasmineDone` method, even if the suite did not pass. To determine * whether the suite passed, check the value that the promise resolves to * or use a {@link Reporter}. The promise will be rejected in the case of * certain serious errors that prevent execution from starting. * * @name Env#execute * @since 2.0.0 * @function * @async * @param {(string[])=} runablesToRun IDs of suites and/or specs to run * @return {Promise<JasmineDoneInfo>} */ this.execute = async function(runablesToRun) { installGlobalErrors(); if (parallelLoadingState) { validateConfigForParallel(); } const result = await runner.execute(runablesToRun); this.cleanup_(); return result; }; /** * Add a custom reporter to the Jasmine environment. * @name Env#addReporter * @since 2.0.0 * @function * @param {Reporter} reporterTo