From 065b21b4bdd49ffca494ff905de29bfc500ed535 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Tue, 1 May 2018 21:04:21 +0100 Subject: [PATCH] Upgrade QUnit from 2.4.0 to 2.6.0 Source https://code.jquery.com/qunit/qunit-2.6.0.js https://code.jquery.com/qunit/qunit-2.6.0.css Changelog https://github.com/qunitjs/qunit/blob/2.6.0/History.md Highlights: * 2.4.1: Fixed various bugs in HTML interface. * 2.5.0: Added assert.rejects. * 2.5.1: Fixed HTML reporter to reset attributes on qunit-fixture. * 2.6.0: Changed behaviour to fail if no test suites exist. Change-Id: I24120a74094db358f02f9fc1935920c43a0a7ced --- RELEASE-NOTES-1.32 | 2 +- resources/lib/qunitjs/qunit.css | 4 +- resources/lib/qunitjs/qunit.js | 348 ++++++++++++++++++++++++++------ 3 files changed, 285 insertions(+), 69 deletions(-) diff --git a/RELEASE-NOTES-1.32 b/RELEASE-NOTES-1.32 index 6b3b1298ca..92379c4df3 100644 --- a/RELEASE-NOTES-1.32 +++ b/RELEASE-NOTES-1.32 @@ -22,7 +22,7 @@ production. * … ==== Upgraded external libraries ==== -* … +* Updated QUnit from 2.4.0 to 2.6.0. ==== New external libraries ==== * … diff --git a/resources/lib/qunitjs/qunit.css b/resources/lib/qunitjs/qunit.css index 4749222173..859544eb22 100644 --- a/resources/lib/qunitjs/qunit.css +++ b/resources/lib/qunitjs/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 2.4.0 + * QUnit 2.6.0 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-07-08T15:20Z + * Date: 2018-03-27T02:18Z */ /** Font Family and Sizes */ diff --git a/resources/lib/qunitjs/qunit.js b/resources/lib/qunitjs/qunit.js index bb8f31d680..d7b22ddf84 100644 --- a/resources/lib/qunitjs/qunit.js +++ b/resources/lib/qunitjs/qunit.js @@ -1,17 +1,17 @@ /*! - * QUnit 2.4.0 + * QUnit 2.6.0 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-07-08T15:20Z + * Date: 2018-03-27T02:18Z */ (function (global$1) { 'use strict'; - global$1 = global$1 && 'default' in global$1 ? global$1['default'] : global$1; + global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1; var window = global$1.window; var self$1 = global$1.self; @@ -1097,58 +1097,89 @@ var priorityCount = 0; var unitSampler = void 0; + // This is a queue of functions that are tasks within a single test. + // After tests are dequeued from config.queue they are expanded into + // a set of tasks in this queue. + var taskQueue = []; + /** - * Advances the ProcessingQueue to the next item if it is ready. - * @param {Boolean} last + * Advances the taskQueue to the next task. If the taskQueue is empty, + * process the testQueue */ function advance() { + advanceTaskQueue(); + + if (!taskQueue.length) { + advanceTestQueue(); + } + } + + /** + * Advances the taskQueue to the next task if it is ready and not empty. + */ + function advanceTaskQueue() { var start = now(); config.depth = (config.depth || 0) + 1; - while (config.queue.length && !config.blocking) { + while (taskQueue.length && !config.blocking) { var elapsedTime = now() - start; if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) { - if (priorityCount > 0) { - priorityCount--; - } - - config.queue.shift()(); + var task = taskQueue.shift(); + task(); } else { - setTimeout(advance, 13); + setTimeout(advance); break; } } config.depth--; + } + /** + * Advance the testQueue to the next test to process. Call done() if testQueue completes. + */ + function advanceTestQueue() { if (!config.blocking && !config.queue.length && config.depth === 0) { done(); + return; } - } - function addToQueueImmediate(callback) { - if (objectType(callback) === "array") { - while (callback.length) { - addToQueueImmediate(callback.pop()); - } + var testTasks = config.queue.shift(); + addToTaskQueue(testTasks()); - return; + if (priorityCount > 0) { + priorityCount--; } - config.queue.unshift(callback); - priorityCount++; + advance(); + } + + /** + * Enqueue the tasks for a test into the task queue. + * @param {Array} tasksArray + */ + function addToTaskQueue(tasksArray) { + taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray)); } /** - * Adds a function to the ProcessingQueue for execution. - * @param {Function|Array} callback - * @param {Boolean} priority + * Return the number of tasks remaining in the task queue to be processed. + * @return {Number} + */ + function taskQueueLength() { + return taskQueue.length; + } + + /** + * Adds a test to the TestQueue for execution. + * @param {Function} testTasksFunc + * @param {Boolean} prioritize * @param {String} seed */ - function addToQueue(callback, prioritize, seed) { + function addToTestQueue(testTasksFunc, prioritize, seed) { if (prioritize) { - config.queue.splice(priorityCount++, 0, callback); + config.queue.splice(priorityCount++, 0, testTasksFunc); } else if (seed) { if (!unitSampler) { unitSampler = unitSamplerGenerator(seed); @@ -1156,9 +1187,9 @@ // Insert into a random position after all prioritized items var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); - config.queue.splice(priorityCount + index, 0, callback); + config.queue.splice(priorityCount + index, 0, testTasksFunc); } else { - config.queue.push(callback); + config.queue.push(testTasksFunc); } } @@ -1196,6 +1227,27 @@ var runtime = now() - config.started; var passed = config.stats.all - config.stats.bad; + if (config.stats.all === 0) { + + if (config.filter && config.filter.length) { + throw new Error("No tests matched the filter \"" + config.filter + "\"."); + } + + if (config.module && config.module.length) { + throw new Error("No tests matched the module \"" + config.module + "\"."); + } + + if (config.moduleId && config.moduleId.length) { + throw new Error("No tests matched the moduleId \"" + config.moduleId + "\"."); + } + + if (config.testId && config.testId.length) { + throw new Error("No tests matched the testId \"" + config.testId + "\"."); + } + + throw new Error("No tests were run."); + } + emit("runEnd", globalSuite.end(true)); runLoggingCallbacks("done", { passed: passed, @@ -1218,9 +1270,9 @@ var ProcessingQueue = { finished: false, - add: addToQueue, - addImmediate: addToQueueImmediate, - advance: advance + add: addToTestQueue, + advance: advance, + taskCount: taskQueueLength }; var TestReport = function () { @@ -1388,6 +1440,13 @@ this.async = false; this.expected = 0; } else { + if (typeof this.callback !== "function") { + var method = this.todo ? "todo" : "test"; + + // eslint-disable-next-line max-len + throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")"); + } + this.assert = new Assert(this); } } @@ -1500,7 +1559,9 @@ _this.preserveEnvironment = true; } - if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) { + // The 'after' hook should only execute when there are not tests left and + // when the 'after' and 'finish' tasks are the only tasks left to process + if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) { return; } @@ -1547,6 +1608,12 @@ finish: function finish() { config.current = this; + + if (this.steps.length) { + var stepsList = this.steps.join(", "); + this.pushFailure("Expected assert.verifySteps() to be called before end of test " + ("after using assert.step(). Unverified steps: " + stepsList), this.stack); + } + if (config.requireExpects && this.expected === null) { this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack); } else if (this.expected !== null && this.expected !== this.assertions.length) { @@ -1653,15 +1720,13 @@ } function runTest() { - - // Each of these can by async - ProcessingQueue.addImmediate([function () { + return [function () { test.before(); - }, test.hooks("before"), function () { + }].concat(toConsumableArray(test.hooks("before")), [function () { test.preserveTestEnvironment(); - }, test.hooks("beforeEach"), function () { + }], toConsumableArray(test.hooks("beforeEach")), [function () { test.run(); - }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () { + }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () { test.after(); }, function () { test.finish(); @@ -1686,7 +1751,7 @@ pushResult: function pushResult(resultInfo) { if (this !== config.current) { - throw new Error("Assertion occured after test had finished."); + throw new Error("Assertion occurred after test had finished."); } // Destructure of resultInfo = { result, actual, expected, message, negative } @@ -1697,13 +1762,16 @@ result: resultInfo.result, message: resultInfo.message, actual: resultInfo.actual, - expected: resultInfo.expected, testId: this.testId, negative: resultInfo.negative || false, runtime: now() - this.started, todo: !!this.todo }; + if (hasOwn.call(resultInfo, "expected")) { + details.expected = resultInfo.expected; + } + if (!resultInfo.result) { source = resultInfo.source || sourceFromStacktrace(); @@ -1729,7 +1797,6 @@ result: false, message: message || "error", actual: actual || null, - expected: null, source: source }); }, @@ -1765,18 +1832,24 @@ then = promise.then; if (objectType(then) === "function") { resume = internalStop(test); - then.call(promise, function () { - resume(); - }, function (error) { - message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error); - test.pushFailure(message, extractStacktrace(error, 0)); - - // Else next test will carry the responsibility - saveGlobal(); - - // Unblock - resume(); - }); + if (config.notrycatch) { + then.call(promise, function () { + resume(); + }); + } else { + then.call(promise, function () { + resume(); + }, function (error) { + message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error); + test.pushFailure(message, extractStacktrace(error, 0)); + + // Else next test will carry the responsibility + saveGlobal(); + + // Unblock + internalRecover(test); + }); + } } } }, @@ -2040,7 +2113,7 @@ } begin(); - }, 13); + }); } else { begin(); } @@ -2125,13 +2198,21 @@ }, { key: "step", value: function step(message) { + var assertionMessage = message; var result = !!message; this.test.steps.push(message); + if (objectType(message) === "undefined" || message === "") { + assertionMessage = "You must provide a message to assert.step"; + } else if (objectType(message) !== "string") { + assertionMessage = "You must provide a string value to assert.step"; + result = false; + } + return this.pushResult({ result: result, - message: message || "You must provide a message to assert.step" + message: assertionMessage }); } @@ -2140,7 +2221,11 @@ }, { key: "verifySteps", value: function verifySteps(steps, message) { - this.deepEqual(this.test.steps, steps, message); + + // Since the steps array is just string values, we can clone with slice + var actualStepsClone = this.test.steps.slice(); + this.deepEqual(actualStepsClone, steps, message); + this.test.steps.length = 0; } // Specify the number of expected assertions to guarantee that failed test @@ -2418,6 +2503,98 @@ message: message }); } + }, { + key: "rejects", + value: function rejects(promise, expected, message) { + var result = false; + + var currentTest = this instanceof Assert && this.test || config.current; + + // 'expected' is optional unless doing string comparison + if (objectType(expected) === "string") { + if (message === undefined) { + message = expected; + expected = undefined; + } else { + message = "assert.rejects does not accept a string value for the expected " + "argument.\nUse a non-string object value (e.g. validator function) instead " + "if necessary."; + + currentTest.assert.pushResult({ + result: false, + message: message + }); + + return; + } + } + + var then = promise && promise.then; + if (objectType(then) !== "function") { + var _message = "The value provided to `assert.rejects` in " + "\"" + currentTest.testName + "\" was not a promise."; + + currentTest.assert.pushResult({ + result: false, + message: _message, + actual: promise + }); + + return; + } + + var done = this.async(); + + return then.call(promise, function handleFulfillment() { + var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject."; + + currentTest.assert.pushResult({ + result: false, + message: message, + actual: promise + }); + + done(); + }, function handleRejection(actual) { + var expectedType = objectType(expected); + + // We don't want to validate + if (expected === undefined) { + result = true; + expected = actual; + + // Expected is a regexp + } else if (expectedType === "regexp") { + result = expected.test(errorString(actual)); + + // Expected is a constructor, maybe an Error constructor + } else if (expectedType === "function" && actual instanceof expected) { + result = true; + + // Expected is an Error object + } else if (expectedType === "object") { + result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; + + // Expected is a validation function which returns true if validation passed + } else { + if (expectedType === "function") { + result = expected.call({}, actual) === true; + expected = null; + + // Expected is some other invalid type + } else { + result = false; + message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + "."; + } + } + + currentTest.assert.pushResult({ + result: result, + actual: actual, + expected: expected, + message: message + }); + + done(); + }); + } }]); return Assert; }(); @@ -2633,6 +2810,25 @@ return false; } + // Handle an unhandled rejection + function onUnhandledRejection(reason) { + var resultInfo = { + result: false, + message: reason.message || "error", + actual: reason, + source: reason.stack || sourceFromStacktrace(3) + }; + + var currentTest = config.current; + if (currentTest) { + currentTest.assert.pushResult(resultInfo); + } else { + test("global failure", extend(function (assert) { + assert.pushResult(resultInfo); + }, { validTest: true })); + } + } + var focused = false; var QUnit = {}; var globalSuite = new SuiteReport(); @@ -2650,7 +2846,7 @@ QUnit.isLocal = !(defined.document && window.location.protocol !== "file:"); // Expose the current QUnit version - QUnit.version = "2.4.0"; + QUnit.version = "2.6.0"; function createModule(name, testEnvironment, modifiers) { var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; @@ -2868,7 +3064,9 @@ return sourceFromStacktrace(offset); }, - onError: onError + onError: onError, + + onUnhandledRejection: onUnhandledRejection }); QUnit.pushFailure = pushFailure; @@ -2886,7 +3084,7 @@ if (defined.setTimeout) { setTimeout(function () { begin(); - }, 13); + }); } else { begin(); } @@ -2955,7 +3153,7 @@ var fixture = document.getElementById("qunit-fixture"); if (fixture) { - config.fixture = fixture.innerHTML; + config.fixture = fixture.cloneNode(true); } } @@ -2968,8 +3166,17 @@ } var fixture = document.getElementById("qunit-fixture"); - if (fixture) { - fixture.innerHTML = config.fixture; + var resetFixtureType = _typeof(config.fixture); + if (resetFixtureType === "string") { + + // support user defined values for `config.fixture` + var newFixture = document.createElement("div"); + newFixture.setAttribute("id", "qunit-fixture"); + newFixture.innerHTML = config.fixture; + fixture.parentNode.replaceChild(newFixture, fixture); + } else { + var clonedFixture = config.fixture.cloneNode(true); + fixture.parentNode.replaceChild(clonedFixture, fixture); } } @@ -3284,7 +3491,8 @@ // Skip inherited or undefined properties if (hasOwn.call(params, key) && params[key] !== undefined) { - // Output a parameter for each value of this key (but usually just one) + // Output a parameter for each value of this key + // (but usually just one) arrValue = [].concat(params[key]); for (i = 0; i < arrValue.length; i++) { querystring += encodeURIComponent(key); @@ -3705,7 +3913,8 @@ if (config.altertitle && document$$1.title) { // Show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset + // use escape sequences in case file gets loaded with non-utf-8 + // charset document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" "); } @@ -3743,7 +3952,7 @@ if (running) { bad = QUnit.config.reorder && details.previousFailure; - running.innerHTML = (bad ? "Rerunning previously failed test:
" : "Running:
") + getNameHtml(details.name, details.module); + running.innerHTML = [bad ? "Rerunning previously failed test:
" : "Running:
", getNameHtml(details.name, details.module)].join(""); } }); @@ -3974,6 +4183,11 @@ return ret; }; + + // Listen for unhandled rejections, and call QUnit.onUnhandledRejection + window.addEventListener("unhandledrejection", function (event) { + QUnit.onUnhandledRejection(event.reason); + }); })(); /* @@ -4874,7 +5088,9 @@ line = text.substring(lineStart, lineEnd + 1); lineStart = lineEnd + 1; - if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) { + var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined; + + if (lineHashExists) { chars += String.fromCharCode(lineHash[line]); } else { chars += String.fromCharCode(lineArrayLength); -- 2.20.1