aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorBardi Harborow <[email protected]>2017-03-20 05:07:18 +0000
committerBardi Harborow <[email protected]>2017-03-20 17:37:05 +1100
commit09fb2b9af3ee7ef0e6a23e913ac74967d4a08d24 (patch)
tree776201277e426bafb96903cc7f2533ba00459c06 /js
parent38f89726b957030de241631ea6fe06460af497e5 (diff)
downloadbootstrap-09fb2b9af3ee7ef0e6a23e913ac74967d4a08d24.tar.xz
bootstrap-09fb2b9af3ee7ef0e6a23e913ac74967d4a08d24.zip
Update dependencies.
Diffstat (limited to 'js')
-rw-r--r--js/tests/vendor/qunit.css9
-rw-r--r--js/tests/vendor/qunit.js823
2 files changed, 656 insertions, 176 deletions
diff --git a/js/tests/vendor/qunit.css b/js/tests/vendor/qunit.css
index 50fe5c290..90e1269f1 100644
--- a/js/tests/vendor/qunit.css
+++ b/js/tests/vendor/qunit.css
@@ -1,12 +1,12 @@
/*!
- * QUnit 2.1.1
+ * QUnit 2.2.0
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
- * Date: 2017-01-06T01:52Z
+ * Date: 2017-03-11T16:19Z
*/
/** Font Family and Sizes */
@@ -384,6 +384,7 @@
background-color: #EBECE9;
}
+#qunit-tests .qunit-todo-label,
#qunit-tests .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
@@ -394,6 +395,10 @@
margin: -0.4em 0.4em -0.4em 0;
}
+#qunit-tests .qunit-todo-label {
+ background-color: #EEE;
+}
+
/** Result */
#qunit-testresult {
diff --git a/js/tests/vendor/qunit.js b/js/tests/vendor/qunit.js
index e90caadfc..b399f417c 100644
--- a/js/tests/vendor/qunit.js
+++ b/js/tests/vendor/qunit.js
@@ -1,12 +1,12 @@
/*!
- * QUnit 2.1.1
+ * QUnit 2.2.0
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
- * Date: 2017-01-06T01:52Z
+ * Date: 2017-03-11T16:19Z
*/
(function (global$1) {
'use strict';
@@ -20,7 +20,17 @@
var document = window && window.document;
var navigator = window && window.navigator;
- var sessionStorage = window && window.sessionStorage;
+
+ var sessionStorage = function () {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem(x, x);
+ sessionStorage.removeItem(x);
+ return sessionStorage;
+ } catch (e) {
+ return undefined;
+ }
+ }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
@@ -141,19 +151,16 @@
return result;
}
- // From jquery.js
+ /**
+ * Determines whether an element exists in a given array or not.
+ *
+ * @method inArray
+ * @param {Any} elem
+ * @param {Array} array
+ * @return {Boolean}
+ */
function inArray(elem, array) {
- if (array.indexOf) {
- return array.indexOf(elem);
- }
-
- for (var i = 0, length = array.length; i < length; i++) {
- if (array[i] === elem) {
- return i;
- }
- }
-
- return -1;
+ return array.indexOf(elem) !== -1;
}
/**
@@ -232,25 +239,25 @@
}
// Test for equality any JavaScript type.
- // Author: Philippe Rathé <[email protected]>
+ // Authors: Philippe Rathé <[email protected]>, David Chan <[email protected]>
var equiv = (function () {
- // Stack to decide between skip/abort functions
- var callers = [];
-
- // Stack to avoiding loops from circular referencing
- var parents = [];
- var parentsB = [];
+ // Value pairs queued for comparison. Used for breadth-first processing order, recursion
+ // detection and avoiding repeated comparison (see below for details).
+ // Elements are { a: val, b: val }.
+ var pairs = [];
var getProto = Object.getPrototypeOf || function (obj) {
return obj.__proto__;
};
- function useStrictEquality(b, a) {
+ function useStrictEquality(a, b) {
- // To catch short annotation VS 'new' annotation of a declaration. e.g.:
+ // This only gets called if a and b are not strict equal, and is used to compare on
+ // the primitive values inside object wrappers. For example:
// `var i = 1;`
// `var j = new Number(1);`
+ // Neither a nor b can be null, as a !== b and they have the same type.
if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
a = a.valueOf();
}
@@ -293,6 +300,31 @@
return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
}
+ function isContainer(val) {
+ return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
+ }
+
+ function breadthFirstCompareChild(a, b) {
+
+ // If a is a container not reference-equal to b, postpone the comparison to the
+ // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
+ // over the pair.
+ if (a === b) {
+ return true;
+ }
+ if (!isContainer(a)) {
+ return typeEquiv(a, b);
+ }
+ if (pairs.every(function (pair) {
+ return pair.a !== a || pair.b !== b;
+ })) {
+
+ // Not yet started comparing this pair
+ pairs.push({ a: a, b: b });
+ }
+ return true;
+ }
+
var callbacks = {
"string": useStrictEquality,
"boolean": useStrictEquality,
@@ -306,24 +338,20 @@
return true;
},
- "regexp": function regexp(b, a) {
+ "regexp": function regexp(a, b) {
return a.source === b.source &&
// Include flags in the comparison
getRegExpFlags(a) === getRegExpFlags(b);
},
- // - skip when the property is a method of an instance (OOP)
- // - abort otherwise,
- // initial === would have catch identical references anyway
- "function": function _function(b, a) {
-
- var caller = callers[callers.length - 1];
- return caller !== Object && typeof caller !== "undefined" && a.toString() === b.toString();
+ // abort (identical references / instance methods were skipped earlier)
+ "function": function _function() {
+ return false;
},
- "array": function array(b, a) {
- var i, j, len, loop, aCircular, bCircular;
+ "array": function array(a, b) {
+ var i, len;
len = a.length;
if (len !== b.length) {
@@ -332,50 +360,63 @@
return false;
}
- // Track reference to avoid circular references
- parents.push(a);
- parentsB.push(b);
for (i = 0; i < len; i++) {
- loop = false;
- for (j = 0; j < parents.length; j++) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
- if (aCircular || bCircular) {
- if (a[i] === b[i] || aCircular && bCircular) {
- loop = true;
- } else {
- parents.pop();
- parentsB.pop();
- return false;
- }
- }
- }
- if (!loop && !innerEquiv(a[i], b[i])) {
- parents.pop();
- parentsB.pop();
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
return false;
}
}
- parents.pop();
- parentsB.pop();
return true;
},
- "set": function set$$1(b, a) {
+ // Define sets a and b to be equivalent if for each element aVal in a, there
+ // is some element bVal in b such that aVal and bVal are equivalent. Element
+ // repetitions are not counted, so these are equivalent:
+ // a = new Set( [ {}, [], [] ] );
+ // b = new Set( [ {}, {}, [] ] );
+ "set": function set$$1(a, b) {
var innerEq,
outerEq = true;
if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) element to two equivalent sets can
+ // make them non-equivalent.
return false;
}
a.forEach(function (aVal) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Set is unused)
+ if (!outerEq) {
+ return;
+ }
+
innerEq = false;
b.forEach(function (bVal) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
if (innerEquiv(bVal, aVal)) {
innerEq = true;
}
+
+ // Replace the global pairs list
+ pairs = parentPairs;
});
if (!innerEq) {
@@ -386,21 +427,54 @@
return outerEq;
},
- "map": function map(b, a) {
+ // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
+ // in a, there is some key-value pair (bKey, bVal) in b such that
+ // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
+ // counted, so these are equivalent:
+ // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
+ // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
+ "map": function map(a, b) {
var innerEq,
outerEq = true;
if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) key-value pair to two equivalent maps
+ // can make them non-equivalent.
return false;
}
a.forEach(function (aVal, aKey) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Map is unused)
+ if (!outerEq) {
+ return;
+ }
+
innerEq = false;
b.forEach(function (bVal, bKey) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
if (innerEquiv([bVal, bKey], [aVal, aKey])) {
innerEq = true;
}
+
+ // Replace the global pairs list
+ pairs = parentPairs;
});
if (!innerEq) {
@@ -411,52 +485,31 @@
return outerEq;
},
- "object": function object(b, a) {
- var i, j, loop, aCircular, bCircular;
-
- // Default to true
- var eq = true;
- var aProperties = [];
- var bProperties = [];
+ "object": function object(a, b) {
+ var i,
+ aProperties = [],
+ bProperties = [];
if (compareConstructors(a, b) === false) {
return false;
}
- // Stack constructor before traversing properties
- callers.push(a.constructor);
-
- // Track reference to avoid circular references
- parents.push(a);
- parentsB.push(b);
-
// Be strict: don't ensure hasOwnProperty and go deep
for (i in a) {
- loop = false;
- for (j = 0; j < parents.length; j++) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
- if (aCircular || bCircular) {
- if (a[i] === b[i] || aCircular && bCircular) {
- loop = true;
- } else {
- eq = false;
- break;
- }
- }
- }
+
+ // Collect a's properties
aProperties.push(i);
- if (!loop && !innerEquiv(a[i], b[i])) {
- eq = false;
- break;
- }
- }
- parents.pop();
- parentsB.pop();
+ // Skip OOP methods that look the same
+ if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
+ continue;
+ }
- // Unstack, we are done
- callers.pop();
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
for (i in b) {
@@ -465,28 +518,52 @@
}
// Ensures identical properties name
- return eq && innerEquiv(aProperties.sort(), bProperties.sort());
+ return typeEquiv(aProperties.sort(), bProperties.sort());
}
};
function typeEquiv(a, b) {
var type = objectType(a);
- return objectType(b) === type && callbacks[type](b, a);
+
+ // Callbacks for containers will append to the pairs queue to achieve breadth-first
+ // search order. The pairs queue is also used to avoid reprocessing any pair of
+ // containers that are reference-equal to a previously visited pair (a special case
+ // this being recursion detection).
+ //
+ // Because of this approach, once typeEquiv returns a false value, it should not be
+ // called again without clearing the pair queue else it may wrongly report a visited
+ // pair as being equivalent.
+ return objectType(b) === type && callbacks[type](a, b);
}
- // The real equiv function
function innerEquiv(a, b) {
+ var i, pair;
// We're done when there's nothing more to compare
if (arguments.length < 2) {
return true;
}
- // Require type-specific equality
- return (a === b || typeEquiv(a, b)) && (
+ // Clear the global pair queue and add the top-level values being compared
+ pairs = [{ a: a, b: b }];
+
+ for (i = 0; i < pairs.length; i++) {
+ pair = pairs[i];
+
+ // Perform type-specific comparison on any pairs that are not strictly
+ // equal. For container types, that comparison will postpone comparison
+ // of any sub-container pair to the end of the pair queue. This gives
+ // breadth-first search order. It also avoids the reprocessing of
+ // reference-equal siblings, cousins etc, which can have a significant speed
+ // impact when comparing a container of small objects each of which has a
+ // reference to the same (singleton) large object.
+ if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
+ return false;
+ }
+ }
// ...across all consecutive argument pairs
- arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)));
+ return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
}
return innerEquiv;
@@ -616,10 +693,10 @@
var res,
parser,
parserType,
- inStack = inArray(obj, stack);
+ objIndex = stack.indexOf(obj);
- if (inStack !== -1) {
- return "recursion(" + (inStack - stack.length) + ")";
+ if (objIndex !== -1) {
+ return "recursion(" + (objIndex - stack.length) + ")";
}
objType = objType || this.typeOf(obj);
@@ -749,7 +826,7 @@
nonEnumerableProperties = ["message", "name"];
for (i in nonEnumerableProperties) {
key = nonEnumerableProperties[i];
- if (key in map && inArray(key, keys) < 0) {
+ if (key in map && !inArray(key, keys)) {
keys.push(key);
}
}
@@ -843,6 +920,64 @@
return dump;
})();
+ var LISTENERS = Object.create(null);
+ var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
+
+ /**
+ * Emits an event with the specified data to all currently registered listeners.
+ * Callbacks will fire in the order in which they are registered (FIFO). This
+ * function is not exposed publicly; it is used by QUnit internals to emit
+ * logging events.
+ *
+ * @private
+ * @method emit
+ * @param {String} eventName
+ * @param {Object} data
+ * @return {Void}
+ */
+ function emit(eventName, data) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when emitting an event");
+ }
+
+ // Clone the callbacks in case one of them registers a new callback
+ var originalCallbacks = LISTENERS[eventName];
+ var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
+
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](data);
+ }
+ }
+
+ /**
+ * Registers a callback as a listener to the specified event.
+ *
+ * @public
+ * @method on
+ * @param {String} eventName
+ * @param {Function} callback
+ * @return {Void}
+ */
+ function on(eventName, callback) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when registering a listener");
+ } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
+ var events = SUPPORTED_EVENTS.join(", ");
+ throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
+ } else if (objectType(callback) !== "function") {
+ throw new TypeError("callback must be a function when registering a listener");
+ }
+
+ if (!LISTENERS[eventName]) {
+ LISTENERS[eventName] = [];
+ }
+
+ // Don't register the same callback more than once
+ if (!inArray(callback, LISTENERS[eventName])) {
+ LISTENERS[eventName].push(callback);
+ }
+ }
+
// Register logging callbacks
function registerLoggingCallbacks(obj) {
var i,
@@ -929,6 +1064,95 @@
return extractStacktrace(error, offset);
}
+ var TestReport = function () {
+ function TestReport(name, suite, options) {
+ classCallCheck(this, TestReport);
+
+ this.name = name;
+ this.suiteName = suite.name;
+ this.fullName = suite.fullName.concat(name);
+ this.runtime = 0;
+ this.assertions = [];
+
+ this.skipped = !!options.skip;
+ this.todo = !!options.todo;
+
+ this._startTime = 0;
+ this._endTime = 0;
+
+ suite.pushTest(this);
+ }
+
+ createClass(TestReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ suiteName: this.suiteName,
+ fullName: this.fullName.slice()
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return extend(this.start(), {
+ runtime: this.getRuntime(),
+ status: this.getStatus(),
+ errors: this.getFailedAssertions(),
+ assertions: this.getAssertions()
+ });
+ }
+ }, {
+ key: "pushAssertion",
+ value: function pushAssertion(assertion) {
+ this.assertions.push(assertion);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ if (this.skipped) {
+ return "skipped";
+ }
+
+ var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
+
+ if (!testPassed) {
+ return "failed";
+ } else if (this.todo) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }, {
+ key: "getFailedAssertions",
+ value: function getFailedAssertions() {
+ return this.assertions.filter(function (assertion) {
+ return !assertion.passed;
+ });
+ }
+ }, {
+ key: "getAssertions",
+ value: function getAssertions() {
+ return this.assertions.slice();
+ }
+ }]);
+ return TestReport;
+ }();
+
var unitSampler;
var focused = false;
var priorityCount = 0;
@@ -945,6 +1169,12 @@
this.usedAsync = false;
this.module = config.currentModule;
this.stack = sourceFromStacktrace(3);
+ this.steps = [];
+
+ this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
+ todo: settings.todo,
+ skip: settings.skip
+ });
// Register unique strings
for (i = 0, l = this.module.tests; i < l.length; i++) {
@@ -995,6 +1225,7 @@
for (i = notStartedModules.length - 1; i >= 0; i--) {
startModule = notStartedModules[i];
startModule.stats = { all: 0, bad: 0, started: now() };
+ emit("suiteStart", startModule.suiteReport.start(true));
runLoggingCallbacks("moduleStart", {
name: startModule.name,
tests: startModule.tests
@@ -1012,6 +1243,7 @@
this.testEnvironment = extend({}, module.testEnvironment);
this.started = now();
+ emit("testStart", this.testReport.start(true));
runLoggingCallbacks("testStart", {
name: this.testName,
module: module.name,
@@ -1129,6 +1361,7 @@
moduleName = module.name,
testName = this.testName,
skipped = !!this.skip,
+ todo = !!this.todo,
bad = 0,
storage = config.storage;
@@ -1156,10 +1389,12 @@
}
}
+ emit("testEnd", this.testReport.end(true));
runLoggingCallbacks("testDone", {
name: testName,
module: moduleName,
skipped: skipped,
+ todo: todo,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length,
@@ -1174,6 +1409,7 @@
});
if (module.testsRun === numberOfTests(module)) {
+ emit("suiteEnd", module.suiteReport.end(true));
runLoggingCallbacks("moduleDone", {
name: module.name,
tests: module.tests,
@@ -1242,18 +1478,19 @@
expected: resultInfo.expected,
testId: this.testId,
negative: resultInfo.negative || false,
- runtime: now() - this.started
+ runtime: now() - this.started,
+ todo: !!this.todo
};
if (!resultInfo.result) {
- source = sourceFromStacktrace();
+ source = resultInfo.source || sourceFromStacktrace();
if (source) {
details.source = source;
}
}
- runLoggingCallbacks("log", details);
+ this.logAssertion(details);
this.assertions.push({
result: !!resultInfo.result,
@@ -1266,28 +1503,37 @@
throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
}
- var details = {
- module: this.module.name,
- name: this.testName,
+ this.assert.pushResult({
result: false,
message: message || "error",
actual: actual || null,
- testId: this.testId,
- runtime: now() - this.started
- };
-
- if (source) {
- details.source = source;
- }
+ expected: null,
+ source: source
+ });
+ },
+ /**
+ * Log assertion details using both the old QUnit.log interface and
+ * QUnit.on( "assertion" ) interface.
+ *
+ * @private
+ */
+ logAssertion: function logAssertion(details) {
runLoggingCallbacks("log", details);
- this.assertions.push({
- result: false,
- message: message
- });
+ var assertion = {
+ passed: details.result,
+ actual: details.actual,
+ expected: details.expected,
+ message: details.message,
+ stack: details.source,
+ todo: details.todo
+ };
+ this.testReport.pushAssertion(assertion);
+ emit("assertion", assertion);
},
+
resolvePromise: function resolvePromise(promise, phase) {
var then,
resume,
@@ -1331,7 +1577,7 @@
}
function moduleChainIdMatch(testModule) {
- return inArray(testModule.moduleId, config.moduleId) > -1 || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
+ return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
}
// Internally-generated tests are always valid
@@ -1344,7 +1590,7 @@
return false;
}
- if (config.testId && config.testId.length > 0 && inArray(this.testId, config.testId) < 0) {
+ if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
return false;
}
@@ -1511,9 +1757,7 @@
return;
}
- var newTest;
-
- newTest = new Test({
+ var newTest = new Test({
testName: testName,
callback: callback
});
@@ -1521,6 +1765,20 @@
newTest.queue();
}
+ function todo(testName, callback) {
+ if (focused) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback,
+ todo: true
+ });
+
+ newTest.queue();
+ }
+
// Will be exposed as QUnit.skip
function skip(testName) {
if (focused) {
@@ -1537,8 +1795,6 @@
// Will be exposed as QUnit.only
function only(testName, callback) {
- var newTest;
-
if (focused) {
return;
}
@@ -1546,7 +1802,7 @@
config.queue.length = 0;
focused = true;
- newTest = new Test({
+ var newTest = new Test({
testName: testName,
callback: callback
});
@@ -1653,6 +1909,24 @@
}
}
+ /**
+ * Returns a function that proxies to the given method name on the globals
+ * console object. The proxy will also detect if the console doesn't exist and
+ * will appropriately no-op. This allows support for IE9, which doesn't have a
+ * console if the developer tools are not open.
+ */
+ function consoleProxy(method) {
+ return function () {
+ if (console) {
+ console[method].apply(console, arguments);
+ }
+ };
+ }
+
+ var Logger = {
+ warn: consoleProxy("warn")
+ };
+
var Assert = function () {
function Assert(testContext) {
classCallCheck(this, Assert);
@@ -1662,11 +1936,34 @@
// Assert helpers
- // Specify the number of expected assertions to guarantee that failed test
- // (no assertions are run at all) don't slip through.
+ // Documents a "step", which is a string value, in a test as a passing assertion
createClass(Assert, [{
+ key: "step",
+ value: function step(message) {
+ var result = !!message;
+
+ this.test.steps.push(message);
+
+ return this.pushResult({
+ result: result,
+ message: message || "You must provide a message to assert.step"
+ });
+ }
+
+ // Verifies the steps in a test match a given array of string values
+
+ }, {
+ key: "verifySteps",
+ value: function verifySteps(steps, message) {
+ this.deepEqual(this.test.steps, steps, message);
+ }
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+
+ }, {
key: "expect",
value: function expect(asserts) {
if (arguments.length === 1) {
@@ -1714,7 +2011,7 @@
}, {
key: "push",
value: function push(result, actual, expected, message, negative) {
- console.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/).");
+ Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/).");
var currentAssert = this instanceof Assert ? this : config.current.assert;
return currentAssert.pushResult({
@@ -2011,45 +2308,148 @@
}
}
- (function () {
- if (!defined.document) {
- return;
- }
+ var SuiteReport = function () {
+ function SuiteReport(name, parentSuite) {
+ classCallCheck(this, SuiteReport);
- // `onErrorFnPrev` initialized at top of scope
- // Preserve other handlers
- var onErrorFnPrev = window.onerror;
+ this.name = name;
+ this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
- // Cover uncaught exceptions
- // Returning true will suppress the default browser handler,
- // returning false will let it run.
- window.onerror = function (error, filePath, linerNr) {
- var ret = false;
- if (onErrorFnPrev) {
- ret = onErrorFnPrev(error, filePath, linerNr);
+ this.tests = [];
+ this.childSuites = [];
+
+ if (parentSuite) {
+ parentSuite.pushChildSuite(this);
}
+ }
- // Treat return value as window.onerror itself does,
- // Only do our handling if not suppressed.
- if (ret !== true) {
- if (config.current) {
- if (config.current.ignoreGlobalErrors) {
- return true;
+ createClass(SuiteReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.start();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.start();
+ }),
+ testCounts: {
+ total: this.getTestCounts().total
}
- pushFailure(error, filePath + ":" + linerNr);
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.end();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.end();
+ }),
+ testCounts: this.getTestCounts(),
+ runtime: this.getRuntime(),
+ status: this.getStatus()
+ };
+ }
+ }, {
+ key: "pushChildSuite",
+ value: function pushChildSuite(suite) {
+ this.childSuites.push(suite);
+ }
+ }, {
+ key: "pushTest",
+ value: function pushTest(test) {
+ this.tests.push(test);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getTestCounts",
+ value: function getTestCounts() {
+ var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
+
+ counts = this.tests.reduce(function (counts, test) {
+ counts[test.getStatus()]++;
+ counts.total++;
+ return counts;
+ }, counts);
+
+ return this.childSuites.reduce(function (counts, suite) {
+ return suite.getTestCounts(counts);
+ }, counts);
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ var _getTestCounts = this.getTestCounts(),
+ total = _getTestCounts.total,
+ failed = _getTestCounts.failed,
+ skipped = _getTestCounts.skipped,
+ todo = _getTestCounts.todo;
+
+ if (failed) {
+ return "failed";
} else {
- test("global failure", extend(function () {
- pushFailure(error, filePath + ":" + linerNr);
- }, { validTest: true }));
+ if (skipped === total) {
+ return "skipped";
+ } else if (todo === total) {
+ return "todo";
+ } else {
+ return "passed";
+ }
}
- return false;
}
+ }]);
+ return SuiteReport;
+ }();
- return ret;
- };
- })();
+ // Handle an unhandled exception. By convention, returns true if further
+ // error handling should be suppressed and false otherwise.
+ // In this case, we will only suppress further error handling if the
+ // "ignoreGlobalErrors" configuration option is enabled.
+ function onError(error) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ if (config.current) {
+ if (config.current.ignoreGlobalErrors) {
+ return true;
+ }
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ } else {
+ test("global failure", extend(function () {
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ }, { validTest: true }));
+ }
+
+ return false;
+ }
var QUnit = {};
+ var globalSuite = new SuiteReport();
+
+ // The initial "currentModule" represents the global (or top-level) module that
+ // is not explicitly defined by the user, therefore we add the "globalSuite" to
+ // it since each module has a suiteReport associated with it.
+ config.currentModule.suiteReport = globalSuite;
var globalStartCalled = false;
var runStarted = false;
@@ -2062,9 +2462,10 @@
QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
// Expose the current QUnit version
- QUnit.version = "2.1.1";
+ QUnit.version = "2.2.0";
extend(QUnit, {
+ on: on,
// Call on start of module test to prepend name to all tests
module: function module(name, testEnvironment, executeNow) {
@@ -2100,13 +2501,16 @@
function createModule() {
var parentModule = config.moduleStack.length ? config.moduleStack.slice(-1)[0] : null;
var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+ var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
var module = {
name: moduleName,
parentModule: parentModule,
tests: [],
moduleId: generateHash(moduleName),
testsRun: 0,
- childModules: []
+ childModules: [],
+ suiteReport: new SuiteReport(name, parentSuite)
};
var env = {};
@@ -2130,6 +2534,8 @@
test: test,
+ todo: todo,
+
skip: skip,
only: only,
@@ -2146,14 +2552,18 @@
throw new Error("Called start() outside of a test context too many times");
} else if (config.autostart) {
throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
- } else if (!defined.document && !config.pageLoaded) {
-
- // Starts from Node even if .load was not previously called
- QUnit.load();
} else if (!config.pageLoaded) {
- // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+ // The page isn't completely loaded yet, so we set autostart and then
+ // load if we're in Node or wait for the browser's load event.
config.autostart = true;
+
+ // Starts from Node even if .load was not previously called. We still return
+ // early otherwise we'll wind up "beginning" twice.
+ if (!defined.document) {
+ QUnit.load();
+ }
+
return;
}
} else {
@@ -2195,7 +2605,9 @@
stack: function stack(offset) {
offset = (offset || 0) + 2;
return sourceFromStacktrace(offset);
- }
+ },
+
+ onError: onError
});
QUnit.pushFailure = pushFailure;
@@ -2244,6 +2656,7 @@
}
// The test run is officially beginning now
+ emit("runStart", globalSuite.start(true));
runLoggingCallbacks("begin", {
totalTests: Test.count,
modules: modulesLog
@@ -2292,6 +2705,7 @@
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
+ emit("runEnd", globalSuite.end(true));
runLoggingCallbacks("done", {
failed: config.stats.bad,
passed: passed,
@@ -2456,6 +2870,13 @@
}
})();
+ var stats = {
+ passedTests: 0,
+ failedTests: 0,
+ skippedTests: 0,
+ todoTests: 0
+ };
+
// Escape text for attribute or text content.
function escapeText(s) {
if (!s) {
@@ -3046,7 +3467,8 @@
var banner = id("qunit-banner"),
tests = id("qunit-tests"),
abortButton = id("qunit-abort-tests-button"),
- html = ["Tests completed in ", details.runtime, " milliseconds.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
+ totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
+ html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
test,
assertLi,
assertList;
@@ -3069,7 +3491,7 @@
}
if (banner && (!abortButton || abortButton.disabled === false)) {
- banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+ banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
}
if (abortButton) {
@@ -3084,7 +3506,7 @@
// Show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
- document$$1.title = [details.failed ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
+ document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
}
// Scroll back to top to show results
@@ -3224,7 +3646,10 @@
good = details.passed;
bad = details.failed;
- if (bad === 0) {
+ // This test passed if it has no unexpected failed assertions
+ var testPassed = details.failed > 0 ? details.todo : !details.todo;
+
+ if (testPassed) {
// Collapse the passing tests
addClass(assertList, "qunit-collapsed");
@@ -3248,6 +3673,8 @@
testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
if (details.skipped) {
+ stats.skippedTests++;
+
testItem.className = "skipped";
skipped = document$$1.createElement("em");
skipped.className = "qunit-skipped-label";
@@ -3258,12 +3685,27 @@
toggleClass(assertList, "qunit-collapsed");
});
- testItem.className = bad ? "fail" : "pass";
+ testItem.className = testPassed ? "pass" : "fail";
+
+ if (details.todo) {
+ var todoLabel = document$$1.createElement("em");
+ todoLabel.className = "qunit-todo-label";
+ todoLabel.innerHTML = "todo";
+ testItem.insertBefore(todoLabel, testTitle);
+ }
time = document$$1.createElement("span");
time.className = "runtime";
time.innerHTML = details.runtime + " ms";
testItem.insertBefore(time, assertList);
+
+ if (!testPassed) {
+ stats.failedTests++;
+ } else if (details.todo) {
+ stats.todoTests++;
+ } else {
+ stats.passedTests++;
+ }
}
// Show the source of the test when showing assertions
@@ -3271,7 +3713,7 @@
sourceName = document$$1.createElement("p");
sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
addClass(sourceName, "qunit-source");
- if (bad === 0) {
+ if (testPassed) {
addClass(sourceName, "qunit-collapsed");
}
addEvent(testTitle, "click", function () {
@@ -3292,6 +3734,39 @@
} else {
addEvent(window, "load", QUnit.load);
}
+
+ // Wrap window.onerror. We will call the original window.onerror to see if
+ // the existing handler fully handles the error; if not, we will call the
+ // QUnit.onError function.
+ var originalWindowOnError = window.onerror;
+
+ // Cover uncaught exceptions
+ // Returning true will suppress the default browser handler,
+ // returning false will let it run.
+ window.onerror = function (message, fileName, lineNumber) {
+ var ret = false;
+ if (originalWindowOnError) {
+ for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
+ args[_key - 3] = arguments[_key];
+ }
+
+ ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if (ret !== true) {
+ var error = {
+ message: message,
+ fileName: fileName,
+ lineNumber: lineNumber
+ };
+
+ ret = QUnit.onError(error);
+ }
+
+ return ret;
+ };
})();
/*