diff --git a/src/static/tests/LICENSE b/src/static/tests/LICENSE
new file mode 100644
index 0000000..3bbb511
--- /dev/null
+++ b/src/static/tests/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2008-2019 Pivotal Labs
+Copyright (c) 2008-2026 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.
diff --git a/src/static/tests/Spec.js b/src/static/tests/Spec.js
new file mode 100644
index 0000000..63624c6
--- /dev/null
+++ b/src/static/tests/Spec.js
@@ -0,0 +1,57 @@
+console.log("Spec.js is loading");
+
+describe("GameArray JavaScript", () => {
+ const inputId= "id_text";
+ const errorClass = "invalid-feedback";
+ const inputSelector = `#${inputId}`;
+ const errorSelector = `.${errorClass}`;
+ let testDiv;
+ let textInput;
+ let errorMsg;
+
+ beforeEach(() => {
+ console.log("beforeEach");
+ testDiv = document.createElement("div");
+ testDiv.innerHTML = `
+
+ `;
+ document.body.appendChild(testDiv);
+ textInput = document.querySelector(inputSelector);
+ errorMsg = document.querySelector(errorSelector);
+ });
+
+ afterEach(() => {
+ testDiv.remove();
+ });
+
+ it("should have a useful html fixture", () => {
+ console.log("in test 1");
+ expect(errorMsg.checkVisibility()).toBe(true);
+ });
+
+ it("should hide error message on input", () => {
+ console.log("in test 2");
+ initialize(inputSelector);
+ textInput.dispatchEvent(new InputEvent("input"));
+
+ expect(errorMsg.checkVisibility()).toBe(false);
+ });
+
+ it("should not hide error message before event is fired", () => {
+ console.log("in test 3");
+ initialize(inputSelector);
+
+ expect(errorMsg.checkVisibility()).toBe(true);
+ });
+});
\ No newline at end of file
diff --git a/src/static/tests/SpecRunner.html b/src/static/tests/SpecRunner.html
new file mode 100644
index 0000000..4f947e7
--- /dev/null
+++ b/src/static/tests/SpecRunner.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Jasmine Spec Runner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/static/tests/lib/jasmine-6.0.1/boot0.js b/src/static/tests/lib/jasmine-6.0.1/boot0.js
new file mode 100644
index 0000000..5a97826
--- /dev/null
+++ b/src/static/tests/lib/jasmine-6.0.1/boot0.js
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2008-2019 Pivotal Labs
+Copyright (c) 2008-2026 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.
+*/
+
+'use strict';
+
+/**
+ This file starts the process of "booting" Jasmine. It initializes Jasmine,
+ makes its globals available, and creates the env. This file should be loaded
+ after `jasmine.js` and `jasmine_html.js`, but before `boot1.js` or any project
+ source files or spec files are loaded.
+ */
+(function() {
+ const jasmineRequire = window.jasmineRequire || require('./jasmine.js');
+
+ /**
+ * ## Require & Instantiate
+ *
+ * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
+ */
+ const jasmine = jasmineRequire.core(jasmineRequire),
+ global = jasmine.getGlobal();
+ global.jasmine = jasmine;
+
+ /**
+ * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
+ */
+ jasmineRequire.html(jasmine);
+
+ /**
+ * Create the Jasmine environment. This is used to run all specs in a project.
+ */
+ const env = jasmine.getEnv();
+
+ /**
+ * ## The Global Interface
+ *
+ * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
+ */
+ const jasmineInterface = jasmineRequire.interface(jasmine, env);
+
+ /**
+ * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
+ */
+ for (const property in jasmineInterface) {
+ global[property] = jasmineInterface[property];
+ }
+})();
diff --git a/src/static/tests/lib/jasmine-6.0.1/boot1.js b/src/static/tests/lib/jasmine-6.0.1/boot1.js
new file mode 100644
index 0000000..ebcb5bb
--- /dev/null
+++ b/src/static/tests/lib/jasmine-6.0.1/boot1.js
@@ -0,0 +1,64 @@
+/*
+Copyright (c) 2008-2019 Pivotal Labs
+Copyright (c) 2008-2026 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.
+*/
+
+'use strict';
+
+/**
+ This file finishes 'booting' Jasmine, performing all of the necessary
+ initialization before executing the loaded environment and all of a project's
+ specs. This file should be loaded after `boot0.js` but before any project
+ source files or spec files are loaded. Thus this file can also be used to
+ customize Jasmine for a project.
+
+ If a project is using Jasmine via the standalone distribution, this file can
+ be customized directly. If you only wish to configure the Jasmine env, you
+ can load another file that calls `jasmine.getEnv().configure({...})`
+ after `boot0.js` is loaded and before this file is loaded.
+ */
+
+(function() {
+ const env = jasmine.getEnv();
+ const urls = new jasmine.HtmlReporterV2Urls();
+
+ /**
+ * Configures Jasmine based on the current set of query parameters. This
+ * supports all parameters set by the HTML reporter as well as
+ * spec=partialPath, which filters out specs whose paths don't contain the
+ * parameter.
+ */
+ env.configure(urls.configFromCurrentUrl());
+
+ const currentWindowOnload = window.onload;
+ window.onload = function() {
+ if (currentWindowOnload) {
+ currentWindowOnload();
+ }
+
+ // The HTML reporter needs to be set up here so it can access the DOM. Other
+ // reporters can be added at any time before env.execute() is called.
+ const htmlReporter = new jasmine.HtmlReporterV2({ env, urls });
+ env.addReporter(htmlReporter);
+ env.execute();
+ };
+})();
diff --git a/src/static/tests/lib/jasmine-6.0.1/jasmine-html.js b/src/static/tests/lib/jasmine-6.0.1/jasmine-html.js
new file mode 100644
index 0000000..c3c92d5
--- /dev/null
+++ b/src/static/tests/lib/jasmine-6.0.1/jasmine-html.js
@@ -0,0 +1,1863 @@
+/*
+Copyright (c) 2008-2019 Pivotal Labs
+Copyright (c) 2008-2026 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-var
+var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
+
+jasmineRequire.html = function(j$) {
+ j$.private.ResultsNode = jasmineRequire.ResultsNode();
+ j$.private.ResultsStateBuilder = jasmineRequire.ResultsStateBuilder(j$);
+ j$.private.htmlReporterUtils = jasmineRequire.htmlReporterUtils(j$);
+ j$.private.AlertsView = jasmineRequire.AlertsView(j$);
+ j$.private.OverallStatusBar = jasmineRequire.OverallStatusBar(j$);
+ j$.private.Banner = jasmineRequire.Banner(j$);
+ j$.private.SymbolsView = jasmineRequire.SymbolsView(j$);
+ j$.private.SummaryTreeView = jasmineRequire.SummaryTreeView(j$);
+ j$.private.FailuresView = jasmineRequire.FailuresView(j$);
+ j$.private.PerformanceView = jasmineRequire.PerformanceView(j$);
+ j$.private.TabBar = jasmineRequire.TabBar(j$);
+ j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
+ j$.HtmlReporterV2Urls = jasmineRequire.HtmlReporterV2Urls(j$);
+ j$.HtmlReporterV2 = jasmineRequire.HtmlReporterV2(j$);
+ j$.QueryString = jasmineRequire.QueryString();
+ j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(j$);
+ j$.private.HtmlSpecFilterV2 = jasmineRequire.HtmlSpecFilterV2();
+};
+
+jasmineRequire.HtmlReporter = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ /**
+ * @class HtmlReporter
+ * @classdesc Displays results and allows re-running individual specs and suites.
+ * @implements {Reporter}
+ * @param options Options object. See lib/jasmine-core/boot1.js for details.
+ * @since 1.2.0
+ * @deprecated Use {@link HtmlReporterV2} instead.
+ */
+ class HtmlReporter {
+ #env;
+ #getContainer;
+ #navigateWithNewParam;
+ #urlBuilder;
+ #filterSpecs;
+ #stateBuilder;
+ #config;
+ #htmlReporterMain;
+
+ // Sub-views
+ #alerts;
+ #symbols;
+ #banner;
+ #failures;
+
+ constructor(options) {
+ this.#env = options.env;
+
+ this.#getContainer = options.getContainer;
+ this.#navigateWithNewParam =
+ options.navigateWithNewParam || function() {};
+ this.#urlBuilder = new UrlBuilder(
+ options.addToExistingQueryString || defaultQueryString
+ );
+ this.#filterSpecs = options.filterSpecs;
+ }
+
+ /**
+ * Initializes the reporter. Should be called before {@link Env#execute}.
+ * @function
+ * @name HtmlReporter#initialize
+ */
+ initialize() {
+ this.#env.deprecated(
+ 'HtmlReporter and HtmlSpecFilter are deprecated. Use HtmlReporterV2 instead.'
+ );
+ this.#clearPrior();
+ this.#config = this.#env ? this.#env.configuration() : {};
+
+ this.#stateBuilder = new j$.private.ResultsStateBuilder();
+
+ this.#alerts = new j$.private.AlertsView(this.#urlBuilder);
+ this.#symbols = new j$.private.SymbolsView();
+ this.#banner = new j$.private.Banner(this.#navigateWithNewParam);
+ this.#failures = new j$.private.FailuresView(this.#urlBuilder);
+ this.#htmlReporterMain = createDom(
+ 'div',
+ { className: 'jasmine_html-reporter' },
+ this.#banner.rootEl,
+ this.#symbols.rootEl,
+ this.#alerts.rootEl,
+ this.#failures.rootEl
+ );
+ this.#getContainer().appendChild(this.#htmlReporterMain);
+ }
+
+ jasmineStarted(options) {
+ this.#stateBuilder.jasmineStarted(options);
+ }
+
+ suiteStarted(result) {
+ this.#stateBuilder.suiteStarted(result);
+ }
+
+ suiteDone(result) {
+ this.#stateBuilder.suiteDone(result);
+
+ if (result.status === 'failed') {
+ this.#failures.append(result, this.#stateBuilder.currentParent);
+ }
+ }
+
+ specStarted() {}
+
+ specDone(result) {
+ this.#stateBuilder.specDone(result);
+ this.#symbols.append(result, this.#config);
+
+ if (noExpectations(result)) {
+ const noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
+ if (result.status === 'failed') {
+ // eslint-disable-next-line no-console
+ console.error(noSpecMsg);
+ } else {
+ // eslint-disable-next-line no-console
+ console.warn(noSpecMsg);
+ }
+ }
+
+ if (result.status === 'failed') {
+ this.#failures.append(result, this.#stateBuilder.currentParent);
+ }
+ }
+
+ jasmineDone(doneResult) {
+ this.#stateBuilder.jasmineDone(doneResult);
+ this.#banner.showOptionsMenu(this.#config);
+
+ if (
+ this.#stateBuilder.specsExecuted < this.#stateBuilder.totalSpecsDefined
+ ) {
+ this.#alerts.addSkipped(
+ this.#stateBuilder.specsExecuted,
+ this.#stateBuilder.totalSpecsDefined
+ );
+ }
+
+ const statusBar = new j$.private.OverallStatusBar(this.#urlBuilder);
+ statusBar.showDone(doneResult, this.#stateBuilder);
+ this.#alerts.addBar(statusBar.rootEl);
+
+ if (doneResult.failedExpectations) {
+ for (const f of doneResult.failedExpectations) {
+ this.#alerts.addGlobalFailure(f);
+ }
+ }
+
+ for (const dw of this.#stateBuilder.deprecationWarnings) {
+ this.#alerts.addDeprecationWarning(dw);
+ }
+
+ const results = this.#find('.jasmine-results');
+ const summary = new j$.private.SummaryTreeView(
+ this.#urlBuilder,
+ this.#filterSpecs
+ );
+ summary.addResults(this.#stateBuilder.topResults);
+ results.appendChild(summary.rootEl);
+
+ if (this.#stateBuilder.anyNonTopSuiteFailures) {
+ this.#addFailureToggle();
+ this.#setMenuModeTo('jasmine-failure-list');
+ this.#failures.show();
+ }
+ }
+
+ #addFailureToggle() {
+ const onClickFailures = () => this.#setMenuModeTo('jasmine-failure-list');
+ const onClickSpecList = () => this.#setMenuModeTo('jasmine-spec-list');
+ const failuresLink = createDom(
+ 'a',
+ { className: 'jasmine-failures-menu', href: '#' },
+ 'Failures'
+ );
+ let specListLink = createDom(
+ 'a',
+ { className: 'jasmine-spec-list-menu', href: '#' },
+ 'Spec List'
+ );
+
+ failuresLink.onclick = function() {
+ onClickFailures();
+ return false;
+ };
+
+ specListLink.onclick = function() {
+ onClickSpecList();
+ return false;
+ };
+
+ this.#alerts.addBar(
+ createDom(
+ 'span',
+ { className: 'jasmine-menu jasmine-bar jasmine-spec-list' },
+ [createDom('span', {}, 'Spec List | '), failuresLink]
+ )
+ );
+ this.#alerts.addBar(
+ createDom(
+ 'span',
+ { className: 'jasmine-menu jasmine-bar jasmine-failure-list' },
+ [specListLink, createDom('span', {}, ' | Failures ')]
+ )
+ );
+ }
+
+ #find(selector) {
+ return this.#getContainer().querySelector(
+ '.jasmine_html-reporter ' + selector
+ );
+ }
+
+ #clearPrior() {
+ const oldReporter = this.#find('');
+
+ if (oldReporter) {
+ this.#getContainer().removeChild(oldReporter);
+ }
+ }
+
+ #setMenuModeTo(mode) {
+ this.#htmlReporterMain.setAttribute(
+ 'class',
+ 'jasmine_html-reporter ' + mode
+ );
+ }
+ }
+
+ class UrlBuilder {
+ #addToExistingQueryString;
+
+ constructor(addToExistingQueryString) {
+ this.#addToExistingQueryString = function(k, v) {
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') + addToExistingQueryString(k, v)
+ );
+ };
+ }
+
+ suiteHref(suite) {
+ const els = [];
+
+ while (suite && suite.parent) {
+ els.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return this.#addToExistingQueryString('spec', els.join(' '));
+ }
+
+ specHref(result) {
+ return this.#addToExistingQueryString('spec', result.fullName);
+ }
+
+ runAllHref() {
+ return this.#addToExistingQueryString('spec', '');
+ }
+
+ seedHref(seed) {
+ return this.#addToExistingQueryString('seed', seed);
+ }
+ }
+
+ function defaultQueryString(key, value) {
+ return '?' + key + '=' + value;
+ }
+
+ return HtmlReporter;
+};
+
+jasmineRequire.HtmlSpecFilter = function(j$) {
+ 'use strict';
+
+ /**
+ * @class HtmlSpecFilter
+ * @param options Options object. See lib/jasmine-core/boot1.js for details.
+ * @deprecated Use {@link HtmlReporterV2Urls} instead.
+ */
+ function HtmlSpecFilter(options) {
+ const env = options?.env ?? j$.getEnv();
+ env.deprecated(
+ 'HtmlReporter and HtmlSpecFilter are deprecated. Use HtmlReporterV2 instead.'
+ );
+
+ const filterString =
+ options &&
+ options.filterString &&
+ options.filterString() &&
+ options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ const filterPattern = new RegExp(filterString);
+
+ /**
+ * Determines whether the spec with the specified name should be executed.
+ * @name HtmlSpecFilter#matches
+ * @function
+ * @param {string} specName The full name of the spec
+ * @returns {boolean}
+ */
+ this.matches = function(specName) {
+ return filterPattern.test(specName);
+ };
+ }
+
+ return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsNode = function() {
+ 'use strict';
+
+ function ResultsNode(result, type, parent) {
+ this.result = result;
+ this.type = type;
+ this.parent = parent;
+
+ this.children = [];
+
+ this.addChild = function(result, type) {
+ this.children.push(new ResultsNode(result, type, this));
+ };
+
+ this.last = function() {
+ return this.children[this.children.length - 1];
+ };
+
+ this.updateResult = function(result) {
+ this.result = result;
+ };
+ }
+
+ return ResultsNode;
+};
+
+jasmineRequire.QueryString = function() {
+ 'use strict';
+
+ /**
+ * Reads and manipulates the query string.
+ * @since 2.0.0
+ */
+ class QueryString {
+ #getWindowLocation;
+
+ /**
+ * @param options Object with a getWindowLocation property, which should be
+ * a function returning the current value of window.location.
+ */
+ constructor(options) {
+ this.#getWindowLocation = options.getWindowLocation;
+ }
+
+ /**
+ * Sets the specified query parameter and navigates to the resulting URL.
+ * @param {string} key
+ * @param {string} value
+ */
+ navigateWithNewParam(key, value) {
+ this.#getWindowLocation().search = this.fullStringWithNewParam(
+ key,
+ value
+ );
+ }
+
+ /**
+ * Returns a new URL based on the current location, with the specified
+ * query parameter set.
+ * @param {string} key
+ * @param {string} value
+ * @return {string}
+ */
+ fullStringWithNewParam(key, value) {
+ const paramMap = this.#queryStringToParamMap();
+ paramMap[key] = value;
+ return toQueryString(paramMap);
+ }
+
+ /**
+ * Gets the value of the specified query parameter.
+ * @param {string} key
+ * @return {string}
+ */
+ getParam(key) {
+ return this.#queryStringToParamMap()[key];
+ }
+
+ #queryStringToParamMap() {
+ const paramStr = this.#getWindowLocation().search.substring(1);
+ let params = [];
+ const paramMap = {};
+
+ if (paramStr.length > 0) {
+ params = paramStr.split('&');
+ for (let i = 0; i < params.length; i++) {
+ const p = params[i].split('=');
+ let value = decodeURIComponent(p[1]);
+ if (value === 'true' || value === 'false') {
+ value = JSON.parse(value);
+ }
+ paramMap[decodeURIComponent(p[0])] = value;
+ }
+ }
+
+ return paramMap;
+ }
+ }
+
+ function toQueryString(paramMap) {
+ const qStrPairs = [];
+ for (const prop in paramMap) {
+ qStrPairs.push(
+ encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
+ );
+ }
+ return '?' + qStrPairs.join('&');
+ }
+
+ return QueryString;
+};
+
+jasmineRequire.AlertsView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+ const errorBarClassName = 'jasmine-bar jasmine-errored';
+ const afterAllMessagePrefix = 'AfterAll ';
+
+ class AlertsView {
+ #urlBuilder;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.rootEl = createDom('div', { className: 'jasmine-alert' });
+ }
+
+ addSkipped(numExecuted, numDefined) {
+ this.#createAndAdd(
+ 'jasmine-bar jasmine-skipped',
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.runAllHref(), title: 'Run all specs' },
+ `Ran ${numExecuted} of ${numDefined} specs - run all`
+ )
+ );
+ }
+
+ addGlobalFailure(failure) {
+ this.#createAndAdd(
+ errorBarClassName,
+ this.#globalFailureMessage(failure)
+ );
+ }
+
+ #globalFailureMessage(failure) {
+ if (failure.globalErrorType === 'load') {
+ const prefix = 'Error during loading: ' + failure.message;
+
+ if (failure.filename) {
+ return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
+ } else {
+ return prefix;
+ }
+ } else if (failure.globalErrorType === 'afterAll') {
+ return afterAllMessagePrefix + failure.message;
+ } else {
+ return failure.message;
+ }
+ }
+
+ addDeprecationWarning(dw) {
+ const children = [];
+ let context;
+
+ switch (dw.runnableType) {
+ case 'spec':
+ context = '(in spec: ' + dw.runnableName + ')';
+ break;
+ case 'suite':
+ context = '(in suite: ' + dw.runnableName + ')';
+ break;
+ default:
+ context = '';
+ }
+
+ for (const line of dw.message.split('\n')) {
+ children.push(line);
+ children.push(createDom('br'));
+ }
+
+ children[0] = 'DEPRECATION: ' + children[0];
+ children.push(context);
+
+ if (dw.stack) {
+ children.push(this.#createExpander(dw.stack));
+ }
+
+ this.#createAndAdd('jasmine-bar jasmine-warning', children);
+ }
+
+ addBar(el) {
+ this.rootEl.appendChild(el);
+ }
+
+ #createAndAdd(className, children) {
+ this.rootEl.appendChild(createDom('span', { className }, children));
+ }
+
+ #createExpander(stackTrace) {
+ const expandLink = createDom('a', { href: '#' }, 'Show stack trace');
+ const root = createDom(
+ 'div',
+ { className: 'jasmine-expander' },
+ expandLink,
+ createDom(
+ 'div',
+ { className: 'jasmine-expander-contents jasmine-stack-trace' },
+ stackTrace
+ )
+ );
+
+ expandLink.addEventListener('click', function(e) {
+ e.preventDefault();
+
+ if (root.classList.contains('jasmine-expanded')) {
+ root.classList.remove('jasmine-expanded');
+ expandLink.textContent = 'Show stack trace';
+ } else {
+ root.classList.add('jasmine-expanded');
+ expandLink.textContent = 'Hide stack trace';
+ }
+ });
+
+ return root;
+ }
+ }
+
+ return AlertsView;
+};
+
+jasmineRequire.Banner = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class Banner {
+ #navigateWithNewParam;
+ #omitHideDisabled;
+
+ constructor(navigateWithNewParam, omitHideDisabled) {
+ this.#navigateWithNewParam = navigateWithNewParam;
+ this.#omitHideDisabled = omitHideDisabled;
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-banner' },
+ createDom('a', {
+ className: 'jasmine-title',
+ href: 'http://jasmine.github.io/',
+ target: '_blank'
+ }),
+ createDom('span', { className: 'jasmine-version' }, j$.version)
+ );
+ }
+
+ showOptionsMenu(config) {
+ this.rootEl.appendChild(this.#optionsMenu(config));
+ }
+
+ #optionsMenu(config) {
+ const items = [
+ createDom(
+ 'div',
+ { className: 'jasmine-stop-on-failure' },
+ createDom('input', {
+ className: 'jasmine-fail-fast',
+ id: 'jasmine-fail-fast',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-fail-fast' },
+ 'stop execution on spec failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-throw-failures' },
+ createDom('input', {
+ className: 'jasmine-throw',
+ id: 'jasmine-throw-failures',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-throw-failures' },
+ 'stop spec on expectation failure'
+ )
+ ),
+ createDom(
+ 'div',
+ { className: 'jasmine-random-order' },
+ createDom('input', {
+ className: 'jasmine-random',
+ id: 'jasmine-random-order',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-random-order' },
+ 'run tests in random order'
+ )
+ )
+ ];
+
+ if (!this.#omitHideDisabled) {
+ items.push(
+ createDom(
+ 'div',
+ { className: 'jasmine-hide-disabled' },
+ createDom('input', {
+ className: 'jasmine-disabled',
+ id: 'jasmine-hide-disabled',
+ type: 'checkbox'
+ }),
+ createDom(
+ 'label',
+ { className: 'jasmine-label', for: 'jasmine-hide-disabled' },
+ 'hide disabled tests'
+ )
+ )
+ );
+ }
+
+ const optionsMenuDom = createDom(
+ 'div',
+ { className: 'jasmine-run-options' },
+ createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+ createDom('div', { className: 'jasmine-payload' }, items)
+ );
+
+ const failFastCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-fail-fast'
+ );
+ failFastCheckbox.checked = config.stopOnSpecFailure;
+ failFastCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopOnSpecFailure',
+ !config.stopOnSpecFailure
+ );
+ };
+
+ const throwCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-throw-failures'
+ );
+ throwCheckbox.checked = config.stopSpecOnExpectationFailure;
+ throwCheckbox.onclick = () => {
+ this.#navigateWithNewParam(
+ 'stopSpecOnExpectationFailure',
+ !config.stopSpecOnExpectationFailure
+ );
+ };
+
+ const randomCheckbox = optionsMenuDom.querySelector(
+ '#jasmine-random-order'
+ );
+ randomCheckbox.checked = config.random;
+ randomCheckbox.onclick = () => {
+ this.#navigateWithNewParam('random', !config.random);
+ };
+
+ if (!this.#omitHideDisabled) {
+ const hideDisabled = optionsMenuDom.querySelector(
+ '#jasmine-hide-disabled'
+ );
+ hideDisabled.checked = config.hideDisabled;
+ hideDisabled.onclick = () => {
+ this.#navigateWithNewParam('hideDisabled', !config.hideDisabled);
+ };
+ }
+
+ const optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
+ optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
+ isOpen = /\bjasmine-open\b/;
+
+ optionsTrigger.onclick = function() {
+ if (isOpen.test(optionsPayload.className)) {
+ optionsPayload.className = optionsPayload.className.replace(
+ isOpen,
+ ''
+ );
+ } else {
+ optionsPayload.className += ' jasmine-open';
+ }
+ };
+
+ return optionsMenuDom;
+ }
+ }
+
+ return Banner;
+};
+
+jasmineRequire.FailuresView = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+
+ class FailuresView {
+ #urlBuilder;
+ #failureEls;
+ #showing;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.#failureEls = [];
+ this.#showing = false;
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-results' },
+ createDom('div', { className: 'jasmine-failures' })
+ );
+ }
+
+ append(result, parent) {
+ // TODO: Figure out why the result is wrong if we build the DOM node later
+ const el = this.#makeFailureEl(result, parent);
+
+ if (this.#showing) {
+ this.rootEl.querySelector('.jasmine-failures').appendChild(el);
+ } else {
+ this.#failureEls.push(el);
+ }
+ }
+
+ show() {
+ const failureNode = this.rootEl.querySelector('.jasmine-failures');
+
+ for (const el of this.#failureEls) {
+ failureNode.appendChild(el);
+ }
+
+ this.#showing = true;
+ }
+
+ #makeFailureEl(result, parent) {
+ const failure = createDom(
+ 'div',
+ { className: 'jasmine-spec-detail jasmine-failed' },
+ this.#failureDescription(result, parent),
+ createDom('div', { className: 'jasmine-messages' })
+ );
+ const messages = failure.childNodes[1];
+
+ for (let i = 0; i < result.failedExpectations.length; i++) {
+ const expectation = result.failedExpectations[i];
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ expectation.message
+ )
+ );
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-stack-trace' },
+ expectation.stack
+ )
+ );
+ }
+
+ if (result.failedExpectations.length === 0) {
+ messages.appendChild(
+ createDom(
+ 'div',
+ { className: 'jasmine-result-message' },
+ 'Spec has no expectations'
+ )
+ );
+ }
+
+ if (result.debugLogs) {
+ messages.appendChild(this.#debugLogTable(result.debugLogs));
+ }
+
+ return failure;
+ }
+
+ #failureDescription(result, suite) {
+ const wrapper = createDom(
+ 'div',
+ { className: 'jasmine-description' },
+ createDom(
+ 'a',
+ {
+ title: result.description,
+ href: this.#urlBuilder.specHref(result)
+ },
+ result.description
+ )
+ );
+ let suiteLink;
+
+ while (suite && suite.parent) {
+ wrapper.insertBefore(
+ document.createTextNode(' > '),
+ wrapper.firstChild
+ );
+ suiteLink = createDom(
+ 'a',
+ { href: this.#urlBuilder.suiteHref(suite) },
+ suite.result.description
+ );
+ wrapper.insertBefore(suiteLink, wrapper.firstChild);
+
+ suite = suite.parent;
+ }
+
+ return wrapper;
+ }
+
+ #debugLogTable(debugLogs) {
+ const tbody = createDom('tbody');
+
+ for (const entry of debugLogs) {
+ tbody.appendChild(
+ createDom(
+ 'tr',
+ {},
+ createDom('td', {}, entry.timestamp.toString()),
+ createDom(
+ 'td',
+ { className: 'jasmine-debug-log-msg' },
+ entry.message
+ )
+ )
+ );
+ }
+
+ return createDom(
+ 'div',
+ { className: 'jasmine-debug-log' },
+ createDom(
+ 'div',
+ { className: 'jasmine-debug-log-header' },
+ 'Debug logs'
+ ),
+ createDom(
+ 'table',
+ {},
+ createDom(
+ 'thead',
+ {},
+ createDom(
+ 'tr',
+ {},
+ createDom('th', {}, 'Time (ms)'),
+ createDom('th', {}, 'Message')
+ )
+ ),
+ tbody
+ )
+ );
+ }
+ }
+
+ return FailuresView;
+};
+
+jasmineRequire.htmlReporterUtils = function(j$) {
+ 'use strict';
+
+ function createDom(type, attrs, childrenArrayOrVarArgs) {
+ const el = document.createElement(type);
+ let children;
+
+ if (Array.isArray(childrenArrayOrVarArgs)) {
+ children = childrenArrayOrVarArgs;
+ } else {
+ children = [];
+
+ for (let i = 2; i < arguments.length; i++) {
+ children.push(arguments[i]);
+ }
+ }
+
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (const attr in attrs) {
+ if (attr === 'className') {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+ }
+
+ function noExpectations(result) {
+ const allExpectations =
+ result.failedExpectations.length + result.passedExpectations.length;
+
+ return (
+ allExpectations === 0 &&
+ (result.status === 'passed' || result.status === 'failed')
+ );
+ }
+
+ return { createDom, noExpectations };
+};
+
+jasmineRequire.HtmlReporterV2 = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ const specListTabId = 'jasmine-specListTab';
+ const failuresTabId = 'jasmine-failuresTab';
+ const perfTabId = 'jasmine-perfTab';
+
+ /**
+ * @class HtmlReporterV2
+ * @classdesc Displays results and allows re-running individual specs and suites.
+ * @implements {Reporter}
+ * @param options Options object
+ * @since 6.0.0
+ * @example
+ * const env = jasmine.getEnv();
+ * const urls = new jasmine.HtmlReporterV2Urls();
+ * const reporter = new jasmine.HtmlReporterV2({
+ * env,
+ * urls,
+ * // container is optional and defaults to document.body.
+ * container: someElement
+ * });
+ */
+ class HtmlReporterV2 {
+ #container;
+ #queryString;
+ #urlBuilder;
+ #filterSpecs;
+ #stateBuilder;
+ #config;
+ #htmlReporterMain;
+
+ // Sub-views
+ #alerts;
+ #statusBar;
+ #tabBar;
+ #progress;
+ #banner;
+ #failures;
+
+ constructor(options) {
+ this.#container = options.container || document.body;
+ this.#queryString =
+ options.queryString ||
+ new j$.QueryString({
+ getWindowLocation() {
+ return window.location;
+ }
+ });
+ this.#urlBuilder = new UrlBuilder({
+ queryString: this.#queryString,
+ getSuiteById: id => this.#stateBuilder.suitesById[id]
+ });
+ this.#filterSpecs = options.urls.filteringSpecs();
+
+ this.#config = options.env ? options.env.configuration() : {};
+
+ this.#stateBuilder = new j$.private.ResultsStateBuilder();
+
+ this.#alerts = new j$.private.AlertsView(this.#urlBuilder);
+ this.#statusBar = new j$.private.OverallStatusBar(this.#urlBuilder);
+ this.#statusBar.showRunning();
+ this.#alerts.addBar(this.#statusBar.rootEl);
+
+ this.#tabBar = new j$.private.TabBar(
+ [
+ { id: specListTabId, label: 'Spec List' },
+ { id: failuresTabId, label: 'Failures' },
+ { id: perfTabId, label: 'Performance' }
+ ],
+ tabId => {
+ if (tabId === specListTabId) {
+ this.#setMenuModeTo('jasmine-spec-list');
+ } else if (tabId === failuresTabId) {
+ this.#setMenuModeTo('jasmine-failure-list');
+ } else {
+ this.#setMenuModeTo('jasmine-performance');
+ }
+ }
+ );
+ this.#alerts.addBar(this.#tabBar.rootEl);
+
+ this.#progress = new ProgressView();
+ this.#banner = new j$.private.Banner(
+ this.#queryString.navigateWithNewParam.bind(this.#queryString),
+ true
+ );
+ this.#failures = new j$.private.FailuresView(this.#urlBuilder);
+ this.#htmlReporterMain = createDom(
+ 'div',
+ { className: 'jasmine_html-reporter' },
+ this.#banner.rootEl,
+ this.#progress.rootEl,
+ this.#alerts.rootEl,
+ this.#failures.rootEl
+ );
+ this.#container.appendChild(this.#htmlReporterMain);
+ this.#failures.show();
+ }
+
+ jasmineStarted(options) {
+ this.#stateBuilder.jasmineStarted(options);
+ this.#progress.start(
+ options.totalSpecsDefined - options.numExcludedSpecs
+ );
+ }
+
+ suiteStarted(result) {
+ this.#stateBuilder.suiteStarted(result);
+ }
+
+ suiteDone(result) {
+ this.#stateBuilder.suiteDone(result);
+
+ if (result.status === 'failed') {
+ this.#failures.append(result, this.#stateBuilder.currentParent);
+ this.#statusBar.showFailing();
+ }
+ }
+
+ specDone(result) {
+ this.#stateBuilder.specDone(result);
+ this.#progress.specDone(result, this.#config);
+
+ if (noExpectations(result)) {
+ const noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
+ if (result.status === 'failed') {
+ // eslint-disable-next-line no-console
+ console.error(noSpecMsg);
+ } else {
+ // eslint-disable-next-line no-console
+ console.warn(noSpecMsg);
+ }
+ }
+
+ if (result.status === 'failed') {
+ this.#failures.append(result, this.#stateBuilder.currentParent);
+ this.#statusBar.showFailing();
+ }
+ }
+
+ jasmineDone(doneResult) {
+ this.#stateBuilder.jasmineDone(doneResult);
+ this.#progress.rootEl.style.visibility = 'hidden';
+ this.#banner.showOptionsMenu(this.#config);
+
+ if (
+ this.#stateBuilder.specsExecuted < this.#stateBuilder.totalSpecsDefined
+ ) {
+ this.#alerts.addSkipped(
+ this.#stateBuilder.specsExecuted,
+ this.#stateBuilder.totalSpecsDefined
+ );
+ }
+
+ this.#statusBar.showDone(doneResult, this.#stateBuilder);
+
+ if (doneResult.failedExpectations) {
+ for (const f of doneResult.failedExpectations) {
+ this.#alerts.addGlobalFailure(f);
+ }
+ }
+
+ for (const dw of this.#stateBuilder.deprecationWarnings) {
+ this.#alerts.addDeprecationWarning(dw);
+ }
+
+ const results = this.#find('.jasmine-results');
+ const summary = new j$.private.SummaryTreeView(
+ this.#urlBuilder,
+ this.#filterSpecs
+ );
+ summary.addResults(this.#stateBuilder.topResults);
+ results.appendChild(summary.rootEl);
+ const perf = new j$.private.PerformanceView();
+ perf.addResults(this.#stateBuilder.topResults);
+ results.appendChild(perf.rootEl);
+ this.#tabBar.showTab(specListTabId);
+ this.#tabBar.showTab(perfTabId);
+
+ if (this.#stateBuilder.anyNonTopSuiteFailures) {
+ this.#tabBar.showTab(failuresTabId);
+ this.#tabBar.selectTab(failuresTabId);
+ } else {
+ this.#tabBar.selectTab(specListTabId);
+ }
+ }
+
+ #find(selector) {
+ return this.#container.querySelector(
+ '.jasmine_html-reporter ' + selector
+ );
+ }
+
+ #setMenuModeTo(mode) {
+ this.#htmlReporterMain.setAttribute(
+ 'class',
+ 'jasmine_html-reporter ' + mode
+ );
+ }
+ }
+
+ class ProgressView {
+ constructor() {
+ this.rootEl = createDom('progress', { value: 0 });
+ }
+
+ start(totalSpecsDefined) {
+ this.rootEl.max = totalSpecsDefined;
+ }
+
+ specDone(result) {
+ if (result.status !== 'excluded') {
+ this.rootEl.value = this.rootEl.value + 1;
+ }
+
+ if (result.status === 'failed') {
+ this.rootEl.classList.add('failed');
+ }
+ }
+ }
+
+ class UrlBuilder {
+ #queryString;
+ #getSuiteById;
+
+ constructor(options) {
+ this.#queryString = options.queryString;
+ this.#getSuiteById = options.getSuiteById;
+ }
+
+ suiteHref(suite) {
+ const path = this.#suitePath(suite);
+ return this.#specPathHref(path);
+ }
+
+ specHref(specResult) {
+ const suite = this.#getSuiteById(specResult.parentSuiteId);
+ const path = this.#suitePath(suite);
+ path.push(specResult.description);
+ return this.#specPathHref(path);
+ }
+
+ runAllHref() {
+ return this.#addToExistingQueryString('path', '');
+ }
+
+ seedHref(seed) {
+ return this.#addToExistingQueryString('seed', seed);
+ }
+
+ #suitePath(suite) {
+ const path = [];
+
+ while (suite && suite.parent) {
+ path.unshift(suite.result.description);
+ suite = suite.parent;
+ }
+
+ return path;
+ }
+
+ #specPathHref(specPath) {
+ return this.#addToExistingQueryString('path', JSON.stringify(specPath));
+ }
+
+ #addToExistingQueryString(k, v) {
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ this.#queryString.fullStringWithNewParam(k, v)
+ );
+ }
+ }
+
+ return HtmlReporterV2;
+};
+
+jasmineRequire.HtmlReporterV2Urls = function(j$) {
+ 'use strict';
+
+ // TODO unify with V2 UrlBuilder?
+ /**
+ * @class HtmlReporterV2Urls
+ * @classdesc Processes URLs for {@link HtmlReporterV2}.
+ * @since 6.0.0
+ */
+ class HtmlReporterV2Urls {
+ constructor(options = {}) {
+ // queryString is injectable for use in our own tests, but user code will
+ // not pass any options.
+ this.queryString =
+ options.queryString ||
+ new jasmine.QueryString({
+ getWindowLocation: function() {
+ return window.location;
+ }
+ });
+ }
+
+ /**
+ * Creates a {@link Configuration} from the current page's URL. Supported
+ * query string parameters include all those set by {@link HtmlReporterV2}
+ * as well as spec=partialPath, which filters out specs whose paths don't
+ * contain partialPath.
+ * @returns {Configuration}
+ * @example
+ * const urls = new jasmine.HtmlReporterV2Urls();
+ * env.configure(urls.configFromCurrentUrl());
+ */
+ configFromCurrentUrl() {
+ const config = {
+ stopOnSpecFailure: this.queryString.getParam('stopOnSpecFailure'),
+ stopSpecOnExpectationFailure: this.queryString.getParam(
+ 'stopSpecOnExpectationFailure'
+ )
+ };
+
+ const random = this.queryString.getParam('random');
+
+ if (random !== undefined && random !== '') {
+ config.random = random;
+ }
+
+ const seed = this.queryString.getParam('seed');
+ if (seed) {
+ config.seed = seed;
+ }
+
+ const specFilter = new j$.private.HtmlSpecFilterV2({
+ filterParams: () => ({
+ path: this.queryString.getParam('path'),
+ spec: this.queryString.getParam('spec')
+ })
+ });
+
+ config.specFilter = function(spec) {
+ return specFilter.matches(spec);
+ };
+
+ return config;
+ }
+
+ filteringSpecs() {
+ return !!this.queryString.getParam('path');
+ }
+ }
+
+ return HtmlReporterV2Urls;
+};
+
+jasmineRequire.HtmlSpecFilterV2 = function() {
+ class HtmlSpecFilterV2 {
+ #getFilterParams;
+
+ constructor(options) {
+ this.#getFilterParams = options.filterParams;
+ }
+
+ matches(spec) {
+ const params = this.#getFilterParams();
+
+ if (params.path) {
+ return this.#matchesPath(spec, JSON.parse(params.path));
+ } else if (params.spec) {
+ // Like legacy HtmlSpecFilter, retained because it's convenient for
+ // hand-constructing filter URLs
+ return spec.getFullName().includes(params.spec);
+ }
+
+ return true;
+ }
+
+ #matchesPath(spec, path) {
+ const specPath = spec.getPath();
+
+ if (path.length > specPath.length) {
+ return false;
+ }
+
+ for (let i = 0; i < path.length; i++) {
+ if (specPath[i] !== path[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return HtmlSpecFilterV2;
+};
+
+jasmineRequire.OverallStatusBar = function(j$) {
+ 'use strict';
+
+ const { createDom } = j$.private.htmlReporterUtils;
+ const staticClassNames = 'jasmine-overall-result jasmine-bar';
+
+ class OverallStatusBar {
+ #urlBuilder;
+
+ constructor(urlBuilder) {
+ this.#urlBuilder = urlBuilder;
+ this.rootEl = createDom('span', {
+ className: staticClassNames,
+ 'aria-live': 'polite'
+ });
+ }
+
+ showRunning() {
+ this.rootEl.textContent = 'Running...';
+ this.rootEl.classList.add('jasmine-in-progress');
+ }
+
+ showFailing() {
+ this.rootEl.textContent = 'Failing...';
+ this.rootEl.classList.add('jasmine-failed');
+ }
+
+ showDone(doneResult, stateBuilder) {
+ // Clear any classes added to represent in-progress state
+ this.rootEl.className = staticClassNames;
+
+ let statusBarMessage = '';
+ const globalFailures =
+ (doneResult && doneResult.failedExpectations) || [];
+ const failed = stateBuilder.failureCount + globalFailures.length > 0;
+
+ if (stateBuilder.totalSpecsDefined > 0 || failed) {
+ statusBarMessage +=
+ pluralize('spec', stateBuilder.specsExecuted) +
+ ', ' +
+ pluralize('failure', stateBuilder.failureCount);
+ if (stateBuilder.pendingSpecCount) {
+ statusBarMessage +=
+ ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount);
+ }
+ }
+
+ if (doneResult.overallStatus === 'passed') {
+ this.rootEl.classList.add('jasmine-passed');
+ } else if (doneResult.overallStatus === 'incomplete') {
+ this.rootEl.classList.add('jasmine-incomplete');
+ statusBarMessage =
+ 'Incomplete: ' +
+ doneResult.incompleteReason +
+ ', ' +
+ statusBarMessage;
+ } else {
+ this.rootEl.classList.add('jasmine-failed');
+ }
+
+ // Replace any existing children with the message
+ this.rootEl.textContent = statusBarMessage;
+
+ const order = doneResult.order;
+ if (order && order.random) {
+ this.#addSeedBar(order);
+ }
+
+ this.#addDuration(doneResult);
+ }
+
+ #addSeedBar(order) {
+ this.rootEl.appendChild(
+ createDom(
+ 'span',
+ { className: 'jasmine-seed-bar' },
+ ', randomized with seed ',
+ createDom(
+ 'a',
+ {
+ title: 'randomized with seed ' + order.seed,
+ href: this.#urlBuilder.seedHref(order.seed)
+ },
+ order.seed
+ )
+ )
+ );
+ }
+
+ #addDuration(doneResult) {
+ const secs = doneResult.totalTime / 1000;
+ this.rootEl.appendChild(
+ createDom(
+ 'span',
+ { className: 'jasmine-duration' },
+ `finished in ${secs}s`
+ )
+ );
+ }
+ }
+
+ function pluralize(singular, count) {
+ const word = count === 1 ? singular : singular + 's';
+ return '' + count + ' ' + word;
+ }
+
+ return OverallStatusBar;
+};
+
+jasmineRequire.PerformanceView = function(j$) {
+ const createDom = j$.private.htmlReporterUtils.createDom;
+ const MAX_SLOW_SPECS = 20;
+
+ class PerformanceView {
+ #summary;
+ #tbody;
+
+ constructor() {
+ this.#tbody = document.createElement('tbody');
+ this.#summary = document.createElement('div');
+ this.rootEl = createDom(
+ 'div',
+ { className: 'jasmine-performance-view' },
+ createDom('h2', {}, 'Performance'),
+ this.#summary,
+ createDom('h3', {}, 'Slowest Specs'),
+ createDom(
+ 'table',
+ {},
+ createDom(
+ 'thead',
+ {},
+ createDom(
+ 'tr',
+ {},
+ createDom('th', {}, 'Duration'),
+ createDom('th', {}, 'Spec Name')
+ )
+ ),
+ this.#tbody
+ )
+ );
+ }
+
+ addResults(resultsTree) {
+ const specResults = [];
+ getSpecResults(resultsTree, specResults);
+
+ if (specResults.length === 0) {
+ return;
+ }
+
+ specResults.sort(function(a, b) {
+ if (a.duration < b.duration) {
+ return 1;
+ } else if (a.duration > b.duration) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+
+ this.#populateSumary(specResults);
+ this.#populateTable(specResults);
+ }
+
+ #populateSumary(specResults) {
+ const total = specResults.map(r => r.duration).reduce((a, b) => a + b, 0);
+ const mean = total / specResults.length;
+ const median = specResults[Math.floor(specResults.length / 2)].duration;
+ this.#summary.appendChild(
+ document.createTextNode(`Mean spec run time: ${mean.toFixed(0)}ms`)
+ );
+ this.#summary.appendChild(document.createElement('br'));
+ this.#summary.appendChild(
+ document.createTextNode(`Median spec run time: ${median}ms`)
+ );
+ }
+
+ #populateTable(specResults) {
+ specResults = specResults.slice(0, MAX_SLOW_SPECS);
+
+ for (const r of specResults) {
+ this.#tbody.appendChild(
+ createDom(
+ 'tr',
+ {},
+ createDom('td', {}, `${r.duration}ms`),
+ createDom('td', {}, r.fullName)
+ )
+ );
+ }
+ }
+ }
+
+ function getSpecResults(resultsTree, dest) {
+ for (const node of resultsTree.children) {
+ if (node.type === 'suite') {
+ getSpecResults(node, dest);
+ } else if (node.result.status !== 'excluded') {
+ dest.push(node.result);
+ }
+ }
+ }
+
+ return PerformanceView;
+};
+
+jasmineRequire.ResultsStateBuilder = function(j$) {
+ 'use strict';
+
+ class ResultsStateBuilder {
+ constructor() {
+ this.topResults = new j$.private.ResultsNode({}, '', null);
+ this.currentParent = this.topResults;
+ this.suitesById = {};
+ this.totalSpecsDefined = 0;
+ this.specsExecuted = 0;
+ this.failureCount = 0;
+ this.anyNonTopSuiteFailures = false;
+ this.pendingSpecCount = 0;
+ this.deprecationWarnings = [];
+ }
+
+ suiteStarted(result) {
+ this.currentParent.addChild(result, 'suite');
+ this.currentParent = this.currentParent.last();
+ this.suitesById[result.id] = this.currentParent;
+ }
+
+ suiteDone(result) {
+ this.currentParent.updateResult(result);
+ this.#addDeprecationWarnings(result, 'suite');
+
+ if (this.currentParent !== this.topResults) {
+ this.currentParent = this.currentParent.parent;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ this.anyNonTopSuiteFailures = true;
+ }
+ }
+
+ specDone(result) {
+ this.currentParent.addChild(result, 'spec');
+ this.#addDeprecationWarnings(result, 'spec');
+
+ if (result.status !== 'excluded') {
+ this.specsExecuted++;
+ }
+
+ if (result.status === 'failed') {
+ this.failureCount++;
+ this.anyNonTopSuiteFailures = true;
+ }
+
+ if (result.status == 'pending') {
+ this.pendingSpecCount++;
+ }
+ }
+
+ jasmineStarted(result) {
+ this.totalSpecsDefined = result.totalSpecsDefined;
+ }
+
+ jasmineDone(result) {
+ if (result.failedExpectations) {
+ this.failureCount += result.failedExpectations.length;
+ }
+
+ this.#addDeprecationWarnings(result);
+ }
+
+ #addDeprecationWarnings(result, runnableType) {
+ if (result.deprecationWarnings) {
+ for (const dw of result.deprecationWarnings) {
+ this.deprecationWarnings.push({
+ message: dw.message,
+ stack: dw.stack,
+ runnableName: result.fullName,
+ runnableType: runnableType
+ });
+ }
+ }
+ }
+ }
+
+ return ResultsStateBuilder;
+};
+
+jasmineRequire.SummaryTreeView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SummaryTreeView {
+ #urlBuilder;
+ #filterSpecs;
+
+ constructor(urlBuilder, filterSpecs) {
+ this.#urlBuilder = urlBuilder;
+ this.#filterSpecs = filterSpecs;
+ this.rootEl = createDom('div', {
+ className: 'jasmine-summary'
+ });
+ }
+
+ addResults(resultsTree) {
+ this.#addResults(resultsTree, this.rootEl);
+ }
+
+ #addResults(resultsTree, domParent) {
+ let specListNode;
+ for (let i = 0; i < resultsTree.children.length; i++) {
+ const resultNode = resultsTree.children[i];
+ if (this.#filterSpecs && !hasActiveSpec(resultNode)) {
+ continue;
+ }
+ if (resultNode.type === 'suite') {
+ const suiteListNode = createDom(
+ 'ul',
+ { className: 'jasmine-suite', id: 'suite-' + resultNode.result.id },
+ createDom(
+ 'li',
+ {
+ className:
+ 'jasmine-suite-detail jasmine-' + resultNode.result.status
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ resultNode.result.description
+ )
+ )
+ );
+
+ this.#addResults(resultNode, suiteListNode);
+ domParent.appendChild(suiteListNode);
+ }
+ if (resultNode.type === 'spec') {
+ if (domParent.getAttribute('class') !== 'jasmine-specs') {
+ specListNode = createDom('ul', {
+ className: 'jasmine-specs'
+ });
+ domParent.appendChild(specListNode);
+ }
+ let specDescription = resultNode.result.description;
+ if (noExpectations(resultNode.result)) {
+ specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+ }
+ if (resultNode.result.status === 'pending') {
+ if (resultNode.result.pendingReason !== '') {
+ specDescription +=
+ ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+ } else {
+ specDescription += ' PENDING';
+ }
+ }
+ specListNode.appendChild(
+ createDom(
+ 'li',
+ {
+ className: 'jasmine-' + resultNode.result.status,
+ id: 'spec-' + resultNode.result.id
+ },
+ createDom(
+ 'a',
+ { href: this.#urlBuilder.specHref(resultNode.result) },
+ specDescription
+ ),
+ createDom(
+ 'span',
+ { className: 'jasmine-spec-duration' },
+ '(' + resultNode.result.duration + 'ms)'
+ )
+ )
+ );
+ }
+ }
+ }
+ }
+
+ function hasActiveSpec(resultNode) {
+ if (resultNode.type === 'spec' && resultNode.result.status !== 'excluded') {
+ return true;
+ }
+
+ if (resultNode.type === 'suite') {
+ for (let i = 0, j = resultNode.children.length; i < j; i++) {
+ if (hasActiveSpec(resultNode.children[i])) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return SummaryTreeView;
+};
+
+jasmineRequire.SymbolsView = function(j$) {
+ 'use strict';
+
+ const { createDom, noExpectations } = j$.private.htmlReporterUtils;
+
+ class SymbolsView {
+ constructor() {
+ this.rootEl = createDom('ul', {
+ className: 'jasmine-symbol-summary'
+ });
+ }
+
+ append(result, config) {
+ this.rootEl.appendChild(
+ createDom('li', {
+ className: this.#className(result, config),
+ id: 'spec_' + result.id,
+ title: result.fullName
+ })
+ );
+ }
+
+ #className(result, config) {
+ if (noExpectations(result) && result.status === 'passed') {
+ return 'jasmine-empty';
+ } else if (result.status === 'excluded') {
+ if (config.hideDisabled) {
+ return 'jasmine-excluded-no-display';
+ } else {
+ return 'jasmine-excluded';
+ }
+ } else {
+ return 'jasmine-' + result.status;
+ }
+ }
+ }
+
+ return SymbolsView;
+};
+
+jasmineRequire.TabBar = function(j$) {
+ const createDom = j$.private.htmlReporterUtils.createDom;
+
+ class TabBar {
+ #tabs;
+ #onSelectTab;
+
+ // tabSpecs should be an array of {id, label}.
+ // All tabs are initially not visible and not selected.
+ constructor(tabSpecs, onSelectTab) {
+ this.#onSelectTab = onSelectTab;
+ this.#tabs = [];
+ this.#tabs = tabSpecs.map(ts => new Tab(ts, () => this.selectTab(ts.id)));
+
+ this.rootEl = createDom(
+ 'span',
+ { className: 'jasmine-menu jasmine-bar' },
+ this.#tabs.map(t => t.rootEl)
+ );
+ }
+
+ showTab(id) {
+ for (const tab of this.#tabs) {
+ if (tab.rootEl.id === id) {
+ tab.setVisibility(true);
+ }
+ }
+ }
+
+ selectTab(id) {
+ for (const tab of this.#tabs) {
+ tab.setSelected(tab.rootEl.id === id);
+ }
+
+ this.#onSelectTab(id);
+ }
+ }
+
+ class Tab {
+ #spec;
+ #onClick;
+
+ constructor(spec, onClick) {
+ this.#spec = spec;
+ this.#onClick = onClick;
+ this.rootEl = createDom(
+ 'span',
+ { id: spec.id, className: 'jasmine-tab jasmine-hidden' },
+ this.#createLink()
+ );
+ }
+
+ setVisibility(visible) {
+ this.rootEl.classList.toggle('jasmine-hidden', !visible);
+ }
+
+ setSelected(selected) {
+ if (selected) {
+ this.rootEl.textContent = this.#spec.label;
+ } else {
+ this.rootEl.textContent = '';
+ this.rootEl.appendChild(this.#createLink());
+ }
+ }
+
+ #createLink() {
+ const link = createDom('a', { href: '#' }, this.#spec.label);
+ link.addEventListener('click', e => {
+ e.preventDefault();
+ this.#onClick();
+ });
+ return link;
+ }
+ }
+
+ return TabBar;
+};
diff --git a/src/static/tests/lib/jasmine-6.0.1/jasmine.css b/src/static/tests/lib/jasmine-6.0.1/jasmine.css
new file mode 100644
index 0000000..3be6101
--- /dev/null
+++ b/src/static/tests/lib/jasmine-6.0.1/jasmine.css
@@ -0,0 +1,351 @@
+@charset "UTF-8";
+body {
+ overflow-y: scroll;
+}
+
+.jasmine_html-reporter {
+ width: 100%;
+ background-color: #eee;
+ padding: 5px;
+ margin: -8px;
+ font-size: 12px;
+ font-family: Monaco, "Lucida Console", monospace;
+ line-height: 14px;
+ color: #333;
+}
+.jasmine_html-reporter a {
+ text-decoration: none;
+}
+.jasmine_html-reporter a:hover {
+ text-decoration: underline;
+}
+.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 {
+ margin: 0;
+ line-height: 14px;
+}
+.jasmine_html-reporter .jasmine-banner,
+.jasmine_html-reporter .jasmine-symbol-summary,
+.jasmine_html-reporter .jasmine-summary,
+.jasmine_html-reporter .jasmine-result-message,
+.jasmine_html-reporter .jasmine-spec .jasmine-description,
+.jasmine_html-reporter .jasmine-spec-detail .jasmine-description,
+.jasmine_html-reporter .jasmine-alert .jasmine-bar,
+.jasmine_html-reporter .jasmine-stack-trace {
+ padding-left: 9px;
+ padding-right: 9px;
+}
+.jasmine_html-reporter .jasmine-banner {
+ position: relative;
+}
+.jasmine_html-reporter .jasmine-banner .jasmine-title {
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==") no-repeat;
+ background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=") no-repeat, none;
+ background-size: 100%;
+ display: block;
+ float: left;
+ width: 90px;
+ height: 25px;
+}
+.jasmine_html-reporter .jasmine-banner .jasmine-version {
+ margin-left: 14px;
+ position: relative;
+ top: 6px;
+}
+.jasmine_html-reporter #jasmine_content {
+ position: fixed;
+ right: 100%;
+}
+.jasmine_html-reporter .jasmine-banner {
+ margin-top: 14px;
+}
+.jasmine_html-reporter .jasmine-duration {
+ color: #fff;
+ float: right;
+ line-height: 28px;
+ padding-right: 9px;
+ font-size: 12px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary {
+ overflow: hidden;
+ margin: 14px 0;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li {
+ display: inline-block;
+ height: 10px;
+ width: 14px;
+ font-size: 16px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed {
+ font-size: 14px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before {
+ color: #007069;
+ content: "•";
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed {
+ line-height: 9px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before {
+ color: #ca3a11;
+ content: "×";
+ font-weight: bold;
+ margin-left: -1px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-excluded {
+ font-size: 14px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-excluded:before {
+ color: #bababa;
+ content: "•";
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-excluded-no-display {
+ font-size: 14px;
+ display: none;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending {
+ line-height: 17px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before {
+ color: #ba9d37;
+ content: "*";
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty {
+ font-size: 14px;
+}
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before {
+ color: #ba9d37;
+ content: "•";
+}
+.jasmine_html-reporter progress {
+ width: 100%;
+}
+.jasmine_html-reporter progress[value] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+.jasmine_html-reporter progress[value]::-webkit-progress-value, .jasmine_html-reporter progress[value]::-moz-progress-bar {
+ background: #007069;
+}
+.failed .jasmine_html-reporter progress[value]::-webkit-progress-value, .failed .jasmine_html-reporter progress[value]::-moz-progress-bar {
+ background: #ca3a11;
+}
+.jasmine_html-reporter progress.failed[value]::-webkit-progress-value, .jasmine_html-reporter progress.failed[value]::-moz-progress-bar {
+ background: #ca3a11;
+}
+.jasmine_html-reporter .jasmine-run-options {
+ float: right;
+ margin-right: 5px;
+ border: 1px solid #8a4182;
+ color: #8a4182;
+ position: relative;
+ line-height: 20px;
+}
+.jasmine_html-reporter .jasmine-run-options .jasmine-trigger {
+ cursor: pointer;
+ padding: 8px 16px;
+}
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload {
+ position: absolute;
+ display: none;
+ right: -1px;
+ border: 1px solid #8a4182;
+ background-color: #eee;
+ white-space: nowrap;
+ padding: 4px 8px;
+}
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open {
+ display: block;
+}
+.jasmine_html-reporter .jasmine-bar {
+ line-height: 28px;
+ font-size: 14px;
+ display: block;
+ color: #eee;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-in-progress {
+ color: #333;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-failed, .jasmine_html-reporter .jasmine-bar.jasmine-errored {
+ background-color: #ca3a11;
+ color: #eee;
+ border-bottom: 1px solid #eee;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-passed {
+ background-color: #007069;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-incomplete {
+ background-color: #bababa;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-skipped {
+ background-color: #bababa;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-warning {
+ margin-top: 14px;
+ margin-bottom: 14px;
+ background-color: #ba9d37;
+ color: #333;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-menu {
+ background-color: #fff;
+ color: #000;
+}
+.jasmine_html-reporter .jasmine-bar.jasmine-menu a {
+ color: blue;
+ text-decoration: underline;
+}
+.jasmine_html-reporter .jasmine-bar a {
+ color: white;
+}
+.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list,
+.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures,
+.jasmine_html-reporter.jasmine-spec-list .jasmine-performance-view {
+ display: none;
+}
+.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list,
+.jasmine_html-reporter.jasmine-failure-list .jasmine-summary,
+.jasmine_html-reporter.jasmine-failure-list .jasmine-performance-view {
+ display: none;
+}
+.jasmine_html-reporter.jasmine-performance .jasmine-results .jasmine-failures,
+.jasmine_html-reporter.jasmine-performance .jasmine-summary {
+ display: none;
+}
+.jasmine_html-reporter .jasmine-results {
+ margin-top: 14px;
+}
+.jasmine_html-reporter .jasmine-summary {
+ margin-top: 14px;
+}
+.jasmine_html-reporter .jasmine-summary ul {
+ list-style-type: none;
+ margin-left: 14px;
+ padding-top: 0;
+ padding-left: 0;
+}
+.jasmine_html-reporter .jasmine-summary ul.jasmine-suite {
+ margin-top: 7px;
+ margin-bottom: 7px;
+}
+.jasmine_html-reporter .jasmine-summary li.jasmine-passed a {
+ color: #007069;
+}
+.jasmine_html-reporter .jasmine-summary li.jasmine-failed a {
+ color: #ca3a11;
+}
+.jasmine_html-reporter .jasmine-summary li.jasmine-empty a {
+ color: #ba9d37;
+}
+.jasmine_html-reporter .jasmine-summary li.jasmine-pending a {
+ color: #ba9d37;
+}
+.jasmine_html-reporter .jasmine-summary li.jasmine-excluded a {
+ color: #bababa;
+}
+.jasmine_html-reporter .jasmine-specs li.jasmine-passed a:before {
+ content: "• ";
+}
+.jasmine_html-reporter .jasmine-specs li.jasmine-failed a:before {
+ content: "× ";
+}
+.jasmine_html-reporter .jasmine-specs li.jasmine-empty a:before {
+ content: "* ";
+}
+.jasmine_html-reporter .jasmine-specs li.jasmine-pending a:before {
+ content: "• ";
+}
+.jasmine_html-reporter .jasmine-specs li.jasmine-excluded a:before {
+ content: "• ";
+}
+.jasmine_html-reporter .jasmine-specs li .jasmine-spec-duration {
+ margin-left: 1em;
+}
+.jasmine_html-reporter .jasmine-description + .jasmine-suite {
+ margin-top: 0;
+}
+.jasmine_html-reporter .jasmine-suite {
+ margin-top: 14px;
+}
+.jasmine_html-reporter .jasmine-suite a {
+ color: #333;
+}
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail {
+ margin-bottom: 28px;
+}
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description {
+ background-color: #ca3a11;
+ color: white;
+}
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a {
+ color: white;
+}
+.jasmine_html-reporter .jasmine-result-message {
+ padding-top: 14px;
+ color: #333;
+ white-space: pre-wrap;
+}
+.jasmine_html-reporter .jasmine-result-message span.jasmine-result {
+ display: block;
+}
+.jasmine_html-reporter .jasmine-stack-trace {
+ margin: 5px 0 0 0;
+ max-height: 224px;
+ overflow: auto;
+ line-height: 18px;
+ color: #666;
+ border: 1px solid #ddd;
+ background: white;
+ white-space: pre;
+}
+.jasmine_html-reporter .jasmine-expander a {
+ display: block;
+ margin-left: 14px;
+ color: blue;
+ text-decoration: underline;
+}
+.jasmine_html-reporter .jasmine-expander-contents {
+ display: none;
+}
+.jasmine_html-reporter .jasmine-expanded {
+ padding-bottom: 10px;
+}
+.jasmine_html-reporter .jasmine-expanded .jasmine-expander-contents {
+ display: block;
+ margin-left: 14px;
+ padding: 5px;
+}
+.jasmine_html-reporter .jasmine-debug-log {
+ margin: 5px 0 0 0;
+ padding: 5px;
+ color: #666;
+ border: 1px solid #ddd;
+ background: white;
+}
+.jasmine_html-reporter .jasmine-debug-log table {
+ border-spacing: 0;
+}
+.jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td {
+ border: 1px solid #ddd;
+}
+.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
+ white-space: pre;
+}
+
+.jasmine-hidden {
+ display: none;
+}
+
+.jasmine-tab + .jasmine-tab:before {
+ content: " | ";
+}
+
+.jasmine-performance-view h2, .jasmine-performance-view h3 {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+.jasmine-performance-view table {
+ border-spacing: 5px;
+}
+.jasmine-performance-view th, .jasmine-performance-view td {
+ text-align: left;
+}
\ No newline at end of file
diff --git a/src/static/tests/lib/jasmine-6.0.1/jasmine.js b/src/static/tests/lib/jasmine-6.0.1/jasmine.js
new file mode 100644
index 0000000..5d5700b
--- /dev/null
+++ b/src/static/tests/lib/jasmine-6.0.1/jasmine.js
@@ -0,0 +1,12412 @@
+/*
+Copyright (c) 2008-2019 Pivotal Labs
+Copyright (c) 2008-2026 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() {
+ 'use strict';
+ let jasmineRequire;
+
+ if (
+ typeof module !== 'undefined' &&
+ module.exports &&
+ typeof exports !== 'undefined'
+ ) {
+ // Node
+ jasmineRequire = exports;
+ } else {
+ // Browser
+ jasmineRequire = globalThis.jasmineRequire = {};
+ }
+
+ function getJasmineRequire() {
+ return jasmineRequire;
+ }
+
+ const loadedAsBrowserEsm =
+ globalThis.document && !globalThis.document.currentScript;
+
+ getJasmineRequire().core = function(jRequire) {
+ const j$ = {};
+ Object.defineProperty(j$, 'private', {
+ enumerable: true,
+ value: {}
+ });
+
+ jRequire.base(j$, globalThis);
+ j$.private.deprecateMonkeyPatching = jRequire.deprecateMonkeyPatching(j$);
+ j$.private.util = jRequire.util(j$);
+ j$.private.errors = jRequire.errors();
+ j$.private.formatErrorMsg = jRequire.formatErrorMsg(j$);
+ j$.private.AllOf = jRequire.AllOf(j$);
+ j$.private.Any = jRequire.Any(j$);
+ j$.private.Anything = jRequire.Anything(j$);
+ j$.private.CallTracker = jRequire.CallTracker(j$);
+ j$.private.MockDate = jRequire.MockDate(j$);
+ j$.private.getStackClearer = jRequire.StackClearer(j$);
+ j$.private.Clock = jRequire.Clock(j$);
+ j$.private.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
+ j$.private.Deprecator = jRequire.Deprecator(j$);
+ j$.private.Configuration = jRequire.Configuration(j$);
+ j$.private.Env = jRequire.Env(j$);
+ j$.private.StackTrace = jRequire.StackTrace(j$);
+ j$.private.ExceptionFormatter = jRequire.ExceptionFormatter(j$);
+ j$.private.ExpectationFilterChain = jRequire.ExpectationFilterChain();
+ j$.private.Expector = jRequire.Expector(j$);
+ j$.private.Expectation = jRequire.Expectation(j$);
+ j$.private.buildExpectationResult = jRequire.buildExpectationResult(j$);
+ j$.private.JsApiReporter = jRequire.JsApiReporter(j$);
+ j$.private.makePrettyPrinter = jRequire.makePrettyPrinter(j$);
+ j$.private.basicPrettyPrinter = j$.private.makePrettyPrinter();
+ j$.private.MatchersUtil = jRequire.MatchersUtil(j$);
+ j$.private.ObjectContaining = jRequire.ObjectContaining(j$);
+ j$.private.ArrayContaining = jRequire.ArrayContaining(j$);
+ j$.private.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$);
+ j$.private.MapContaining = jRequire.MapContaining(j$);
+ j$.private.SetContaining = jRequire.SetContaining(j$);
+ j$.private.QueueRunner = jRequire.QueueRunner(j$);
+ j$.private.NeverSkipPolicy = jRequire.NeverSkipPolicy(j$);
+ j$.private.SkipAfterBeforeAllErrorPolicy = jRequire.SkipAfterBeforeAllErrorPolicy(
+ j$
+ );
+ j$.private.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy(
+ j$
+ );
+ j$.private.reporterEvents = jRequire.reporterEvents(j$);
+ j$.private.ReportDispatcher = jRequire.ReportDispatcher(j$);
+ j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$);
+ j$.private.CurrentRunableTracker = jRequire.CurrentRunableTracker();
+ j$.private.RunableResources = jRequire.RunableResources(j$);
+ j$.private.Runner = jRequire.Runner(j$);
+ j$.private.Spec = jRequire.Spec(j$);
+ j$.private.Spy = jRequire.Spy(j$);
+ j$.private.SpyFactory = jRequire.SpyFactory(j$);
+ j$.private.SpyRegistry = jRequire.SpyRegistry(j$);
+ j$.private.SpyStrategy = jRequire.SpyStrategy(j$);
+ j$.private.StringMatching = jRequire.StringMatching(j$);
+ j$.private.StringContaining = jRequire.StringContaining(j$);
+ j$.private.UserContext = jRequire.UserContext(j$);
+ j$.private.Suite = jRequire.Suite(j$);
+ j$.private.SuiteBuilder = jRequire.SuiteBuilder(j$);
+ j$.Timer = jRequire.Timer();
+ j$.private.TreeProcessor = jRequire.TreeProcessor(j$);
+ j$.private.TreeRunner = jRequire.TreeRunner(j$);
+ j$.version = jRequire.version();
+ j$.private.Order = jRequire.Order();
+ j$.private.DiffBuilder = jRequire.DiffBuilder(j$);
+ j$.private.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
+ j$.private.ObjectPath = jRequire.ObjectPath(j$);
+ j$.private.MismatchTree = jRequire.MismatchTree(j$);
+ j$.private.GlobalErrors = jRequire.GlobalErrors(j$);
+ j$.private.Truthy = jRequire.Truthy(j$);
+ j$.private.Falsy = jRequire.Falsy(j$);
+ j$.private.Empty = jRequire.Empty(j$);
+ j$.private.NotEmpty = jRequire.NotEmpty(j$);
+ j$.private.Is = jRequire.Is(j$);
+
+ j$.private.matchers = jRequire.requireMatchers(jRequire, j$);
+ j$.private.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$);
+
+ j$.private.loadedAsBrowserEsm = loadedAsBrowserEsm;
+
+ j$.private.deprecateMonkeyPatching(j$, [
+ // These are meant to be set by users.
+ 'DEFAULT_TIMEOUT_INTERVAL',
+ 'MAX_PRETTY_PRINT_ARRAY_LENGTH',
+ 'MAX_PRETTY_PRINT_CHARS',
+ 'MAX_PRETTY_PRINT_DEPTH',
+
+ // These are part of the deprecation warning mechanism. To avoid infinite
+ // recursion, they're separately protected in a way that doesn't emit
+ // deprecation warnings.
+ 'private',
+ 'getEnv'
+ ]);
+
+ return j$;
+ };
+
+ return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+ 'use strict';
+
+ 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) {
+ 'use strict';
+
+ /**
+ * 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 1000
+ * @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$.private.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}
+ */
+ Object.defineProperty(j$, 'getEnv', {
+ enumerable: true,
+ value: function(options) {
+ const env = (j$.private.currentEnv_ =
+ j$.private.currentEnv_ || new j$.private.Env(options));
+ //jasmine. singletons in here (setTimeout blah blah).
+ return env;
+ }
+ });
+
+ j$.private.isObject = function(value) {
+ return (
+ value !== undefined && value !== null && j$.private.isA('Object', value)
+ );
+ };
+
+ j$.private.isString = function(value) {
+ return j$.private.isA('String', value);
+ };
+
+ j$.private.isNumber = function(value) {
+ return j$.private.isA('Number', value);
+ };
+
+ j$.private.isFunction = function(value) {
+ return j$.private.isA('Function', value);
+ };
+
+ j$.private.isAsyncFunction = function(value) {
+ return j$.private.isA('AsyncFunction', value);
+ };
+
+ j$.private.isGeneratorFunction = function(value) {
+ return j$.private.isA('GeneratorFunction', value);
+ };
+
+ j$.private.isTypedArray = function(value) {
+ return (
+ j$.private.isA('Float32Array', value) ||
+ j$.private.isA('Float64Array', value) ||
+ j$.private.isA('Int16Array', value) ||
+ j$.private.isA('Int32Array', value) ||
+ j$.private.isA('Int8Array', value) ||
+ j$.private.isA('Uint16Array', value) ||
+ j$.private.isA('Uint32Array', value) ||
+ j$.private.isA('Uint8Array', value) ||
+ j$.private.isA('Uint8ClampedArray', value)
+ );
+ };
+
+ j$.private.isA = function(typeName, value) {
+ return j$.private.getType(value) === '[object ' + typeName + ']';
+ };
+
+ j$.private.isError = function(value) {
+ if (!value) {
+ return false;
+ }
+
+ if (value instanceof Error) {
+ return true;
+ }
+
+ return typeof value.stack === 'string' && typeof value.message === 'string';
+ };
+
+ j$.private.isAsymmetricEqualityTester = function(obj) {
+ return obj ? j$.private.isA('Function', obj.asymmetricMatch) : false;
+ };
+
+ j$.private.getType = function(value) {
+ return Object.prototype.toString.apply(value);
+ };
+
+ j$.private.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$.private.isMap = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ obj.constructor === jasmineGlobal.Map
+ );
+ };
+
+ j$.private.isSet = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ obj.constructor === jasmineGlobal.Set
+ );
+ };
+
+ j$.private.isWeakMap = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ obj.constructor === jasmineGlobal.WeakMap
+ );
+ };
+
+ j$.private.isURL = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ obj.constructor === jasmineGlobal.URL
+ );
+ };
+
+ j$.private.isIterable = function(value) {
+ return value && !!value[Symbol.iterator];
+ };
+
+ j$.private.isDataView = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ obj.constructor === jasmineGlobal.DataView
+ );
+ };
+
+ j$.private.isPromise = function(obj) {
+ return !!obj && obj.constructor === jasmineGlobal.Promise;
+ };
+
+ j$.private.isPromiseLike = function(obj) {
+ return !!obj && j$.private.isFunction(obj.then);
+ };
+
+ j$.private.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] : '';
+ };
+
+ j$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.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$.private.SpyStrategy &&
+ putativeSpy.calls instanceof j$.private.CallTracker
+ );
+ };
+
+ /**
+ * Logs a message for use in debugging. If the spec fails, trace messages
+ * will be included in the {@link SpecDoneEvent|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$) {
+ 'use strict';
+
+ 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$.private.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$.private.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
+ );
+ }
+ };
+
+ util.assertReporterCloneable = function(v, msgPrefix) {
+ try {
+ // Reporter events are cloned internally via structuredClone, and it's
+ // common for reporters (including jasmine-browser-runner's) to JSON
+ // serialize them.
+ JSON.stringify(structuredClone(v));
+ } catch (e) {
+ throw new Error(`${msgPrefix} can't be cloned`, { cause: e });
+ }
+ };
+
+ return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+ 'use strict';
+
+ class Spec {
+ #autoCleanClosures;
+ #throwOnExpectationFailure;
+ #timer;
+ #metadata;
+ #executionState;
+
+ 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();
+
+ this.reset();
+
+ if (!this.queueableFn.fn) {
+ this.exclude();
+ }
+ }
+
+ addExpectationResult(passed, data, isError) {
+ const expectationResult = j$.private.buildExpectationResult(data);
+
+ if (passed) {
+ this.#executionState.passedExpectations.push(expectationResult);
+ } else {
+ if (this.reportedDone) {
+ this.onLateError(expectationResult);
+ } else {
+ this.#executionState.failedExpectations.push(expectationResult);
+ }
+
+ if (this.#throwOnExpectationFailure && !isError) {
+ throw new j$.private.errors.ExpectationFailed();
+ }
+ }
+ }
+
+ getSpecProperty(key) {
+ this.#executionState.properties = this.#executionState.properties || {};
+ return this.#executionState.properties[key];
+ }
+
+ setSpecProperty(key, value) {
+ // Key and value will eventually be cloned during reporting. The error
+ // thrown at that point if they aren't cloneable isn't very helpful.
+ // Throw a better one now.
+ if (!j$.private.isString(key)) {
+ throw new Error('Key must be a string');
+ }
+ j$.private.util.assertReporterCloneable(value, 'Value');
+
+ this.#executionState.properties = this.#executionState.properties || {};
+ this.#executionState.properties[key] = value;
+ }
+
+ executionStarted() {
+ this.#timer.start();
+ }
+
+ executionFinished(excluded, failSpecWithNoExp) {
+ this.#executionState.dynamicallyExcluded = excluded;
+ this.#executionState.requireExpectations = failSpecWithNoExp;
+
+ if (this.#autoCleanClosures) {
+ this.queueableFn.fn = null;
+ }
+
+ this.#executionState.duration = this.#timer.elapsed();
+
+ if (this.status() !== 'failed') {
+ this.#executionState.debugLogs = null;
+ }
+ }
+
+ hadBeforeAllFailure() {
+ this.addExpectationResult(
+ false,
+ {
+ passed: false,
+ message:
+ 'Not run because a beforeAll function failed. The ' +
+ 'beforeAll failure will be reported on the suite that ' +
+ 'caused it.'
+ },
+ true
+ );
+ }
+
+ reset() {
+ this.#executionState = {
+ failedExpectations: [],
+ passedExpectations: [],
+ deprecationWarnings: [],
+ pendingReason: this.excludeMessage || '',
+ duration: null,
+ properties: null,
+ debugLogs: null,
+ // TODO: better naming. Don't make 'excluded' mean two things.
+ dynamicallyExcluded: false,
+ requireExpectations: false,
+ markedPending: this.markedExcluding
+ };
+ this.reportedDone = false;
+ }
+
+ startedEvent() {
+ /**
+ * @typedef SpecStartedEvent
+ * @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 - Deprecated. 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. This property may be removed in
+ * a future version unless there is enough user interest in keeping it.
+ * See {@link https://github.com/jasmine/jasmine/issues/2065}.
+ * @since 2.0.0
+ */
+ return this.#commonEventFields();
+ }
+
+ doneEvent() {
+ /**
+ * @typedef SpecDoneEvent
+ * @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 - The result of this spec. May be 'passed', 'failed', 'pending', or 'excluded'.
+ * @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
+ */
+ const event = {
+ ...this.#commonEventFields(),
+ status: this.status()
+ };
+ const toCopy = [
+ 'failedExpectations',
+ 'passedExpectations',
+ 'deprecationWarnings',
+ 'pendingReason',
+ 'duration',
+ 'properties',
+ 'debugLogs'
+ ];
+
+ for (const k of toCopy) {
+ event[k] = this.#executionState[k];
+ }
+
+ return event;
+ }
+
+ #commonEventFields() {
+ return {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ parentSuiteId: this.parentSuiteId,
+ filename: this.filename
+ };
+ }
+
+ handleException(e) {
+ if (Spec.isPendingSpecException(e)) {
+ this.pend(extractCustomPendingMessage(e));
+ return;
+ }
+
+ if (e instanceof j$.private.errors.ExpectationFailed) {
+ return;
+ }
+
+ this.addExpectationResult(
+ false,
+ {
+ matcherName: '',
+ passed: false,
+ error: e
+ },
+ true
+ );
+ }
+
+ pend(message) {
+ this.#executionState.markedPending = true;
+ if (message) {
+ this.#executionState.pendingReason = message;
+ }
+ }
+
+ get markedPending() {
+ return this.#executionState.markedPending;
+ }
+
+ // 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);
+ }
+
+ status() {
+ if (this.#executionState.dynamicallyExcluded) {
+ return 'excluded';
+ }
+
+ if (this.markedPending) {
+ return 'pending';
+ }
+
+ if (
+ this.#executionState.failedExpectations.length > 0 ||
+ (this.#executionState.requireExpectations &&
+ this.#executionState.failedExpectations.length +
+ this.#executionState.passedExpectations.length ===
+ 0)
+ ) {
+ return 'failed';
+ }
+
+ return 'passed';
+ }
+
+ getFullName() {
+ return this.getPath().join(' ');
+ }
+
+ addDeprecationWarning(deprecation) {
+ if (typeof deprecation === 'string') {
+ deprecation = { message: deprecation };
+ }
+ this.#executionState.deprecationWarnings.push(
+ j$.private.buildExpectationResult(deprecation)
+ );
+ }
+
+ debugLog(msg) {
+ if (!this.#executionState.debugLogs) {
+ this.#executionState.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.#executionState.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.}
+ * @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() {
+ 'use strict';
+
+ 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$) {
+ 'use strict';
+
+ const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3;
+
+ /**
+ * @class Env
+ * @since 2.0.0
+ * @classdesc The Jasmine environment.
+ * _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$.private.GlobalErrors;
+ const global = envOptions.global || j$.getGlobal();
+
+ const realSetTimeout = global.setTimeout;
+ const realClearTimeout = global.clearTimeout;
+ const stackClearer = j$.private.getStackClearer(global);
+ this.clock = new j$.private.Clock(
+ global,
+ function() {
+ return new j$.private.DelayedFunctionScheduler();
+ },
+ new j$.private.MockDate(global)
+ );
+
+ const globalErrors = new GlobalErrors(
+ global,
+ // 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$.private.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$.private.Configuration();
+
+ if (!envOptions.suppressLoadErrors) {
+ installGlobalErrors();
+ globalErrors.pushListener(function loadtimeErrorHandler(error) {
+ topSuite.addExpectationResult(false, {
+ passed: false,
+ globalErrorType: 'load',
+ message: error.message,
+ stack: error.stack,
+ filename: error.filename,
+ lineno: error.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);
+ stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
+ };
+
+ /**
+ * 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$.private.Expectation.addCoreMatchers(j$.private.matchers);
+ j$.private.Expectation.addAsyncCoreMatchers(j$.private.asyncMatchers);
+
+ const expectationFactory = function(actual, spec) {
+ return j$.private.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.
+ *
+ * @property {String} matcherName - The name of the matcher that was executed for this expectation.
+ * @property {String} message - The failure message for the expectation.
+ */
+ const error = new Error(result.message);
+ error.message = result.message;
+ error.matcherName = result.matcherName;
+ throw error;
+ }
+ };
+
+ const throwUnlessFactory = function(actual, spec) {
+ return j$.private.Expectation.factory({
+ matchersUtil: runableResources.makeMatchersUtil(),
+ customMatchers: runableResources.customMatchers(),
+ actual: actual,
+ addExpectationResult: handleThrowUnlessFailure
+ });
+ };
+
+ const throwUnlessAsyncFactory = function(actual, spec) {
+ return j$.private.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$.private.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.addExpectationResult(false, 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.addExpectationResult(false, 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$.private.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
+ */
+ Object.defineProperty(this, 'deprecated', {
+ enumerable: true,
+ value: function(deprecation, options) {
+ const runable = runner.currentRunable() || topSuite;
+ deprecator.addDeprecationWarning(runable, deprecation, options);
+ }
+ });
+
+ function runQueue(options) {
+ options.clearStack = options.clearStack || stackClearer;
+ 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$.private.QueueRunner(options).execute();
+ }
+
+ const suiteBuilder = new j$.private.SuiteBuilder({
+ env: this,
+ expectationFactory,
+ asyncExpectationFactory,
+ onLateError: recordLateError,
+ runQueue
+ });
+ topSuite = suiteBuilder.topSuite;
+ const deprecator =
+ envOptions?.deprecator ?? new j$.private.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$.private.ReportDispatcher(
+ j$.private.reporterEvents,
+ function(options) {
+ options.SkipPolicy = j$.private.NeverSkipPolicy;
+ return runQueue(options);
+ },
+ recordLateError
+ );
+
+ runner = new j$.private.Runner({
+ topSuite,
+ totalSpecsDefined: () => suiteBuilder.totalSpecsDefined,
+ focusedRunables: () => suiteBuilder.focusedRunables,
+ runableResources,
+ reportDispatcher,
+ runQueue,
+ TreeProcessor: j$.private.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}
+ */
+ this.execute = async function(runablesToRun) {
+ installGlobalErrors();
+
+ // Karma incorrectly loads jasmine-core as an ES module. It isn't one,
+ // and we don't test that configuration. Warn about it.
+ if (j$.private.loadedAsBrowserEsm) {
+ this.deprecated(
+ "jasmine-core isn't an ES module but it was loaded as one. This is not a supported configuration."
+ );
+ }
+
+ 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} reporterToAdd The reporter to be added.
+ * @see custom_reporter
+ */
+ this.addReporter = function(reporterToAdd) {
+ if (parallelLoadingState) {
+ throw new Error('Reporters cannot be added via Env in parallel mode');
+ }
+
+ reportDispatcher.addReporter(reporterToAdd);
+ };
+
+ /**
+ * Provide a fallback reporter if no other reporters have been specified.
+ * @name Env#provideFallbackReporter
+ * @since 2.5.0
+ * @function
+ * @param {Reporter} reporterToAdd The reporter
+ * @see custom_reporter
+ */
+ this.provideFallbackReporter = function(reporterToAdd) {
+ reportDispatcher.provideFallbackReporter(reporterToAdd);
+ };
+
+ /**
+ * Clear all registered reporters
+ * @name Env#clearReporters
+ * @since 2.5.2
+ * @function
+ */
+ this.clearReporters = function() {
+ if (parallelLoadingState) {
+ throw new Error('Reporters cannot be removed via Env in parallel mode');
+ }
+
+ reportDispatcher.clearReporters();
+ };
+
+ /**
+ * Configures whether Jasmine should allow the same function to be spied on
+ * more than once during the execution of a spec. By default, spying on
+ * a function that is already a spy will cause an error.
+ * @name Env#allowRespy
+ * @function
+ * @since 2.5.0
+ * @param {boolean} allow Whether to allow respying
+ */
+ this.allowRespy = function(allow) {
+ runableResources.spyRegistry.allowRespy(allow);
+ };
+
+ this.spyOn = function() {
+ return runableResources.spyRegistry.spyOn.apply(
+ runableResources.spyRegistry,
+ arguments
+ );
+ };
+
+ this.spyOnProperty = function() {
+ return runableResources.spyRegistry.spyOnProperty.apply(
+ runableResources.spyRegistry,
+ arguments
+ );
+ };
+
+ this.spyOnAllFunctions = function() {
+ return runableResources.spyRegistry.spyOnAllFunctions.apply(
+ runableResources.spyRegistry,
+ arguments
+ );
+ };
+
+ this.createSpy = function(name, originalFn) {
+ return runableResources.spyFactory.createSpy(name, originalFn);
+ };
+
+ this.createSpyObj = function(baseName, methodNames, propertyNames) {
+ return runableResources.spyFactory.createSpyObj(
+ baseName,
+ methodNames,
+ propertyNames
+ );
+ };
+
+ this.spyOnGlobalErrorsAsync = async function(fn) {
+ const spy = this.createSpy('global error handler');
+ const associatedRunable = runner.currentRunable();
+ let cleanedUp = false;
+
+ globalErrors.setOverrideListener(spy, () => {
+ if (!cleanedUp) {
+ const message =
+ 'Global error spy was not uninstalled. (Did you ' +
+ 'forget to await the return value of spyOnGlobalErrorsAsync?)';
+ associatedRunable.addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ message,
+ error: null
+ });
+ }
+
+ cleanedUp = true;
+ });
+
+ try {
+ const maybePromise = fn(spy);
+
+ if (!j$.private.isPromiseLike(maybePromise)) {
+ throw new Error(
+ 'The callback to spyOnGlobalErrorsAsync must be an async or promise-returning function'
+ );
+ }
+
+ await maybePromise;
+ } finally {
+ if (!cleanedUp) {
+ cleanedUp = true;
+ globalErrors.removeOverrideListener();
+ }
+ }
+ };
+
+ function ensureIsNotNested(method) {
+ const runable = runner.currentRunable();
+ if (runable !== null && runable !== undefined) {
+ throw new Error(
+ "'" + method + "' should only be used in 'describe' function"
+ );
+ }
+ }
+
+ function ensureNonParallel(method) {
+ if (parallelLoadingState) {
+ throw new Error(`'${method}' is not available in parallel mode`);
+ }
+ }
+
+ function ensureNonParallelOrInDescribe(msg) {
+ if (parallelLoadingState && !suiteBuilder.inDescribe()) {
+ throw new Error(msg);
+ }
+ }
+
+ function ensureNonParallelOrInHelperOrInDescribe(method) {
+ if (parallelLoadingState === 'specs' && !suiteBuilder.inDescribe()) {
+ throw new Error(
+ 'In parallel mode, ' +
+ method +
+ ' must be in a describe block or in a helper file'
+ );
+ }
+ }
+
+ function validateConfigForParallel() {
+ if (!config.random) {
+ throw new Error('Randomization cannot be disabled in parallel mode');
+ }
+
+ if (config.seed !== null && config.seed !== undefined) {
+ throw new Error('Random seed cannot be set in parallel mode');
+ }
+ }
+
+ this.describe = function(description, definitionFn) {
+ ensureIsNotNested('describe');
+ const filename = indirectCallerFilename(describeStackDepth());
+ return suiteBuilder.describe(description, definitionFn, filename)
+ .metadata;
+ };
+
+ this.xdescribe = function(description, definitionFn) {
+ ensureIsNotNested('xdescribe');
+ const filename = indirectCallerFilename(describeStackDepth());
+ return suiteBuilder.xdescribe(description, definitionFn, filename)
+ .metadata;
+ };
+
+ this.fdescribe = function(description, definitionFn) {
+ ensureIsNotNested('fdescribe');
+ ensureNonParallel('fdescribe');
+ const filename = indirectCallerFilename(describeStackDepth());
+ return suiteBuilder.fdescribe(description, definitionFn, filename)
+ .metadata;
+ };
+
+ this.it = function(description, fn, timeout) {
+ ensureIsNotNested('it');
+ const filename = indirectCallerFilename(itStackDepth());
+ return suiteBuilder.it(description, fn, timeout, filename).metadata;
+ };
+
+ this.xit = function(description, fn, timeout) {
+ ensureIsNotNested('xit');
+ const filename = indirectCallerFilename(itStackDepth());
+ return suiteBuilder.xit(description, fn, timeout, filename).metadata;
+ };
+
+ this.fit = function(description, fn, timeout) {
+ ensureIsNotNested('fit');
+ ensureNonParallel('fit');
+ const filename = indirectCallerFilename(itStackDepth());
+ return suiteBuilder.fit(description, fn, timeout, filename).metadata;
+ };
+
+ function itStackDepth() {
+ return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames;
+ }
+
+ function describeStackDepth() {
+ return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames;
+ }
+
+ /**
+ * Get a user-defined property as part of the properties field of {@link SpecDoneEvent}
+ * @name Env#getSpecProperty
+ * @since 5.10.0
+ * @function
+ * @param {String} key The name of the property
+ * @returns {*} The value of the property
+ */
+ this.getSpecProperty = function(key) {
+ if (
+ !runner.currentRunable() ||
+ runner.currentRunable() == runner.currentSuite()
+ ) {
+ throw new Error(
+ "'getSpecProperty' was used when there was no current spec"
+ );
+ }
+ return runner.currentRunable().getSpecProperty(key);
+ };
+
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult}
+ * @name Env#setSpecProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ this.setSpecProperty = function(key, value) {
+ if (
+ !runner.currentRunable() ||
+ runner.currentRunable() == runner.currentSuite()
+ ) {
+ throw new Error(
+ "'setSpecProperty' was used when there was no current spec"
+ );
+ }
+ runner.currentRunable().setSpecProperty(key, value);
+ };
+
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SuiteResult}
+ * @name Env#setSuiteProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ this.setSuiteProperty = function(key, value) {
+ if (!runner.currentSuite()) {
+ throw new Error(
+ "'setSuiteProperty' was used when there was no current suite"
+ );
+ }
+ runner.currentSuite().setSuiteProperty(key, value);
+ };
+
+ this.debugLog = function(msg) {
+ const maybeSpec = runner.currentRunable();
+
+ if (!maybeSpec || !maybeSpec.debugLog) {
+ throw new Error("'debugLog' was called when there was no current spec");
+ }
+
+ maybeSpec.debugLog(msg);
+ };
+
+ this.expect = function(actual) {
+ const runable = runner.currentRunable();
+
+ if (!runable) {
+ throw new Error(
+ "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
+ );
+ }
+
+ return runable.expectationFactory(actual, runable);
+ };
+
+ this.expectAsync = function(actual) {
+ const runable = runner.currentRunable();
+
+ if (!runable) {
+ throw new Error(
+ "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
+ );
+ }
+
+ return runable.asyncExpectationFactory(actual, runable);
+ };
+
+ this.throwUnless = function(actual) {
+ const runable = runner.currentRunable();
+ return throwUnlessFactory(actual, runable);
+ };
+
+ this.throwUnlessAsync = function(actual) {
+ const runable = runner.currentRunable();
+ return throwUnlessAsyncFactory(actual, runable);
+ };
+
+ this.beforeEach = function(beforeEachFunction, timeout) {
+ ensureIsNotNested('beforeEach');
+ ensureNonParallelOrInHelperOrInDescribe('beforeEach');
+ suiteBuilder.beforeEach(beforeEachFunction, timeout);
+ };
+
+ this.beforeAll = function(beforeAllFunction, timeout) {
+ ensureIsNotNested('beforeAll');
+ // This message is -npm-specific, but currently parallel operation is
+ // only supported via -npm.
+ ensureNonParallelOrInDescribe(
+ "In parallel mode, 'beforeAll' " +
+ 'must be in a describe block. Use the globalSetup config ' +
+ 'property for exactly-once setup in parallel mode.'
+ );
+ suiteBuilder.beforeAll(beforeAllFunction, timeout);
+ };
+
+ this.afterEach = function(afterEachFunction, timeout) {
+ ensureIsNotNested('afterEach');
+ ensureNonParallelOrInHelperOrInDescribe('afterEach');
+ suiteBuilder.afterEach(afterEachFunction, timeout);
+ };
+
+ this.afterAll = function(afterAllFunction, timeout) {
+ ensureIsNotNested('afterAll');
+ // This message is -npm-specific, but currently parallel operation is
+ // only supported via -npm.
+ ensureNonParallelOrInDescribe(
+ "In parallel mode, 'afterAll' " +
+ 'must be in a describe block. Use the globalTeardown config ' +
+ 'property for exactly-once teardown in parallel mode.'
+ );
+ suiteBuilder.afterAll(afterAllFunction, timeout);
+ };
+
+ this.pending = function(message) {
+ let fullMessage = j$.private.Spec.pendingSpecExceptionMessage;
+ if (message) {
+ fullMessage += message;
+ }
+ throw fullMessage;
+ };
+
+ this.fail = function(error) {
+ if (!runner.currentRunable()) {
+ throw new Error(
+ "'fail' was used when there was no current spec, this could be because an asynchronous test timed out"
+ );
+ }
+
+ let message = 'Failed';
+ if (error) {
+ message += ': ';
+ if (error.message) {
+ message += error.message;
+ } else if (j$.private.isString(error)) {
+ message += error;
+ } else {
+ // pretty print all kind of objects. This includes arrays.
+ const pp = runableResources.makePrettyPrinter();
+ message += pp(error);
+ }
+ }
+
+ runner.currentRunable().addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ message: message,
+ error: error && error.message ? error : null
+ });
+
+ if (config.stopSpecOnExpectationFailure) {
+ throw new Error(message);
+ }
+ };
+
+ this.pp = function(value) {
+ const pp = runner.currentRunable()
+ ? runableResources.makePrettyPrinter()
+ : j$.private.basicPrettyPrinter;
+ return pp(value);
+ };
+
+ this.cleanup_ = function() {
+ uninstallGlobalErrors();
+ };
+
+ j$.private.deprecateMonkeyPatching(this, ['deprecated']);
+ }
+
+ function indirectCallerFilename(depth) {
+ const frames = new j$.private.StackTrace(new Error()).frames;
+ // The specified frame should always exist except in Jasmine's own tests,
+ // which bypass the global it/describe layer, but could be absent in case
+ // of misconfiguration. Don't crash if it's absent.
+ return frames[depth] && frames[depth].file;
+ }
+
+ return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function(j$) {
+ 'use strict';
+
+ // TODO: remove in 7.0.
+ /**
+ * @name jsApiReporter
+ * @classdesc {@link Reporter} added by default in `boot.js` to record results for retrieval in javascript code. An instance is made available as `jsApiReporter` on the global object.
+ * @class
+ * @hideconstructor
+ * @deprecated In most cases jsApiReporter can simply be removed. If necessary, it can be replaced with a {@link Reporter|custom reporter}.
+ */
+ function JsApiReporter(options) {
+ const timer = options.timer || new j$.Timer();
+ 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';
+ };
+
+ /**
+ * Get the current status for the Jasmine environment.
+ * @name jsApiReporter#status
+ * @since 2.0.0
+ * @function
+ * @return {String} - One of `loaded`, `started`, or `done`
+ */
+ this.status = function() {
+ return status;
+ };
+
+ const suites = [],
+ suites_hash = {};
+
+ this.suiteStarted = function(result) {
+ suites_hash[result.id] = result;
+ };
+
+ this.suiteDone = function(result) {
+ storeSuite(result);
+ };
+
+ /**
+ * Get the results for a set of suites.
+ *
+ * Retrievable in slices for easier serialization.
+ * @name jsApiReporter#suiteResults
+ * @since 2.1.0
+ * @function
+ * @param {Number} index - The position in the suites list to start from.
+ * @param {Number} length - Maximum number of suite results to return.
+ * @return {SuiteResult[]}
+ */
+ this.suiteResults = function(index, length) {
+ return suites.slice(index, index + length);
+ };
+
+ function storeSuite(result) {
+ suites.push(result);
+ suites_hash[result.id] = result;
+ }
+
+ /**
+ * Get all of the suites in a single object, with their `id` as the key.
+ * @name jsApiReporter#suites
+ * @since 2.0.0
+ * @function
+ * @return {Object} - Map of suite id to {@link SuiteResult}
+ */
+ this.suites = function() {
+ return suites_hash;
+ };
+
+ const specs = [];
+
+ this.specDone = function(result) {
+ specs.push(result);
+ };
+
+ /**
+ * Get the results for a set of specs.
+ *
+ * Retrievable in slices for easier serialization.
+ * @name jsApiReporter#specResults
+ * @since 2.0.0
+ * @function
+ * @param {Number} index - The position in the specs list to start from.
+ * @param {Number} length - Maximum number of specs results to return.
+ * @return {SpecDoneEvent[]}
+ */
+ this.specResults = function(index, length) {
+ return specs.slice(index, index + length);
+ };
+
+ /**
+ * Get all spec results.
+ * @name jsApiReporter#specs
+ * @since 2.0.0
+ * @function
+ * @return {SpecDoneEvent[]}
+ */
+ this.specs = function() {
+ return specs;
+ };
+
+ /**
+ * Get the number of milliseconds it took for the full Jasmine suite to run.
+ * @name jsApiReporter#executionTime
+ * @since 2.0.0
+ * @function
+ * @return {Number}
+ */
+ this.executionTime = function() {
+ return executionTime;
+ };
+ }
+
+ return JsApiReporter;
+};
+
+getJasmineRequireObj().AllOf = function(j$) {
+ 'use strict';
+
+ function AllOf() {
+ const expectedValues = Array.from(arguments);
+ if (expectedValues.length === 0) {
+ throw new TypeError(
+ 'jasmine.allOf() expects at least one argument to be passed.'
+ );
+ }
+ this.expectedValues = expectedValues;
+ }
+
+ AllOf.prototype.asymmetricMatch = function(other, matchersUtil) {
+ for (const expectedValue of this.expectedValues) {
+ if (!matchersUtil.equals(other, expectedValue)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ AllOf.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return AllOf;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+ 'use strict';
+
+ function Any(expectedObject) {
+ if (typeof expectedObject === 'undefined') {
+ throw new TypeError(
+ 'jasmine.any() expects to be passed a constructor function. ' +
+ 'Please pass one or use jasmine.anything() to match any object.'
+ );
+ }
+ this.expectedObject = expectedObject;
+ }
+
+ Any.prototype.asymmetricMatch = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedObject == Object) {
+ return other !== null && typeof other == 'object';
+ }
+
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
+
+ if (typeof Symbol != 'undefined' && this.expectedObject == Symbol) {
+ return typeof other == 'symbol';
+ }
+
+ return other instanceof this.expectedObject;
+ };
+
+ Any.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+ 'use strict';
+
+ function Anything() {}
+
+ Anything.prototype.asymmetricMatch = function(other) {
+ return other !== undefined && other !== null;
+ };
+
+ Anything.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+ 'use strict';
+
+ function ArrayContaining(sample) {
+ this.sample = sample;
+ }
+
+ ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!Array.isArray(this.sample)) {
+ throw new Error(
+ 'You must provide an array to arrayContaining, not ' +
+ j$.private.basicPrettyPrinter(this.sample) +
+ '.'
+ );
+ }
+
+ // If the actual parameter is not an array, we can fail immediately, since it couldn't
+ // possibly be an "array containing" anything. However, we also want an empty sample
+ // array to match anything, so we need to double-check we aren't in that case
+ if (!Array.isArray(other) && this.sample.length > 0) {
+ return false;
+ }
+
+ for (const item of this.sample) {
+ if (!matchersUtil.contains(other, item)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ArrayContaining.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return ArrayContaining;
+};
+
+getJasmineRequireObj().ArrayWithExactContents = function(j$) {
+ 'use strict';
+
+ function ArrayWithExactContents(sample) {
+ this.sample = sample;
+ }
+
+ ArrayWithExactContents.prototype.asymmetricMatch = function(
+ other,
+ matchersUtil
+ ) {
+ if (!Array.isArray(this.sample)) {
+ throw new Error(
+ 'You must provide an array to arrayWithExactContents, not ' +
+ j$.private.basicPrettyPrinter(this.sample) +
+ '.'
+ );
+ }
+
+ if (this.sample.length !== other.length) {
+ return false;
+ }
+
+ for (const item of this.sample) {
+ if (!matchersUtil.contains(other, item)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ArrayWithExactContents.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return ArrayWithExactContents;
+};
+
+getJasmineRequireObj().Empty = function(j$) {
+ 'use strict';
+
+ function Empty() {}
+
+ Empty.prototype.asymmetricMatch = function(other) {
+ if (
+ j$.private.isString(other) ||
+ Array.isArray(other) ||
+ j$.private.isTypedArray(other)
+ ) {
+ return other.length === 0;
+ }
+
+ if (j$.private.isMap(other) || j$.private.isSet(other)) {
+ return other.size === 0;
+ }
+
+ if (j$.private.isObject(other)) {
+ return Object.keys(other).length === 0;
+ }
+ return false;
+ };
+
+ Empty.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Empty;
+};
+
+getJasmineRequireObj().Falsy = function(j$) {
+ 'use strict';
+
+ function Falsy() {}
+
+ Falsy.prototype.asymmetricMatch = function(other) {
+ return !other;
+ };
+
+ Falsy.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Falsy;
+};
+
+getJasmineRequireObj().Is = function(j$) {
+ 'use strict';
+
+ class Is {
+ constructor(expected) {
+ this.expected_ = expected;
+ }
+
+ asymmetricMatch(actual) {
+ return actual === this.expected_;
+ }
+
+ jasmineToString(pp) {
+ return ``;
+ }
+ }
+
+ return Is;
+};
+
+getJasmineRequireObj().MapContaining = function(j$) {
+ 'use strict';
+
+ function MapContaining(sample) {
+ if (!j$.private.isMap(sample)) {
+ throw new Error(
+ 'You must provide a map to `mapContaining`, not ' +
+ j$.private.basicPrettyPrinter(sample)
+ );
+ }
+
+ this.sample = sample;
+ }
+
+ MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.private.isMap(other)) {
+ return false;
+ }
+
+ for (const [key, value] of this.sample) {
+ // for each key/value pair in `sample`
+ // there should be at least one pair in `other` whose key and value both match
+ let hasMatch = false;
+ for (const [oKey, oValue] of other) {
+ if (
+ matchersUtil.equals(oKey, key) &&
+ matchersUtil.equals(oValue, value)
+ ) {
+ hasMatch = true;
+ break;
+ }
+ }
+
+ if (!hasMatch) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ MapContaining.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return MapContaining;
+};
+
+getJasmineRequireObj().NotEmpty = function(j$) {
+ 'use strict';
+
+ function NotEmpty() {}
+
+ NotEmpty.prototype.asymmetricMatch = function(other) {
+ if (
+ j$.private.isString(other) ||
+ Array.isArray(other) ||
+ j$.private.isTypedArray(other)
+ ) {
+ return other.length !== 0;
+ }
+
+ if (j$.private.isMap(other) || j$.private.isSet(other)) {
+ return other.size !== 0;
+ }
+
+ if (j$.private.isObject(other)) {
+ return Object.keys(other).length !== 0;
+ }
+
+ return false;
+ };
+
+ NotEmpty.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return NotEmpty;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+ 'use strict';
+
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
+
+ function hasProperty(obj, property) {
+ if (!obj || typeof obj !== 'object') {
+ return false;
+ }
+
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
+ return true;
+ }
+
+ return hasProperty(Object.getPrototypeOf(obj), property);
+ }
+
+ ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (typeof this.sample !== 'object') {
+ throw new Error(
+ "You must provide an object to objectContaining, not '" +
+ this.sample +
+ "'."
+ );
+ }
+ if (typeof other !== 'object') {
+ return false;
+ }
+
+ for (const property in this.sample) {
+ if (
+ !hasProperty(other, property) ||
+ !matchersUtil.equals(this.sample[property], other[property])
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ObjectContaining.prototype.valuesForDiff_ = function(other, pp) {
+ if (!j$.private.isObject(other)) {
+ return {
+ self: this.jasmineToString(pp),
+ other: other
+ };
+ }
+
+ const filteredOther = {};
+ Object.keys(this.sample).forEach(function(k) {
+ // eq short-circuits comparison of objects that have different key sets,
+ // so include all keys even if undefined.
+ filteredOther[k] = other[k];
+ });
+
+ return {
+ self: this.sample,
+ other: filteredOther
+ };
+ };
+
+ ObjectContaining.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return ObjectContaining;
+};
+
+getJasmineRequireObj().SetContaining = function(j$) {
+ 'use strict';
+
+ function SetContaining(sample) {
+ if (!j$.private.isSet(sample)) {
+ throw new Error(
+ 'You must provide a set to `setContaining`, not ' +
+ j$.private.basicPrettyPrinter(sample)
+ );
+ }
+
+ this.sample = sample;
+ }
+
+ SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.private.isSet(other)) {
+ return false;
+ }
+
+ for (const item of this.sample) {
+ // for each item in `sample` there should be at least one matching item in `other`
+ // (not using `matchersUtil.contains` because it compares set members by reference,
+ // not by deep value equality)
+ let hasMatch = false;
+ for (const oItem of other) {
+ if (matchersUtil.equals(oItem, item)) {
+ hasMatch = true;
+ break;
+ }
+ }
+
+ if (!hasMatch) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ SetContaining.prototype.jasmineToString = function(pp) {
+ return '';
+ };
+
+ return SetContaining;
+};
+
+getJasmineRequireObj().StringContaining = function(j$) {
+ 'use strict';
+
+ function StringContaining(expected) {
+ if (!j$.private.isString(expected)) {
+ throw new Error('Expected is not a String');
+ }
+
+ this.expected = expected;
+ }
+
+ StringContaining.prototype.asymmetricMatch = function(other) {
+ if (!j$.private.isString(other)) {
+ // Arrays, etc. don't match no matter what their indexOf returns.
+ return false;
+ }
+
+ return other.indexOf(this.expected) !== -1;
+ };
+
+ StringContaining.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return StringContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+ 'use strict';
+
+ function StringMatching(expected) {
+ if (!j$.private.isString(expected) && !j$.private.isA('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
+
+ this.regexp = new RegExp(expected);
+ }
+
+ StringMatching.prototype.asymmetricMatch = function(other) {
+ return this.regexp.test(other);
+ };
+
+ StringMatching.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return StringMatching;
+};
+
+getJasmineRequireObj().Truthy = function(j$) {
+ 'use strict';
+
+ function Truthy() {}
+
+ Truthy.prototype.asymmetricMatch = function(other) {
+ return !!other;
+ };
+
+ Truthy.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Truthy;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function(j$) {
+ 'use strict';
+
+ function buildExpectationResult(options) {
+ const exceptionFormatter = new j$.private.ExceptionFormatter();
+
+ /**
+ * Describes the result of evaluating an expectation
+ *
+ * @typedef ExpectationResult
+ * @property {String} matcherName - The name of the matcher that was executed for this expectation.
+ * @property {String} message - The failure message for the expectation.
+ * @property {String} stack - The stack trace for the failure if available.
+ * @property {Boolean} passed - Whether the expectation passed or failed.
+ * @property {String|undefined} globalErrorType - The type of an error that
+ * is reported on the top suite. Valid values are undefined, "afterAll",
+ * "load", "lateExpectation", and "lateError".
+ */
+ const result = {
+ matcherName: options.matcherName,
+ message: message(),
+ stack: options.omitStackTrace ? '' : stack(),
+ passed: options.passed,
+ globalErrorType: options.globalErrorType
+ };
+
+ if (options.filename !== undefined) {
+ result.filename = options.filename;
+ }
+ if (options.lineno !== undefined) {
+ result.lineno = options.lineno;
+ }
+
+ if (!result.passed) {
+ if (options.error && !j$.private.isString(options.error)) {
+ if ('code' in options.error) {
+ result.code = options.error.code;
+ }
+
+ if (options.error.code === 'ERR_ASSERTION') {
+ result.matcherName = 'assert ' + options.error.operator;
+ }
+ }
+ }
+
+ return result;
+
+ function message() {
+ if (options.passed) {
+ return 'Passed.';
+ } else if (options.message) {
+ return options.message;
+ } else if (options.error) {
+ return exceptionFormatter.message(options.error);
+ }
+ return '';
+ }
+
+ function stack() {
+ if (options.passed) {
+ return '';
+ }
+
+ let error = options.error;
+
+ if (!error) {
+ if (options.errorForStack) {
+ error = options.errorForStack;
+ } else if (options.stack) {
+ error = options;
+ } else {
+ error = new Error(message());
+ }
+ }
+ // Omit the message from the stack trace because it will be
+ // included elsewhere.
+ return exceptionFormatter.stack(error, { omitMessage: true });
+ }
+ }
+
+ return buildExpectationResult;
+};
+
+getJasmineRequireObj().CallTracker = function(j$) {
+ 'use strict';
+
+ /**
+ * @namespace Spy#calls
+ * @since 2.0.0
+ */
+ function CallTracker() {
+ let calls = [];
+ const opts = {};
+
+ this.track = function(context) {
+ if (opts.cloneArgs) {
+ context.args = opts.argsCloner(context.args);
+ }
+ calls.push(context);
+ };
+
+ /**
+ * Check whether this spy has been invoked.
+ * @name Spy#calls#any
+ * @since 2.0.0
+ * @function
+ * @return {Boolean}
+ */
+ this.any = function() {
+ return !!calls.length;
+ };
+
+ /**
+ * Get the number of invocations of this spy.
+ * @name Spy#calls#count
+ * @since 2.0.0
+ * @function
+ * @return {Integer}
+ */
+ this.count = function() {
+ return calls.length;
+ };
+
+ /**
+ * Get the arguments that were passed to a specific invocation of this spy.
+ * @name Spy#calls#argsFor
+ * @since 2.0.0
+ * @function
+ * @param {Integer} index The 0-based invocation index.
+ * @return {Array}
+ */
+ this.argsFor = function(index) {
+ const call = calls[index];
+ return call ? call.args : [];
+ };
+
+ /**
+ * Get the "this" object that was passed to a specific invocation of this spy.
+ * @name Spy#calls#thisFor
+ * @since 3.8.0
+ * @function
+ * @param {Integer} index The 0-based invocation index.
+ * @return {Object?}
+ */
+ this.thisFor = function(index) {
+ const call = calls[index];
+ return call ? call.object : undefined;
+ };
+
+ /**
+ * Get the raw calls array for this spy.
+ * @name Spy#calls#all
+ * @since 2.0.0
+ * @function
+ * @return {Spy.callData[]}
+ */
+ this.all = function() {
+ return calls;
+ };
+
+ /**
+ * Get all of the arguments for each invocation of this spy in the order they were received.
+ * @name Spy#calls#allArgs
+ * @since 2.0.0
+ * @function
+ * @return {Array}
+ */
+ this.allArgs = function() {
+ return calls.map(c => c.args);
+ };
+
+ /**
+ * Get the first invocation of this spy.
+ * @name Spy#calls#first
+ * @since 2.0.0
+ * @function
+ * @return {ObjecSpy.callData}
+ */
+ this.first = function() {
+ return calls[0];
+ };
+
+ /**
+ * Get the most recent invocation of this spy.
+ * @name Spy#calls#mostRecent
+ * @since 2.0.0
+ * @function
+ * @return {ObjecSpy.callData}
+ */
+ this.mostRecent = function() {
+ return calls[calls.length - 1];
+ };
+
+ /**
+ * Reset this spy as if it has never been called.
+ * @name Spy#calls#reset
+ * @since 2.0.0
+ * @function
+ */
+ this.reset = function() {
+ calls = [];
+ };
+
+ /**
+ * Set this spy to do a clone of arguments passed to each invocation.
+ * @name Spy#calls#saveArgumentsByValue
+ * @since 2.5.0
+ * @param {Function} [argsCloner] A function to use to clone the arguments. Defaults to a shallow cloning function.
+ * @function
+ */
+ this.saveArgumentsByValue = function(
+ argsCloner = j$.private.util.cloneArgs
+ ) {
+ opts.cloneArgs = true;
+ opts.argsCloner = argsCloner;
+ };
+
+ this.unverifiedCount = function() {
+ return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
+ };
+ }
+
+ return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function(j$) {
+ 'use strict';
+
+ /* global process */
+ const NODE_JS =
+ typeof process !== 'undefined' &&
+ process.versions &&
+ typeof process.versions.node === 'string';
+
+ const IsMockClockTimingFn = Symbol('IsMockClockTimingFn');
+
+ /**
+ * @class Clock
+ * @since 1.3.0
+ * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ * _Note:_ Do not construct this directly. You can get the current clock with
+ * {@link jasmine.clock}.
+ * @hideconstructor
+ */
+ function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
+ const realTimingFunctions = {
+ setTimeout: global.setTimeout,
+ clearTimeout: global.clearTimeout,
+ setInterval: global.setInterval,
+ clearInterval: global.clearInterval
+ };
+ const fakeTimingFunctions = {
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout,
+ setInterval: setInterval,
+ clearInterval: clearInterval
+ };
+ let installed = false;
+ let delayedFunctionScheduler;
+ let timer;
+ // Tracks how the clock ticking behaves.
+ // By default, the clock only ticks when the user manually calls a tick method.
+ // There is also an 'auto' mode which will advance the clock automatically to
+ // to the next task. Once enabled, there is currently no mechanism for users
+ // to disable the auto ticking.
+ let tickMode = {
+ mode: 'manual',
+ counter: 0
+ };
+
+ this.FakeTimeout = FakeTimeout;
+
+ /**
+ * Install the mock clock over the built-in methods.
+ * @name Clock#install
+ * @since 2.0.0
+ * @function
+ * @return {Clock}
+ */
+ this.install = function() {
+ if (!originalTimingFunctionsIntact()) {
+ throw new Error(
+ 'Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'
+ );
+ }
+ replace(global, fakeTimingFunctions);
+ timer = fakeTimingFunctions;
+ delayedFunctionScheduler = delayedFunctionSchedulerFactory();
+ installed = true;
+
+ return this;
+ };
+
+ /**
+ * Uninstall the mock clock, returning the built-in methods to their places.
+ * @name Clock#uninstall
+ * @since 2.0.0
+ * @function
+ */
+ this.uninstall = function() {
+ // Ensure auto ticking loop is aborted when clock is uninstalled
+ if (tickMode.mode === 'auto') {
+ tickMode = { mode: 'manual', counter: tickMode.counter + 1 };
+ }
+ delayedFunctionScheduler = null;
+ mockDate.uninstall();
+ replace(global, realTimingFunctions);
+
+ timer = realTimingFunctions;
+ installed = false;
+ };
+
+ /**
+ * Execute a function with a mocked Clock
+ *
+ * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes.
+ * @name Clock#withMock
+ * @since 2.3.0
+ * @function
+ * @param {Function} closure The function to be called.
+ */
+ this.withMock = function(closure) {
+ this.install();
+ try {
+ closure();
+ } finally {
+ this.uninstall();
+ }
+ };
+
+ /**
+ * Instruct the installed Clock to also mock the date returned by `new Date()`
+ * @name Clock#mockDate
+ * @since 2.1.0
+ * @function
+ * @param {Date} [initialDate=now] The `Date` to provide.
+ */
+ this.mockDate = function(initialDate) {
+ mockDate.install(initialDate);
+ };
+
+ this.setTimeout = function(fn, delay, params) {
+ return Function.prototype.apply.apply(timer.setTimeout, [
+ global,
+ arguments
+ ]);
+ };
+
+ this.setInterval = function(fn, delay, params) {
+ return Function.prototype.apply.apply(timer.setInterval, [
+ global,
+ arguments
+ ]);
+ };
+
+ this.clearTimeout = function(id) {
+ return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+ };
+
+ this.clearInterval = function(id) {
+ return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+ };
+
+ /**
+ * Tick the Clock forward, running any enqueued timeouts along the way
+ * @name Clock#tick
+ * @since 1.3.0
+ * @function
+ * @param {int} millis The number of milliseconds to tick.
+ */
+ this.tick = function(millis) {
+ if (installed) {
+ delayedFunctionScheduler.tick(millis, function(millis) {
+ mockDate.tick(millis);
+ });
+ } else {
+ throw new Error(
+ 'Mock clock is not installed, use jasmine.clock().install()'
+ );
+ }
+ };
+
+ /**
+ * Updates the clock to automatically advance time.
+ *
+ * With this mode, the clock advances to the first scheduled timer and fires it, in a loop.
+ * Between each timer, it will also break the event loop, allowing any scheduled promise
+callbacks to execute _before_ running the next one.
+ *
+ * This mode allows tests to be authored in a way that does not need to be aware of the
+ * mock clock. Consequently, tests which have been authored without a mock clock installed
+ * can one with auto tick enabled without any other updates to the test logic.
+ *
+ * In many cases, this can greatly improve test execution speed because asynchronous tasks
+ * will execute as quickly as possible rather than waiting real time to complete.
+ *
+ * Furthermore, tests can be authored in a consistent manner. They can always be written in an asynchronous style
+ * rather than having `tick` sprinkled throughout the tests with mock time in order to manually
+ * advance the clock.
+ *
+ * When auto tick is enabled, `tick` can still be used to synchronously advance the clock if necessary.
+ * @name Clock#autoTick
+ * @function
+ * @since 5.7.0
+ */
+ this.autoTick = function() {
+ if (tickMode.mode === 'auto') {
+ return;
+ }
+
+ tickMode = { mode: 'auto', counter: tickMode.counter + 1 };
+ advanceUntilModeChanges();
+ };
+
+ setTimeout[IsMockClockTimingFn] = true;
+ clearTimeout[IsMockClockTimingFn] = true;
+ setInterval[IsMockClockTimingFn] = true;
+ clearInterval[IsMockClockTimingFn] = true;
+
+ j$.private.deprecateMonkeyPatching(this);
+
+ return this;
+
+ // Advances the Clock's time until the mode changes.
+ //
+ // The time is advanced asynchronously, giving microtasks and events a chance
+ // to run before each timer runs.
+ //
+ // @function
+ // @return {!Promise}
+ async function advanceUntilModeChanges() {
+ if (!installed) {
+ throw new Error(
+ 'Mock clock is not installed, use jasmine.clock().install()'
+ );
+ }
+ const { counter } = tickMode;
+
+ while (true) {
+ await newMacrotask();
+
+ if (
+ tickMode.counter !== counter ||
+ !installed ||
+ delayedFunctionScheduler === null
+ ) {
+ return;
+ }
+
+ if (!delayedFunctionScheduler.isEmpty()) {
+ delayedFunctionScheduler.runNextQueuedFunction(function(millis) {
+ mockDate.tick(millis);
+ });
+ }
+ }
+ }
+
+ // Waits until a new macro task.
+ //
+ // Used with autoTick(), which is meant to act when the test is waiting, we need
+ // to insert ourselves in the macro task queue.
+ //
+ // @return {!Promise}
+ async function newMacrotask() {
+ if (NODE_JS) {
+ // setImmediate is generally faster than setTimeout in Node
+ // https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#setimmediate-vs-settimeout
+ return new Promise(resolve => void setImmediate(resolve));
+ }
+
+ // MessageChannel ensures that setTimeout is not throttled to 4ms.
+ // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
+ // https://stackblitz.com/edit/stackblitz-starters-qtlpcc
+ // Note: This trick does not work in Safari, which will still throttle the setTimeout
+ const channel = new MessageChannel();
+ await new Promise(resolve => {
+ channel.port1.onmessage = resolve;
+ channel.port2.postMessage(undefined);
+ });
+ channel.port1.close();
+ channel.port2.close();
+ // setTimeout ensures that we interleave with other setTimeouts.
+ await new Promise(resolve => {
+ realTimingFunctions.setTimeout.call(global, resolve);
+ });
+ }
+
+ function originalTimingFunctionsIntact() {
+ return (
+ global.setTimeout === realTimingFunctions.setTimeout &&
+ global.clearTimeout === realTimingFunctions.clearTimeout &&
+ global.setInterval === realTimingFunctions.setInterval &&
+ global.clearInterval === realTimingFunctions.clearInterval
+ );
+ }
+
+ function replace(dest, source) {
+ for (const prop in source) {
+ dest[prop] = source[prop];
+ }
+ }
+
+ function setTimeout(fn, delay) {
+ if (!NODE_JS) {
+ return delayedFunctionScheduler.scheduleFunction(
+ fn,
+ delay,
+ argSlice(arguments, 2)
+ );
+ }
+
+ const timeout = new FakeTimeout();
+
+ delayedFunctionScheduler.scheduleFunction(
+ fn,
+ delay,
+ argSlice(arguments, 2),
+ false,
+ timeout
+ );
+
+ return timeout;
+ }
+
+ function clearTimeout(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function setInterval(fn, interval) {
+ if (!NODE_JS) {
+ return delayedFunctionScheduler.scheduleFunction(
+ fn,
+ interval,
+ argSlice(arguments, 2),
+ true
+ );
+ }
+
+ const timeout = new FakeTimeout();
+
+ delayedFunctionScheduler.scheduleFunction(
+ fn,
+ interval,
+ argSlice(arguments, 2),
+ true,
+ timeout
+ );
+
+ return timeout;
+ }
+
+ function clearInterval(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function argSlice(argsObj, n) {
+ return Array.prototype.slice.call(argsObj, n);
+ }
+ }
+
+ /**
+ * Mocks Node.js Timeout class
+ */
+ function FakeTimeout() {}
+
+ FakeTimeout.prototype.ref = function() {
+ return this;
+ };
+
+ FakeTimeout.prototype.unref = function() {
+ return this;
+ };
+
+ Clock.IsMockClockTimingFn = IsMockClockTimingFn;
+ return Clock;
+};
+
+getJasmineRequireObj().CompleteOnFirstErrorSkipPolicy = function(j$) {
+ 'use strict';
+
+ function CompleteOnFirstErrorSkipPolicy(queueableFns) {
+ this.queueableFns_ = queueableFns;
+ this.erroredFnIx_ = null;
+ }
+
+ CompleteOnFirstErrorSkipPolicy.prototype.skipTo = function(lastRanFnIx) {
+ let i;
+
+ for (
+ i = lastRanFnIx + 1;
+ i < this.queueableFns_.length && this.shouldSkip_(i);
+ i++
+ ) {}
+ return i;
+ };
+
+ CompleteOnFirstErrorSkipPolicy.prototype.fnErrored = function(fnIx) {
+ this.erroredFnIx_ = fnIx;
+ };
+
+ CompleteOnFirstErrorSkipPolicy.prototype.shouldSkip_ = function(fnIx) {
+ if (this.erroredFnIx_ === null) {
+ return false;
+ }
+
+ const fn = this.queueableFns_[fnIx];
+ const candidateSuite = fn.suite;
+ const errorSuite = this.queueableFns_[this.erroredFnIx_].suite;
+ const wasCleanupFn =
+ fn.type === 'afterEach' ||
+ fn.type === 'afterAll' ||
+ fn.type === 'specCleanup';
+ return (
+ !wasCleanupFn ||
+ (candidateSuite && isDescendent(candidateSuite, errorSuite))
+ );
+ };
+
+ function isDescendent(candidate, ancestor) {
+ if (!candidate.parentSuite) {
+ return false;
+ } else if (candidate.parentSuite === ancestor) {
+ return true;
+ } else {
+ return isDescendent(candidate.parentSuite, ancestor);
+ }
+ }
+
+ return CompleteOnFirstErrorSkipPolicy;
+};
+
+getJasmineRequireObj().Configuration = function(j$) {
+ 'use strict';
+
+ /**
+ * 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 defaultConfig = {
+ /**
+ * 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
+ *
+ *
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.