5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2018-03-27T02:18Z
11 (function (global
$1) {
14 global
$1 = global
$1 && global
$1.hasOwnProperty('default') ? global
$1['default'] : global
$1;
16 var window
= global
$1.window
;
17 var self
$1 = global
$1.self
;
18 var console
= global
$1.console
;
19 var setTimeout
= global
$1.setTimeout
;
20 var clearTimeout
= global
$1.clearTimeout
;
22 var document
= window
&& window
.document
;
23 var navigator
= window
&& window
.navigator
;
25 var localSessionStorage = function () {
26 var x
= "qunit-test-string";
28 global
$1.sessionStorage
.setItem(x
, x
);
29 global
$1.sessionStorage
.removeItem(x
);
30 return global
$1.sessionStorage
;
36 var _typeof
= typeof Symbol
=== "function" && typeof Symbol
.iterator
=== "symbol" ? function (obj
) {
39 return obj
&& typeof Symbol
=== "function" && obj
.constructor === Symbol
&& obj
!== Symbol
.prototype ? "symbol" : typeof obj
;
52 var classCallCheck = function (instance
, Constructor
) {
53 if (!(instance
instanceof Constructor
)) {
54 throw new TypeError("Cannot call a class as a function");
58 var createClass = function () {
59 function defineProperties(target
, props
) {
60 for (var i
= 0; i
< props
.length
; i
++) {
61 var descriptor
= props
[i
];
62 descriptor
.enumerable
= descriptor
.enumerable
|| false;
63 descriptor
.configurable
= true;
64 if ("value" in descriptor
) descriptor
.writable
= true;
65 Object
.defineProperty(target
, descriptor
.key
, descriptor
);
69 return function (Constructor
, protoProps
, staticProps
) {
70 if (protoProps
) defineProperties(Constructor
.prototype, protoProps
);
71 if (staticProps
) defineProperties(Constructor
, staticProps
);
116 var toConsumableArray = function (arr
) {
117 if (Array
.isArray(arr
)) {
118 for (var i
= 0, arr2
= Array(arr
.length
); i
< arr
.length
; i
++) arr2
[i
] = arr
[i
];
122 return Array
.from(arr
);
126 var toString
= Object
.prototype.toString
;
127 var hasOwn
= Object
.prototype.hasOwnProperty
;
128 var now
= Date
.now
|| function () {
129 return new Date().getTime();
133 document
: window
&& window
.document
!== undefined,
134 setTimeout
: setTimeout
!== undefined
137 // Returns a new Array with the elements that are in a but not in b
138 function diff(a
, b
) {
143 for (i
= 0; i
< result
.length
; i
++) {
144 for (j
= 0; j
< b
.length
; j
++) {
145 if (result
[i
] === b
[j
]) {
156 * Determines whether an element exists in a given array or not.
160 * @param {Array} array
163 function inArray(elem
, array
) {
164 return array
.indexOf(elem
) !== -1;
168 * Makes a clone of an object using only Array or Object as base,
169 * and copies over the own enumerable properties.
171 * @param {Object} obj
172 * @return {Object} New object with only the own properties (recursively).
174 function objectValues(obj
) {
177 vals
= is("array", obj
) ? [] : {};
179 if (hasOwn
.call(obj
, key
)) {
181 vals
[key
] = val
=== Object(val
) ? objectValues(val
) : val
;
187 function extend(a
, b
, undefOnly
) {
188 for (var prop
in b
) {
189 if (hasOwn
.call(b
, prop
)) {
190 if (b
[prop
] === undefined) {
192 } else if (!(undefOnly
&& typeof a
[prop
] !== "undefined")) {
201 function objectType(obj
) {
202 if (typeof obj
=== "undefined") {
206 // Consider: typeof null === object
211 var match
= toString
.call(obj
).match(/^\[object\s(.*)\]$/),
212 type
= match
&& match
[1];
229 return type
.toLowerCase();
231 return typeof obj
=== "undefined" ? "undefined" : _typeof(obj
);
235 // Safe object type checking
236 function is(type
, obj
) {
237 return objectType(obj
) === type
;
240 // Based on Java's String.hashCode, a simple but not
241 // rigorously collision resistant hashing function
242 function generateHash(module
, testName
) {
243 var str
= module
+ "\x1C" + testName
;
246 for (var i
= 0; i
< str
.length
; i
++) {
247 hash
= (hash
<< 5) - hash
+ str
.charCodeAt(i
);
251 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
252 // strictly necessary but increases user understanding that the id is a SHA-like hash
253 var hex
= (0x100000000 + hash
).toString(16);
254 if (hex
.length
< 8) {
255 hex
= "0000000" + hex
;
258 return hex
.slice(-8);
261 // Test for equality any JavaScript type.
262 // Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
263 var equiv
= (function () {
265 // Value pairs queued for comparison. Used for breadth-first processing order, recursion
266 // detection and avoiding repeated comparison (see below for details).
267 // Elements are { a: val, b: val }.
270 var getProto
= Object
.getPrototypeOf
|| function (obj
) {
271 return obj
.__proto__
;
274 function useStrictEquality(a
, b
) {
276 // This only gets called if a and b are not strict equal, and is used to compare on
277 // the primitive values inside object wrappers. For example:
279 // `var j = new Number(1);`
280 // Neither a nor b can be null, as a !== b and they have the same type.
281 if ((typeof a
=== "undefined" ? "undefined" : _typeof(a
)) === "object") {
284 if ((typeof b
=== "undefined" ? "undefined" : _typeof(b
)) === "object") {
291 function compareConstructors(a
, b
) {
292 var protoA
= getProto(a
);
293 var protoB
= getProto(b
);
295 // Comparing constructors is more strict than using `instanceof`
296 if (a
.constructor === b
.constructor) {
301 // If the obj prototype descends from a null constructor, treat it
302 // as a null prototype.
303 if (protoA
&& protoA
.constructor === null) {
306 if (protoB
&& protoB
.constructor === null) {
310 // Allow objects with no prototype to be equivalent to
311 // objects with Object as their constructor.
312 if (protoA
=== null && protoB
=== Object
.prototype || protoB
=== null && protoA
=== Object
.prototype) {
319 function getRegExpFlags(regexp
) {
320 return "flags" in regexp
? regexp
.flags
: regexp
.toString().match(/[gimuy]*$/)[0];
323 function isContainer(val
) {
324 return ["object", "array", "map", "set"].indexOf(objectType(val
)) !== -1;
327 function breadthFirstCompareChild(a
, b
) {
329 // If a is a container not reference-equal to b, postpone the comparison to the
330 // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
335 if (!isContainer(a
)) {
336 return typeEquiv(a
, b
);
338 if (pairs
.every(function (pair
) {
339 return pair
.a
!== a
|| pair
.b
!== b
;
342 // Not yet started comparing this pair
343 pairs
.push({ a
: a
, b
: b
});
349 "string": useStrictEquality
,
350 "boolean": useStrictEquality
,
351 "number": useStrictEquality
,
352 "null": useStrictEquality
,
353 "undefined": useStrictEquality
,
354 "symbol": useStrictEquality
,
355 "date": useStrictEquality
,
357 "nan": function nan() {
361 "regexp": function regexp(a
, b
) {
362 return a
.source
=== b
.source
&&
364 // Include flags in the comparison
365 getRegExpFlags(a
) === getRegExpFlags(b
);
368 // abort (identical references / instance methods were skipped earlier)
369 "function": function _function() {
373 "array": function array(a
, b
) {
377 if (len
!== b
.length
) {
383 for (i
= 0; i
< len
; i
++) {
385 // Compare non-containers; queue non-reference-equal containers
386 if (!breadthFirstCompareChild(a
[i
], b
[i
])) {
393 // Define sets a and b to be equivalent if for each element aVal in a, there
394 // is some element bVal in b such that aVal and bVal are equivalent. Element
395 // repetitions are not counted, so these are equivalent:
396 // a = new Set( [ {}, [], [] ] );
397 // b = new Set( [ {}, {}, [] ] );
398 "set": function set$$1(a
, b
) {
402 if (a
.size
!== b
.size
) {
404 // This optimization has certain quirks because of the lack of
405 // repetition counting. For instance, adding the same
406 // (reference-identical) element to two equivalent sets can
407 // make them non-equivalent.
411 a
.forEach(function (aVal
) {
413 // Short-circuit if the result is already known. (Using for...of
414 // with a break clause would be cleaner here, but it would cause
415 // a syntax error on older Javascript implementations even if
423 b
.forEach(function (bVal
) {
426 // Likewise, short-circuit if the result is already known
431 // Swap out the global pairs list, as the nested call to
432 // innerEquiv will clobber its contents
434 if (innerEquiv(bVal
, aVal
)) {
438 // Replace the global pairs list
450 // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
451 // in a, there is some key-value pair (bKey, bVal) in b such that
452 // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
453 // counted, so these are equivalent:
454 // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
455 // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
456 "map": function map(a
, b
) {
460 if (a
.size
!== b
.size
) {
462 // This optimization has certain quirks because of the lack of
463 // repetition counting. For instance, adding the same
464 // (reference-identical) key-value pair to two equivalent maps
465 // can make them non-equivalent.
469 a
.forEach(function (aVal
, aKey
) {
471 // Short-circuit if the result is already known. (Using for...of
472 // with a break clause would be cleaner here, but it would cause
473 // a syntax error on older Javascript implementations even if
481 b
.forEach(function (bVal
, bKey
) {
484 // Likewise, short-circuit if the result is already known
489 // Swap out the global pairs list, as the nested call to
490 // innerEquiv will clobber its contents
492 if (innerEquiv([bVal
, bKey
], [aVal
, aKey
])) {
496 // Replace the global pairs list
508 "object": function object(a
, b
) {
513 if (compareConstructors(a
, b
) === false) {
517 // Be strict: don't ensure hasOwnProperty and go deep
520 // Collect a's properties
523 // Skip OOP methods that look the same
524 if (a
.constructor !== Object
&& typeof a
.constructor !== "undefined" && typeof a
[i
] === "function" && typeof b
[i
] === "function" && a
[i
].toString() === b
[i
].toString()) {
528 // Compare non-containers; queue non-reference-equal containers
529 if (!breadthFirstCompareChild(a
[i
], b
[i
])) {
536 // Collect b's properties
540 // Ensures identical properties name
541 return typeEquiv(aProperties
.sort(), bProperties
.sort());
545 function typeEquiv(a
, b
) {
546 var type
= objectType(a
);
548 // Callbacks for containers will append to the pairs queue to achieve breadth-first
549 // search order. The pairs queue is also used to avoid reprocessing any pair of
550 // containers that are reference-equal to a previously visited pair (a special case
551 // this being recursion detection).
553 // Because of this approach, once typeEquiv returns a false value, it should not be
554 // called again without clearing the pair queue else it may wrongly report a visited
555 // pair as being equivalent.
556 return objectType(b
) === type
&& callbacks
[type
](a
, b
);
559 function innerEquiv(a
, b
) {
562 // We're done when there's nothing more to compare
563 if (arguments
.length
< 2) {
567 // Clear the global pair queue and add the top-level values being compared
568 pairs
= [{ a
: a
, b
: b
}];
570 for (i
= 0; i
< pairs
.length
; i
++) {
573 // Perform type-specific comparison on any pairs that are not strictly
574 // equal. For container types, that comparison will postpone comparison
575 // of any sub-container pair to the end of the pair queue. This gives
576 // breadth-first search order. It also avoids the reprocessing of
577 // reference-equal siblings, cousins etc, which can have a significant speed
578 // impact when comparing a container of small objects each of which has a
579 // reference to the same (singleton) large object.
580 if (pair
.a
!== pair
.b
&& !typeEquiv(pair
.a
, pair
.b
)) {
585 // ...across all consecutive argument pairs
586 return arguments
.length
=== 2 || innerEquiv
.apply(this, [].slice
.call(arguments
, 1));
590 var result
= innerEquiv
.apply(undefined, arguments
);
592 // Release any retained objects
599 * Config object: Maintain internal state
600 * Later exposed as QUnit.config
601 * `config` initialized at top of scope
605 // The queue of tests to run
608 // Block until document ready
611 // By default, run previously failed tests first
612 // very useful in combination with "Hide passed tests" checked
615 // By default, modify document.title when suite is done
618 // HTML Reporter: collapse every test except the first failing test
619 // If false, all failing tests will be expanded
622 // By default, scroll to top of the page when suite is done
625 // Depth up-to which object will be dumped
628 // When enabled, all tests must call expect()
629 requireExpects
: false,
631 // Placeholder for user-configurable form-exposed URL parameters
634 // Set of all modules.
637 // The first unnamed module
643 unskippedTestsRun
: 0,
654 // The storage module to use for reordering tests
655 storage
: localSessionStorage
658 // take a predefined QUnit.config and extend the defaults
659 var globalConfig
= window
&& window
.QUnit
&& window
.QUnit
.config
;
661 // only extend the global config if there is no QUnit overload
662 if (window
&& window
.QUnit
&& !window
.QUnit
.version
) {
663 extend(config
, globalConfig
);
666 // Push a loose unnamed module to the modules collection
667 config
.modules
.push(config
.currentModule
);
669 // Based on jsDump by Ariel Flesler
670 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
671 var dump
= (function () {
672 function quote(str
) {
673 return "\"" + str
.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
675 function literal(o) {
678 function join(pre, arr, post) {
679 var s = dump.separator(),
680 base = dump.indent(),
681 inner = dump.indent(1);
683 arr = arr.join("," + s + inner);
688 return [pre, inner + arr, base + post].join(s);
690 function array(arr, stack) {
694 if (dump.maxDepth && dump.depth > dump.maxDepth) {
695 return "[object Array
]";
700 ret[i] = this.parse(arr[i], undefined, stack);
703 return join("[", ret, "]");
706 function isArray(obj) {
710 toString.call(obj) === "[object Array
]" ||
713 typeof obj.length === "number
" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
717 var reName = /^function (\w+)/,
720 // The objType is used mostly internally, you can fix a (custom) type in advance
721 parse: function parse(obj, objType, stack) {
726 objIndex = stack.indexOf(obj);
728 if (objIndex !== -1) {
729 return "recursion(" + (objIndex - stack.length) + ")";
732 objType = objType || this.typeOf(obj);
733 parser = this.parsers[objType];
734 parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
736 if (parserType === "function") {
738 res = parser.call(this, obj, stack);
742 return parserType === "string
" ? parser : this.parsers.error;
744 typeOf: function typeOf(obj) {
749 } else if (typeof obj === "undefined") {
751 } else if (is("regexp
", obj)) {
753 } else if (is("date
", obj)) {
755 } else if (is("function", obj)) {
757 } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
759 } else if (obj.nodeType === 9) {
761 } else if (obj.nodeType) {
763 } else if (isArray(obj)) {
765 } else if (obj.constructor === Error.prototype.constructor) {
768 type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
773 separator: function separator() {
774 if (this.multiline) {
775 return this.HTML ? "<br
/>" : "\n";
777 return this.HTML ? " " : " ";
781 // Extra can be a number, shortcut for increasing-calling-decreasing
782 indent: function indent(extra) {
783 if (!this.multiline) {
786 var chr = this.indentChar;
788 chr = chr.replace(/\t/g, " ").replace(/ /g, " ");
790 return new Array(this.depth + (extra || 0)).join(chr);
793 this.depth += a || 1;
795 down: function down(a) {
796 this.depth -= a || 1;
798 setParser: function setParser(name, parser) {
799 this.parsers[name] = parser;
802 // The next 3 are exposed so you can use them
807 maxDepth: config.maxDepth,
809 // This is the list of parsers, to modify them, use dump.setParser
812 document: "[Document
]",
813 error: function error(_error) {
814 return "Error(\"" + _error.message + "\")";
816 unknown: "[Unknown
]",
818 "undefined": "undefined",
819 "function": function _function(fn) {
820 var ret = "function",
823 // Functions never have name in IE
824 name = "name
" in fn ? fn.name : (reName.exec(fn) || [])[1];
831 ret = [ret, dump.parse(fn, "functionArgs
"), "){"].join("");
832 return join(ret, dump.parse(fn, "functionCode
"), "}");
837 object: function object(map, stack) {
842 nonEnumerableProperties,
845 if (dump.maxDepth && dump.depth > dump.maxDepth) {
846 return "[object Object
]";
855 // Some properties are not always enumerable on Error objects.
856 nonEnumerableProperties = ["message
", "name
"];
857 for (i in nonEnumerableProperties) {
858 key = nonEnumerableProperties[i];
859 if (key in map && !inArray(key, keys)) {
864 for (i = 0; i < keys.length; i++) {
867 ret.push(dump.parse(key, "key
") + ": " + dump.parse(val, undefined, stack));
870 return join("{", ret, "}");
872 node: function node(_node) {
876 open = dump.HTML ? "<
;" : "<",
877 close = dump.HTML ? ">
;" : ">",
878 tag = _node.nodeName.toLowerCase(),
880 attrs = _node.attributes;
883 for (i = 0, len = attrs.length; i < len; i++) {
884 val = attrs[i].nodeValue;
886 // IE6 includes all attributes in .attributes, even ones not explicitly
887 // set. Those have values like undefined, null, 0, false, "" or
889 if (val && val !== "inherit
") {
890 ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute
");
896 // Show content of TextNode or CDATASection
897 if (_node.nodeType === 3 || _node.nodeType === 4) {
898 ret += _node.nodeValue;
901 return ret + open + "/" + tag + close;
904 // Function calls it internally, it's the arguments part of the function
905 functionArgs: function functionArgs(fn) {
917 args[l] = String.fromCharCode(97 + l);
919 return " " + args.join(", ") + " ";
922 // Object calls it internally, the key part of an item in a map
925 // Function calls it internally, it's the content of the function
926 functionCode: "[code
]",
928 // Node calls it internally, it's a html attribute value
935 symbol: function symbol(sym) {
936 return sym.toString();
940 // If true, entities are escaped ( <, >, \t, space and \n )
946 // If true, items in a collection, are separated by a \n, else just a space.
953 var LISTENERS = Object.create(null);
954 var SUPPORTED_EVENTS = ["runStart
", "suiteStart
", "testStart
", "assertion
", "testEnd
", "suiteEnd
", "runEnd
"];
957 * Emits an event with the specified data to all currently registered listeners.
958 * Callbacks will fire in the order in which they are registered (FIFO). This
959 * function is not exposed publicly; it is used by QUnit internals to emit
964 * @param {String} eventName
965 * @param {Object} data
968 function emit(eventName, data) {
969 if (objectType(eventName) !== "string
") {
970 throw new TypeError("eventName must be a string when emitting an event
");
973 // Clone the callbacks in case one of them registers a new callback
974 var originalCallbacks = LISTENERS[eventName];
975 var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
977 for (var i = 0; i < callbacks.length; i++) {
983 * Registers a callback as a listener to the specified event.
987 * @param {String} eventName
988 * @param {Function} callback
991 function on(eventName, callback) {
992 if (objectType(eventName) !== "string
") {
993 throw new TypeError("eventName must be a string when registering a listener
");
994 } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
995 var events = SUPPORTED_EVENTS.join(", ");
996 throw new Error("\"" + eventName + "\" is not a valid event
; must be one
of: " + events + ".");
997 } else if (objectType(callback) !== "function") {
998 throw new TypeError("callback must be a
function when registering a listener
");
1001 if (!LISTENERS[eventName]) {
1002 LISTENERS[eventName] = [];
1005 // Don't register the same callback more than once
1006 if (!inArray(callback, LISTENERS[eventName])) {
1007 LISTENERS[eventName].push(callback);
1011 // Register logging callbacks
1012 function registerLoggingCallbacks(obj) {
1016 callbackNames = ["begin
", "done
", "log
", "testStart
", "testDone
", "moduleStart
", "moduleDone
"];
1018 function registerLoggingCallback(key) {
1019 var loggingCallback = function loggingCallback(callback) {
1020 if (objectType(callback) !== "function") {
1021 throw new Error("QUnit logging methods require a callback
function as their first parameters
.");
1024 config.callbacks[key].push(callback);
1027 return loggingCallback;
1030 for (i = 0, l = callbackNames.length; i < l; i++) {
1031 key = callbackNames[i];
1033 // Initialize key collection of logging callback
1034 if (objectType(config.callbacks[key]) === "undefined") {
1035 config.callbacks[key] = [];
1038 obj[key] = registerLoggingCallback(key);
1042 function runLoggingCallbacks(key, args) {
1043 var i, l, callbacks;
1045 callbacks = config.callbacks[key];
1046 for (i = 0, l = callbacks.length; i < l; i++) {
1051 // Doesn't support IE9, it will return undefined on these browsers
1052 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1053 var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
1055 function extractStacktrace(e, offset) {
1056 offset = offset === undefined ? 4 : offset;
1058 var stack, include, i;
1061 stack = e.stack.split("\n");
1062 if (/^error$/i.test(stack[0])) {
1067 for (i = offset; i < stack.length; i++) {
1068 if (stack[i].indexOf(fileName) !== -1) {
1071 include.push(stack[i]);
1073 if (include.length) {
1074 return include.join("\n");
1077 return stack[offset];
1081 function sourceFromStacktrace(offset) {
1082 var error = new Error();
1084 // Support: Safari <=7 only, IE <=10 - 11 only
1085 // Not all browsers generate the `stack` property for `new Error()`, see also #636
1094 return extractStacktrace(error, offset);
1097 var priorityCount = 0;
1098 var unitSampler = void 0;
1100 // This is a queue of functions that are tasks within a single test.
1101 // After tests are dequeued from config.queue they are expanded into
1102 // a set of tasks in this queue.
1106 * Advances the taskQueue to the next task. If the taskQueue is empty,
1107 * process the testQueue
1109 function advance() {
1112 if (!taskQueue.length) {
1118 * Advances the taskQueue to the next task if it is ready and not empty.
1120 function advanceTaskQueue() {
1122 config.depth = (config.depth || 0) + 1;
1124 while (taskQueue.length && !config.blocking) {
1125 var elapsedTime = now() - start;
1127 if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
1128 var task = taskQueue.shift();
1131 setTimeout(advance);
1140 * Advance the testQueue to the next test to process. Call done() if testQueue completes.
1142 function advanceTestQueue() {
1143 if (!config.blocking && !config.queue.length && config.depth === 0) {
1148 var testTasks = config.queue.shift();
1149 addToTaskQueue(testTasks());
1151 if (priorityCount > 0) {
1159 * Enqueue the tasks for a test into the task queue.
1160 * @param {Array} tasksArray
1162 function addToTaskQueue(tasksArray) {
1163 taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
1167 * Return the number of tasks remaining in the task queue to be processed.
1170 function taskQueueLength() {
1171 return taskQueue.length;
1175 * Adds a test to the TestQueue for execution.
1176 * @param {Function} testTasksFunc
1177 * @param {Boolean} prioritize
1178 * @param {String} seed
1180 function addToTestQueue(testTasksFunc, prioritize, seed) {
1182 config.queue.splice(priorityCount++, 0, testTasksFunc);
1185 unitSampler = unitSamplerGenerator(seed);
1188 // Insert into a random position after all prioritized items
1189 var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
1190 config.queue.splice(priorityCount + index, 0, testTasksFunc);
1192 config.queue.push(testTasksFunc);
1197 * Creates a seeded "sample
" generator which is used for randomizing tests.
1199 function unitSamplerGenerator(seed) {
1201 // 32-bit xorshift, requires only a nonzero seed
1202 // http://excamera.com/sphinx/article-xorshift.html
1203 var sample = parseInt(generateHash(seed), 16) || -1;
1204 return function () {
1205 sample ^= sample << 13;
1206 sample ^= sample >>> 17;
1207 sample ^= sample << 5;
1209 // ECMAScript has no unsigned number type
1211 sample += 0x100000000;
1214 return sample / 0x100000000;
1219 * This function is called when the ProcessingQueue is done processing all
1220 * items. It handles emitting the final run events.
1223 var storage = config.storage;
1225 ProcessingQueue.finished = true;
1227 var runtime = now() - config.started;
1228 var passed = config.stats.all - config.stats.bad;
1230 if (config.stats.all === 0) {
1232 if (config.filter && config.filter.length) {
1233 throw new Error("No tests matched the filter
\"" + config.filter + "\".");
1236 if (config.module && config.module.length) {
1237 throw new Error("No tests matched the module
\"" + config.module + "\".");
1240 if (config.moduleId && config.moduleId.length) {
1241 throw new Error("No tests matched the moduleId
\"" + config.moduleId + "\".");
1244 if (config.testId && config.testId.length) {
1245 throw new Error("No tests matched the testId
\"" + config.testId + "\".");
1248 throw new Error("No tests were run
.");
1251 emit("runEnd
", globalSuite.end(true));
1252 runLoggingCallbacks("done
", {
1254 failed: config.stats.bad,
1255 total: config.stats.all,
1259 // Clear own storage items if all tests passed
1260 if (storage && config.stats.bad === 0) {
1261 for (var i = storage.length - 1; i >= 0; i--) {
1262 var key = storage.key(i);
1264 if (key.indexOf("qunit
-test
-") === 0) {
1265 storage.removeItem(key);
1271 var ProcessingQueue = {
1273 add: addToTestQueue,
1275 taskCount: taskQueueLength
1278 var TestReport = function () {
1279 function TestReport(name, suite, options) {
1280 classCallCheck(this, TestReport);
1283 this.suiteName = suite.name;
1284 this.fullName = suite.fullName.concat(name);
1286 this.assertions = [];
1288 this.skipped = !!options.skip;
1289 this.todo = !!options.todo;
1291 this.valid = options.valid;
1293 this._startTime = 0;
1296 suite.pushTest(this);
1299 createClass(TestReport, [{
1301 value: function start(recordTime) {
1303 this._startTime = Date.now();
1308 suiteName: this.suiteName,
1309 fullName: this.fullName.slice()
1314 value: function end(recordTime) {
1316 this._endTime = Date.now();
1319 return extend(this.start(), {
1320 runtime: this.getRuntime(),
1321 status: this.getStatus(),
1322 errors: this.getFailedAssertions(),
1323 assertions: this.getAssertions()
1327 key: "pushAssertion
",
1328 value: function pushAssertion(assertion) {
1329 this.assertions.push(assertion);
1333 value: function getRuntime() {
1334 return this._endTime - this._startTime;
1338 value: function getStatus() {
1343 var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
1347 } else if (this.todo) {
1354 key: "getFailedAssertions
",
1355 value: function getFailedAssertions() {
1356 return this.assertions.filter(function (assertion) {
1357 return !assertion.passed;
1361 key: "getAssertions
",
1362 value: function getAssertions() {
1363 return this.assertions.slice();
1366 // Remove actual and expected values from assertions. This is to prevent
1367 // leaking memory throughout a test suite.
1370 key: "slimAssertions
",
1371 value: function slimAssertions() {
1372 this.assertions = this.assertions.map(function (assertion) {
1373 delete assertion.actual;
1374 delete assertion.expected;
1382 var focused$1 = false;
1384 function Test(settings) {
1389 this.expected = null;
1390 this.assertions = [];
1392 this.module = config.currentModule;
1393 this.stack = sourceFromStacktrace(3);
1395 this.timeout = undefined;
1397 // If a module is skipped, all its tests and the tests of the child suites
1398 // should be treated as skipped even if they are defined as `only` or `todo`.
1399 // As for `todo` module, all its tests will be treated as `todo` except for
1400 // tests defined as `skip` which will be left intact.
1402 // So, if a test is defined as `todo` and is inside a skipped module, we should
1403 // then treat that test as if was defined as `skip`.
1404 if (this.module.skip) {
1405 settings.skip = true;
1406 settings.todo = false;
1408 // Skipped tests should be left intact
1409 } else if (this.module.todo && !settings.skip) {
1410 settings.todo = true;
1413 extend(this, settings);
1415 this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
1416 todo: settings.todo,
1417 skip: settings.skip,
1421 // Register unique strings
1422 for (i = 0, l = this.module.tests; i < l.length; i++) {
1423 if (this.module.tests[i].name === this.testName) {
1424 this.testName += " ";
1428 this.testId = generateHash(this.module.name, this.testName);
1430 this.module.tests.push({
1431 name: this.testName,
1432 testId: this.testId,
1433 skip: !!settings.skip
1436 if (settings.skip) {
1438 // Skipped tests will fully ignore any sent callback
1439 this.callback = function () {};
1443 if (typeof this.callback !== "function") {
1444 var method = this.todo ? "todo
" : "test
";
1446 // eslint-disable-next-line max-len
1447 throw new TypeError("You must provide a
function as a test callback to QUnit
." + method + "(\"" + settings.testName + "\")");
1450 this.assert = new Assert(this);
1456 function getNotStartedModules(startModule) {
1457 var module = startModule,
1460 while (module && module.testsRun === 0) {
1461 modules.push(module);
1462 module = module.parentModule;
1469 before: function before() {
1472 module = this.module,
1473 notStartedModules = getNotStartedModules(module);
1475 for (i = notStartedModules.length - 1; i >= 0; i--) {
1476 startModule = notStartedModules[i];
1477 startModule.stats = { all: 0, bad: 0, started: now() };
1478 emit("suiteStart
", startModule.suiteReport.start(true));
1479 runLoggingCallbacks("moduleStart
", {
1480 name: startModule.name,
1481 tests: startModule.tests
1485 config.current = this;
1487 this.testEnvironment = extend({}, module.testEnvironment);
1489 this.started = now();
1490 emit("testStart
", this.testReport.start(true));
1491 runLoggingCallbacks("testStart
", {
1492 name: this.testName,
1493 module: module.name,
1494 testId: this.testId,
1495 previousFailure: this.previousFailure
1498 if (!config.pollution) {
1503 run: function run() {
1506 config.current = this;
1508 this.callbackStarted = now();
1510 if (config.notrycatch) {
1518 this.pushFailure("Died on test
#" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
1520 // Else next test will carry the responsibility
1523 // Restart the tests if they're blocking
1524 if (config.blocking) {
1525 internalRecover(this);
1529 function runTest(test) {
1530 promise = test.callback.call(test.testEnvironment, test.assert);
1531 test.resolvePromise(promise);
1533 // If the test has a "lock
" on it, but the timeout is 0, then we push a
1534 // failure as the test should be synchronous.
1535 if (test.timeout === 0 && test.semaphore !== 0) {
1536 pushFailure("Test did not finish synchronously even though assert
.timeout( 0 ) was used
.", sourceFromStacktrace(2));
1541 after: function after() {
1545 queueHook: function queueHook(hook, hookName, hookOwner) {
1548 var callHook = function callHook() {
1549 var promise = hook.call(_this.testEnvironment, _this.assert);
1550 _this.resolvePromise(promise, hookName);
1553 var runHook = function runHook() {
1554 if (hookName === "before
") {
1555 if (hookOwner.unskippedTestsRun !== 0) {
1559 _this.preserveEnvironment = true;
1562 // The 'after' hook should only execute when there are not tests left and
1563 // when the 'after' and 'finish' tasks are the only tasks left to process
1564 if (hookName === "after
" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) {
1568 config.current = _this;
1569 if (config.notrycatch) {
1576 _this.pushFailure(hookName + " failed on
" + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
1584 // Currently only used for module level hooks, can be used to add global level ones
1585 hooks: function hooks(handler) {
1588 function processHooks(test, module) {
1589 if (module.parentModule) {
1590 processHooks(test, module.parentModule);
1593 if (module.hooks[handler].length) {
1594 for (var i = 0; i < module.hooks[handler].length; i++) {
1595 hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
1600 // Hooks are ignored on skipped tests
1602 processHooks(this, this.module);
1609 finish: function finish() {
1610 config.current = this;
1612 if (this.steps.length) {
1613 var stepsList = this.steps.join(", ");
1614 this.pushFailure("Expected assert
.verifySteps() to be called before end
of test
" + ("after using assert
.step(). Unverified steps
: " + stepsList), this.stack);
1617 if (config.requireExpects && this.expected === null) {
1618 this.pushFailure("Expected number
of assertions to be defined
, but
expect() was
" + "not called
.", this.stack);
1619 } else if (this.expected !== null && this.expected !== this.assertions.length) {
1620 this.pushFailure("Expected
" + this.expected + " assertions
, but
" + this.assertions.length + " were run
", this.stack);
1621 } else if (this.expected === null && !this.assertions.length) {
1622 this.pushFailure("Expected at least one assertion
, but none were run
- call
" + "expect(0) to accept zero assertions
.", this.stack);
1626 module = this.module,
1627 moduleName = module.name,
1628 testName = this.testName,
1629 skipped = !!this.skip,
1632 storage = config.storage;
1634 this.runtime = now() - this.started;
1636 config.stats.all += this.assertions.length;
1637 module.stats.all += this.assertions.length;
1639 for (i = 0; i < this.assertions.length; i++) {
1640 if (!this.assertions[i].result) {
1647 notifyTestsRan(module, skipped);
1649 // Store result when possible
1652 storage.setItem("qunit
-test
-" + moduleName + "-" + testName, bad);
1654 storage.removeItem("qunit
-test
-" + moduleName + "-" + testName);
1658 // After emitting the js-reporters event we cleanup the assertion data to
1659 // avoid leaking it. It is not used by the legacy testDone callbacks.
1660 emit("testEnd
", this.testReport.end(true));
1661 this.testReport.slimAssertions();
1663 runLoggingCallbacks("testDone
", {
1669 passed: this.assertions.length - bad,
1670 total: this.assertions.length,
1671 runtime: skipped ? 0 : this.runtime,
1673 // HTML Reporter use
1674 assertions: this.assertions,
1675 testId: this.testId,
1681 if (module.testsRun === numberOfTests(module)) {
1682 logSuiteEnd(module);
1684 // Check if the parent modules, iteratively, are done. If that the case,
1685 // we emit the `suiteEnd` event and trigger `moduleDone` callback.
1686 var parent = module.parentModule;
1687 while (parent && parent.testsRun === numberOfTests(parent)) {
1688 logSuiteEnd(parent);
1689 parent = parent.parentModule;
1693 config.current = undefined;
1695 function logSuiteEnd(module) {
1696 emit("suiteEnd
", module.suiteReport.end(true));
1697 runLoggingCallbacks("moduleDone
", {
1699 tests: module.tests,
1700 failed: module.stats.bad,
1701 passed: module.stats.all - module.stats.bad,
1702 total: module.stats.all,
1703 runtime: now() - module.stats.started
1708 preserveTestEnvironment: function preserveTestEnvironment() {
1709 if (this.preserveEnvironment) {
1710 this.module.testEnvironment = this.testEnvironment;
1711 this.testEnvironment = extend({}, this.module.testEnvironment);
1715 queue: function queue() {
1718 if (!this.valid()) {
1722 function runTest() {
1723 return [function () {
1725 }].concat(toConsumableArray(test.hooks("before
")), [function () {
1726 test.preserveTestEnvironment();
1727 }], toConsumableArray(test.hooks("beforeEach
")), [function () {
1729 }], toConsumableArray(test.hooks("afterEach
").reverse()), toConsumableArray(test.hooks("after
").reverse()), [function () {
1736 var previousFailCount = config.storage && +config.storage.getItem("qunit
-test
-" + this.module.name + "-" + this.testName);
1738 // Prioritize previously failed tests, detected from storage
1739 var prioritize = config.reorder && !!previousFailCount;
1741 this.previousFailure = !!previousFailCount;
1743 ProcessingQueue.add(runTest, prioritize, config.seed);
1745 // If the queue has already finished, we manually process the new test
1746 if (ProcessingQueue.finished) {
1747 ProcessingQueue.advance();
1752 pushResult: function pushResult(resultInfo) {
1753 if (this !== config.current) {
1754 throw new Error("Assertion occurred after test had finished
.");
1757 // Destructure of resultInfo = { result, actual, expected, message, negative }
1760 module: this.module.name,
1761 name: this.testName,
1762 result: resultInfo.result,
1763 message: resultInfo.message,
1764 actual: resultInfo.actual,
1765 testId: this.testId,
1766 negative: resultInfo.negative || false,
1767 runtime: now() - this.started,
1771 if (hasOwn.call(resultInfo, "expected
")) {
1772 details.expected = resultInfo.expected;
1775 if (!resultInfo.result) {
1776 source = resultInfo.source || sourceFromStacktrace();
1779 details.source = source;
1783 this.logAssertion(details);
1785 this.assertions.push({
1786 result: !!resultInfo.result,
1787 message: resultInfo.message
1791 pushFailure: function pushFailure(message, source, actual) {
1792 if (!(this instanceof Test)) {
1793 throw new Error("pushFailure() assertion outside test context
, was
" + sourceFromStacktrace(2));
1798 message: message || "error
",
1799 actual: actual || null,
1805 * Log assertion details using both the old QUnit.log interface and
1806 * QUnit.on( "assertion
" ) interface.
1810 logAssertion: function logAssertion(details) {
1811 runLoggingCallbacks("log
", details);
1814 passed: details.result,
1815 actual: details.actual,
1816 expected: details.expected,
1817 message: details.message,
1818 stack: details.source,
1821 this.testReport.pushAssertion(assertion);
1822 emit("assertion
", assertion);
1826 resolvePromise: function resolvePromise(promise, phase) {
1831 if (promise != null) {
1832 then = promise.then;
1833 if (objectType(then) === "function") {
1834 resume = internalStop(test);
1835 if (config.notrycatch) {
1836 then.call(promise, function () {
1840 then.call(promise, function () {
1842 }, function (error) {
1843 message = "Promise rejected
" + (!phase ? "during
" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
1844 test.pushFailure(message, extractStacktrace(error, 0));
1846 // Else next test will carry the responsibility
1850 internalRecover(test);
1857 valid: function valid() {
1858 var filter = config.filter,
1859 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
1860 module = config.module && config.module.toLowerCase(),
1861 fullName = this.module.name + ": " + this.testName;
1863 function moduleChainNameMatch(testModule) {
1864 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1865 if (testModuleName === module) {
1867 } else if (testModule.parentModule) {
1868 return moduleChainNameMatch(testModule.parentModule);
1874 function moduleChainIdMatch(testModule) {
1875 return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
1878 // Internally-generated tests are always valid
1879 if (this.callback && this.callback.validTest) {
1883 if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
1888 if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
1893 if (module && !moduleChainNameMatch(this.module)) {
1901 return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
1904 regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
1905 var regex = new RegExp(pattern, flags);
1906 var match = regex.test(fullName);
1908 return match !== exclude;
1911 stringFilter: function stringFilter(filter, fullName) {
1912 filter = filter.toLowerCase();
1913 fullName = fullName.toLowerCase();
1915 var include = filter.charAt(0) !== "!";
1917 filter = filter.slice(1);
1920 // If the filter matches, we need to honour include
1921 if (fullName.indexOf(filter) !== -1) {
1925 // Otherwise, do the opposite
1930 function pushFailure() {
1931 if (!config.current) {
1932 throw new Error("pushFailure() assertion outside test context
, in " + sourceFromStacktrace(2));
1935 // Gets current test obj
1936 var currentTest = config.current;
1938 return currentTest.pushFailure.apply(currentTest, arguments);
1941 function saveGlobal() {
1942 config.pollution = [];
1944 if (config.noglobals) {
1945 for (var key in global$1) {
1946 if (hasOwn.call(global$1, key)) {
1948 // In Opera sometimes DOM element ids show up here, ignore them
1949 if (/^qunit-test-output/.test(key)) {
1952 config.pollution.push(key);
1958 function checkPollution() {
1961 old = config.pollution;
1965 newGlobals = diff(config.pollution, old);
1966 if (newGlobals.length > 0) {
1967 pushFailure("Introduced global
variable(s
): " + newGlobals.join(", "));
1970 deletedGlobals = diff(old, config.pollution);
1971 if (deletedGlobals.length > 0) {
1972 pushFailure("Deleted global
variable(s
): " + deletedGlobals.join(", "));
1976 // Will be exposed as QUnit.test
1977 function test(testName, callback) {
1982 var newTest = new Test({
1990 function todo(testName, callback) {
1995 var newTest = new Test({
2004 // Will be exposed as QUnit.skip
2005 function skip(testName) {
2010 var test = new Test({
2018 // Will be exposed as QUnit.only
2019 function only(testName, callback) {
2024 config.queue.length = 0;
2027 var newTest = new Test({
2035 // Put a hold on processing and return a function that will release it.
2036 function internalStop(test) {
2037 test.semaphore += 1;
2038 config.blocking = true;
2040 // Set a recovery timeout, if so configured.
2041 if (defined.setTimeout) {
2042 var timeoutDuration = void 0;
2044 if (typeof test.timeout === "number
") {
2045 timeoutDuration = test.timeout;
2046 } else if (typeof config.testTimeout === "number
") {
2047 timeoutDuration = config.testTimeout;
2050 if (typeof timeoutDuration === "number
" && timeoutDuration > 0) {
2051 clearTimeout(config.timeout);
2052 config.timeout = setTimeout(function () {
2053 pushFailure("Test took longer than
" + timeoutDuration + "ms
; test timed out
.", sourceFromStacktrace(2));
2054 internalRecover(test);
2055 }, timeoutDuration);
2059 var released = false;
2060 return function resume() {
2066 test.semaphore -= 1;
2067 internalStart(test);
2071 // Forcefully release all processing holds.
2072 function internalRecover(test) {
2074 internalStart(test);
2077 // Release a processing hold, scheduling a resumption attempt if no holds remain.
2078 function internalStart(test) {
2080 // If semaphore is non-numeric, throw error
2081 if (isNaN(test.semaphore)) {
2084 pushFailure("Invalid value on test
.semaphore
", sourceFromStacktrace(2));
2088 // Don't start until equal number of stop-calls
2089 if (test.semaphore > 0) {
2093 // Throw an Error if start is called more often than stop
2094 if (test.semaphore < 0) {
2097 pushFailure("Tried to restart test
while already
started (test
's semaphore was 0 already)", sourceFromStacktrace(2));
2101 // Add a slight delay to allow more assertions etc.
2102 if (defined.setTimeout) {
2103 if (config.timeout) {
2104 clearTimeout(config.timeout);
2106 config.timeout = setTimeout(function () {
2107 if (test.semaphore > 0) {
2111 if (config.timeout) {
2112 clearTimeout(config.timeout);
2122 function collectTests(module) {
2123 var tests = [].concat(module.tests);
2124 var modules = [].concat(toConsumableArray(module.childModules));
2126 // Do a breadth-first traversal of the child modules
2127 while (modules.length) {
2128 var nextModule = modules.shift();
2129 tests.push.apply(tests, nextModule.tests);
2130 modules.push.apply(modules, toConsumableArray(nextModule.childModules));
2136 function numberOfTests(module) {
2137 return collectTests(module).length;
2140 function numberOfUnskippedTests(module) {
2141 return collectTests(module).filter(function (test) {
2146 function notifyTestsRan(module, skipped) {
2149 module.unskippedTestsRun++;
2151 while (module = module.parentModule) {
2154 module.unskippedTestsRun++;
2160 * Returns a function that proxies to the given method name on the globals
2161 * console object. The proxy will also detect if the console doesn't exist and
2162 * will appropriately no
-op
. This allows support
for IE9
, which doesn
't have a
2163 * console if the developer tools are not open.
2165 function consoleProxy(method) {
2166 return function () {
2168 console[method].apply(console, arguments);
2174 warn: consoleProxy("warn")
2177 var Assert = function () {
2178 function Assert(testContext) {
2179 classCallCheck(this, Assert);
2181 this.test = testContext;
2186 createClass(Assert, [{
2188 value: function timeout(duration) {
2189 if (typeof duration !== "number") {
2190 throw new Error("You must pass a number as the duration to assert.timeout");
2193 this.test.timeout = duration;
2196 // Documents a "step", which is a string value, in a test as a passing assertion
2200 value: function step(message) {
2201 var assertionMessage = message;
2202 var result = !!message;
2204 this.test.steps.push(message);
2206 if (objectType(message) === "undefined" || message === "") {
2207 assertionMessage = "You must provide a message to assert.step";
2208 } else if (objectType(message) !== "string") {
2209 assertionMessage = "You must provide a string value to assert.step";
2213 return this.pushResult({
2215 message: assertionMessage
2219 // Verifies the steps in a test match a given array of string values
2223 value: function verifySteps(steps, message) {
2225 // Since the steps array is just string values, we can clone with slice
2226 var actualStepsClone = this.test.steps.slice();
2227 this.deepEqual(actualStepsClone, steps, message);
2228 this.test.steps.length = 0;
2231 // Specify the number of expected assertions to guarantee that failed test
2232 // (no assertions are run at all) don't slip through
.
2236 value
: function expect(asserts
) {
2237 if (arguments
.length
=== 1) {
2238 this.test
.expected
= asserts
;
2240 return this.test
.expected
;
2244 // Put a hold on processing and return a function that will release it a maximum of once.
2248 value
: function async(count
) {
2249 var test
$$1 = this.test
;
2252 acceptCallCount
= count
;
2254 if (typeof acceptCallCount
=== "undefined") {
2255 acceptCallCount
= 1;
2258 var resume
= internalStop(test
$$1);
2260 return function done() {
2261 if (config
.current
!== test
$$1) {
2262 throw Error("assert.async callback called after test finished.");
2266 test
$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
2270 acceptCallCount
-= 1;
2271 if (acceptCallCount
> 0) {
2280 // Exports test.push() to the user API
2281 // Alias of pushResult.
2285 value
: function push(result
, actual
, expected
, message
, negative
) {
2286 Logger
.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).");
2288 var currentAssert
= this instanceof Assert
? this : config
.current
.assert
;
2289 return currentAssert
.pushResult({
2299 value
: function pushResult(resultInfo
) {
2301 // Destructure of resultInfo = { result, actual, expected, message, negative }
2303 var currentTest
= assert
instanceof Assert
&& assert
.test
|| config
.current
;
2305 // Backwards compatibility fix.
2306 // Allows the direct use of global exported assertions and QUnit.assert.*
2307 // Although, it's use is not recommended as it can leak assertions
2308 // to other tests from async tests, because we only get a reference to the current test,
2309 // not exactly the test where assertion were intended to be called.
2311 throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
2314 if (!(assert
instanceof Assert
)) {
2315 assert
= currentTest
.assert
;
2318 return assert
.test
.pushResult(resultInfo
);
2322 value
: function ok(result
, message
) {
2324 message
= result
? "okay" : "failed, expected argument to be truthy, was: " + dump
.parse(result
);
2336 value
: function notOk(result
, message
) {
2338 message
= !result
? "okay" : "failed, expected argument to be falsy, was: " + dump
.parse(result
);
2350 value
: function equal(actual
, expected
, message
) {
2352 // eslint-disable-next-line eqeqeq
2353 var result
= expected
== actual
;
2364 value
: function notEqual(actual
, expected
, message
) {
2366 // eslint-disable-next-line eqeqeq
2367 var result
= expected
!= actual
;
2379 value
: function propEqual(actual
, expected
, message
) {
2380 actual
= objectValues(actual
);
2381 expected
= objectValues(expected
);
2384 result
: equiv(actual
, expected
),
2391 key
: "notPropEqual",
2392 value
: function notPropEqual(actual
, expected
, message
) {
2393 actual
= objectValues(actual
);
2394 expected
= objectValues(expected
);
2397 result
: !equiv(actual
, expected
),
2406 value
: function deepEqual(actual
, expected
, message
) {
2408 result
: equiv(actual
, expected
),
2415 key
: "notDeepEqual",
2416 value
: function notDeepEqual(actual
, expected
, message
) {
2418 result
: !equiv(actual
, expected
),
2427 value
: function strictEqual(actual
, expected
, message
) {
2429 result
: expected
=== actual
,
2436 key
: "notStrictEqual",
2437 value
: function notStrictEqual(actual
, expected
, message
) {
2439 result
: expected
!== actual
,
2448 value
: function throws(block
, expected
, message
) {
2449 var actual
= void 0,
2452 var currentTest
= this instanceof Assert
&& this.test
|| config
.current
;
2454 // 'expected' is optional unless doing string comparison
2455 if (objectType(expected
) === "string") {
2456 if (message
== null) {
2460 throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary.");
2464 currentTest
.ignoreGlobalErrors
= true;
2466 block
.call(currentTest
.testEnvironment
);
2470 currentTest
.ignoreGlobalErrors
= false;
2473 var expectedType
= objectType(expected
);
2475 // We don't want to validate thrown error
2480 // Expected is a regexp
2481 } else if (expectedType
=== "regexp") {
2482 result
= expected
.test(errorString(actual
));
2484 // Expected is a constructor, maybe an Error constructor
2485 } else if (expectedType
=== "function" && actual
instanceof expected
) {
2488 // Expected is an Error object
2489 } else if (expectedType
=== "object") {
2490 result
= actual
instanceof expected
.constructor && actual
.name
=== expected
.name
&& actual
.message
=== expected
.message
;
2492 // Expected is a validation function which returns true if validation passed
2493 } else if (expectedType
=== "function" && expected
.call({}, actual
) === true) {
2499 currentTest
.assert
.pushResult({
2508 value
: function rejects(promise
, expected
, message
) {
2511 var currentTest
= this instanceof Assert
&& this.test
|| config
.current
;
2513 // 'expected' is optional unless doing string comparison
2514 if (objectType(expected
) === "string") {
2515 if (message
=== undefined) {
2517 expected
= undefined;
2519 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.";
2521 currentTest
.assert
.pushResult({
2530 var then
= promise
&& promise
.then
;
2531 if (objectType(then
) !== "function") {
2532 var _message
= "The value provided to `assert.rejects` in " + "\"" + currentTest
.testName
+ "\" was not a promise.";
2534 currentTest
.assert
.pushResult({
2543 var done
= this.async();
2545 return then
.call(promise
, function handleFulfillment() {
2546 var message
= "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest
.testName
+ "\" did not reject.";
2548 currentTest
.assert
.pushResult({
2555 }, function handleRejection(actual
) {
2556 var expectedType
= objectType(expected
);
2558 // We don't want to validate
2559 if (expected
=== undefined) {
2563 // Expected is a regexp
2564 } else if (expectedType
=== "regexp") {
2565 result
= expected
.test(errorString(actual
));
2567 // Expected is a constructor, maybe an Error constructor
2568 } else if (expectedType
=== "function" && actual
instanceof expected
) {
2571 // Expected is an Error object
2572 } else if (expectedType
=== "object") {
2573 result
= actual
instanceof expected
.constructor && actual
.name
=== expected
.name
&& actual
.message
=== expected
.message
;
2575 // Expected is a validation function which returns true if validation passed
2577 if (expectedType
=== "function") {
2578 result
= expected
.call({}, actual
) === true;
2581 // Expected is some other invalid type
2584 message
= "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest
.testName
+ "\": " + expectedType
+ ".";
2588 currentTest
.assert
.pushResult({
2602 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
2603 // Known to us are: Closure Compiler, Narwhal
2604 // eslint-disable-next-line dot-notation
2607 Assert
.prototype.raises
= Assert
.prototype["throws"];
2610 * Converts an error into a simple string for comparisons.
2612 * @param {Error} error
2615 function errorString(error
) {
2616 var resultErrorString
= error
.toString();
2618 if (resultErrorString
.substring(0, 7) === "[object") {
2619 var name
= error
.name
? error
.name
.toString() : "Error";
2620 var message
= error
.message
? error
.message
.toString() : "";
2622 if (name
&& message
) {
2623 return name
+ ": " + message
;
2626 } else if (message
) {
2632 return resultErrorString
;
2636 /* global module, exports, define */
2637 function exportQUnit(QUnit
) {
2639 if (defined
.document
) {
2641 // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
2642 if (window
.QUnit
&& window
.QUnit
.version
) {
2643 throw new Error("QUnit has already been defined.");
2646 window
.QUnit
= QUnit
;
2650 if (typeof module
!== "undefined" && module
&& module
.exports
) {
2651 module
.exports
= QUnit
;
2653 // For consistency with CommonJS environments' exports
2654 module
.exports
.QUnit
= QUnit
;
2657 // For CommonJS with exports, but without module.exports, like Rhino
2658 if (typeof exports
!== "undefined" && exports
) {
2659 exports
.QUnit
= QUnit
;
2662 if (typeof define
=== "function" && define
.amd
) {
2663 define(function () {
2666 QUnit
.config
.autostart
= false;
2669 // For Web/Service Workers
2670 if (self
$1 && self
$1.WorkerGlobalScope
&& self
$1 instanceof self
$1.WorkerGlobalScope
) {
2671 self
$1.QUnit
= QUnit
;
2675 var SuiteReport = function () {
2676 function SuiteReport(name
, parentSuite
) {
2677 classCallCheck(this, SuiteReport
);
2680 this.fullName
= parentSuite
? parentSuite
.fullName
.concat(name
) : [];
2683 this.childSuites
= [];
2686 parentSuite
.pushChildSuite(this);
2690 createClass(SuiteReport
, [{
2692 value
: function start(recordTime
) {
2694 this._startTime
= Date
.now();
2699 fullName
: this.fullName
.slice(),
2700 tests
: this.tests
.map(function (test
) {
2701 return test
.start();
2703 childSuites
: this.childSuites
.map(function (suite
) {
2704 return suite
.start();
2707 total
: this.getTestCounts().total
2713 value
: function end(recordTime
) {
2715 this._endTime
= Date
.now();
2720 fullName
: this.fullName
.slice(),
2721 tests
: this.tests
.map(function (test
) {
2724 childSuites
: this.childSuites
.map(function (suite
) {
2727 testCounts
: this.getTestCounts(),
2728 runtime
: this.getRuntime(),
2729 status
: this.getStatus()
2733 key
: "pushChildSuite",
2734 value
: function pushChildSuite(suite
) {
2735 this.childSuites
.push(suite
);
2739 value
: function pushTest(test
) {
2740 this.tests
.push(test
);
2744 value
: function getRuntime() {
2745 return this._endTime
- this._startTime
;
2748 key
: "getTestCounts",
2749 value
: function getTestCounts() {
2750 var counts
= arguments
.length
> 0 && arguments
[0] !== undefined ? arguments
[0] : { passed
: 0, failed
: 0, skipped
: 0, todo
: 0, total
: 0 };
2752 counts
= this.tests
.reduce(function (counts
, test
) {
2754 counts
[test
.getStatus()]++;
2761 return this.childSuites
.reduce(function (counts
, suite
) {
2762 return suite
.getTestCounts(counts
);
2767 value
: function getStatus() {
2768 var _getTestCounts
= this.getTestCounts(),
2769 total
= _getTestCounts
.total
,
2770 failed
= _getTestCounts
.failed
,
2771 skipped
= _getTestCounts
.skipped
,
2772 todo
= _getTestCounts
.todo
;
2777 if (skipped
=== total
) {
2779 } else if (todo
=== total
) {
2790 // Handle an unhandled exception. By convention, returns true if further
2791 // error handling should be suppressed and false otherwise.
2792 // In this case, we will only suppress further error handling if the
2793 // "ignoreGlobalErrors" configuration option is enabled.
2794 function onError(error
) {
2795 for (var _len
= arguments
.length
, args
= Array(_len
> 1 ? _len
- 1 : 0), _key
= 1; _key
< _len
; _key
++) {
2796 args
[_key
- 1] = arguments
[_key
];
2799 if (config
.current
) {
2800 if (config
.current
.ignoreGlobalErrors
) {
2803 pushFailure
.apply(undefined, [error
.message
, error
.fileName
+ ":" + error
.lineNumber
].concat(args
));
2805 test("global failure", extend(function () {
2806 pushFailure
.apply(undefined, [error
.message
, error
.fileName
+ ":" + error
.lineNumber
].concat(args
));
2807 }, { validTest
: true }));
2813 // Handle an unhandled rejection
2814 function onUnhandledRejection(reason
) {
2817 message
: reason
.message
|| "error",
2819 source
: reason
.stack
|| sourceFromStacktrace(3)
2822 var currentTest
= config
.current
;
2824 currentTest
.assert
.pushResult(resultInfo
);
2826 test("global failure", extend(function (assert
) {
2827 assert
.pushResult(resultInfo
);
2828 }, { validTest
: true }));
2832 var focused
= false;
2834 var globalSuite
= new SuiteReport();
2836 // The initial "currentModule" represents the global (or top-level) module that
2837 // is not explicitly defined by the user, therefore we add the "globalSuite" to
2838 // it since each module has a suiteReport associated with it.
2839 config
.currentModule
.suiteReport
= globalSuite
;
2841 var moduleStack
= [];
2842 var globalStartCalled
= false;
2843 var runStarted
= false;
2845 // Figure out if we're running the tests from a server or not
2846 QUnit
.isLocal
= !(defined
.document
&& window
.location
.protocol
!== "file:");
2848 // Expose the current QUnit version
2849 QUnit
.version
= "2.6.0";
2851 function createModule(name
, testEnvironment
, modifiers
) {
2852 var parentModule
= moduleStack
.length
? moduleStack
.slice(-1)[0] : null;
2853 var moduleName
= parentModule
!== null ? [parentModule
.name
, name
].join(" > ") : name
;
2854 var parentSuite
= parentModule
? parentModule
.suiteReport
: globalSuite
;
2856 var skip
$$1 = parentModule
!== null && parentModule
.skip
|| modifiers
.skip
;
2857 var todo
$$1 = parentModule
!== null && parentModule
.todo
|| modifiers
.todo
;
2861 parentModule
: parentModule
,
2863 moduleId
: generateHash(moduleName
),
2865 unskippedTestsRun
: 0,
2867 suiteReport
: new SuiteReport(name
, parentSuite
),
2869 // Pass along `skip` and `todo` properties from parent module, in case
2870 // there is one, to childs. And use own otherwise.
2871 // This property will be used to mark own tests and tests of child suites
2872 // as either `skipped` or `todo`.
2874 todo
: skip
$$1 ? false : todo
$$1
2879 parentModule
.childModules
.push(module
);
2880 extend(env
, parentModule
.testEnvironment
);
2882 extend(env
, testEnvironment
);
2883 module
.testEnvironment
= env
;
2885 config
.modules
.push(module
);
2889 function processModule(name
, options
, executeNow
) {
2890 var modifiers
= arguments
.length
> 3 && arguments
[3] !== undefined ? arguments
[3] : {};
2892 var module
= createModule(name
, options
, modifiers
);
2894 // Move any hooks to a 'hooks' object
2895 var testEnvironment
= module
.testEnvironment
;
2896 var hooks
= module
.hooks
= {};
2898 setHookFromEnvironment(hooks
, testEnvironment
, "before");
2899 setHookFromEnvironment(hooks
, testEnvironment
, "beforeEach");
2900 setHookFromEnvironment(hooks
, testEnvironment
, "afterEach");
2901 setHookFromEnvironment(hooks
, testEnvironment
, "after");
2903 function setHookFromEnvironment(hooks
, environment
, name
) {
2904 var potentialHook
= environment
[name
];
2905 hooks
[name
] = typeof potentialHook
=== "function" ? [potentialHook
] : [];
2906 delete environment
[name
];
2910 before
: setHookFunction(module
, "before"),
2911 beforeEach
: setHookFunction(module
, "beforeEach"),
2912 afterEach
: setHookFunction(module
, "afterEach"),
2913 after
: setHookFunction(module
, "after")
2916 var currentModule
= config
.currentModule
;
2917 if (objectType(executeNow
) === "function") {
2918 moduleStack
.push(module
);
2919 config
.currentModule
= module
;
2920 executeNow
.call(module
.testEnvironment
, moduleFns
);
2922 module
= module
.parentModule
|| currentModule
;
2925 config
.currentModule
= module
;
2928 // TODO: extract this to a new file alongside its related functions
2929 function module
$1(name
, options
, executeNow
) {
2934 if (arguments
.length
=== 2) {
2935 if (objectType(options
) === "function") {
2936 executeNow
= options
;
2937 options
= undefined;
2941 processModule(name
, options
, executeNow
);
2944 module
$1.only = function () {
2949 config
.modules
.length
= 0;
2950 config
.queue
.length
= 0;
2952 module
$1.apply(undefined, arguments
);
2957 module
$1.skip = function (name
, options
, executeNow
) {
2962 if (arguments
.length
=== 2) {
2963 if (objectType(options
) === "function") {
2964 executeNow
= options
;
2965 options
= undefined;
2969 processModule(name
, options
, executeNow
, { skip
: true });
2972 module
$1.todo = function (name
, options
, executeNow
) {
2977 if (arguments
.length
=== 2) {
2978 if (objectType(options
) === "function") {
2979 executeNow
= options
;
2980 options
= undefined;
2984 processModule(name
, options
, executeNow
, { todo
: true });
3000 start
: function start(count
) {
3001 var globalStartAlreadyCalled
= globalStartCalled
;
3003 if (!config
.current
) {
3004 globalStartCalled
= true;
3007 throw new Error("Called start() while test already started running");
3008 } else if (globalStartAlreadyCalled
|| count
> 1) {
3009 throw new Error("Called start() outside of a test context too many times");
3010 } else if (config
.autostart
) {
3011 throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
3012 } else if (!config
.pageLoaded
) {
3014 // The page isn't completely loaded yet, so we set autostart and then
3015 // load if we're in Node or wait for the browser's load event.
3016 config
.autostart
= true;
3018 // Starts from Node even if .load was not previously called. We still return
3019 // early otherwise we'll wind up "beginning" twice.
3020 if (!defined
.document
) {
3027 throw new Error("QUnit.start cannot be called inside a test context.");
3037 objectType
: objectType
,
3041 load
: function load() {
3042 config
.pageLoaded
= true;
3044 // Initialize the configuration options
3046 stats
: { all
: 0, bad
: 0 },
3054 config
.blocking
= false;
3056 if (config
.autostart
) {
3062 stack
: function stack(offset
) {
3063 offset
= (offset
|| 0) + 2;
3064 return sourceFromStacktrace(offset
);
3069 onUnhandledRejection
: onUnhandledRejection
3072 QUnit
.pushFailure
= pushFailure
;
3073 QUnit
.assert
= Assert
.prototype;
3074 QUnit
.equiv
= equiv
;
3077 registerLoggingCallbacks(QUnit
);
3079 function scheduleBegin() {
3083 // Add a slight delay to allow definition of more modules and tests.
3084 if (defined
.setTimeout
) {
3085 setTimeout(function () {
3098 // If the test run hasn't officially begun yet
3099 if (!config
.started
) {
3101 // Record the time of the test run's beginning
3102 config
.started
= now();
3104 // Delete the loose unnamed module if unused.
3105 if (config
.modules
[0].name
=== "" && config
.modules
[0].tests
.length
=== 0) {
3106 config
.modules
.shift();
3109 // Avoid unnecessary information by not logging modules' test environments
3110 for (i
= 0, l
= config
.modules
.length
; i
< l
; i
++) {
3112 name
: config
.modules
[i
].name
,
3113 tests
: config
.modules
[i
].tests
3117 // The test run is officially beginning now
3118 emit("runStart", globalSuite
.start(true));
3119 runLoggingCallbacks("begin", {
3120 totalTests
: Test
.count
,
3125 config
.blocking
= false;
3126 ProcessingQueue
.advance();
3129 function setHookFunction(module
, hookName
) {
3130 return function setHook(callback
) {
3131 module
.hooks
[hookName
].push(callback
);
3139 if (typeof window
=== "undefined" || typeof document
=== "undefined") {
3143 var config
= QUnit
.config
,
3144 hasOwn
= Object
.prototype.hasOwnProperty
;
3146 // Stores fixture HTML for resetting later
3147 function storeFixture() {
3149 // Avoid overwriting user-defined values
3150 if (hasOwn
.call(config
, "fixture")) {
3154 var fixture
= document
.getElementById("qunit-fixture");
3156 config
.fixture
= fixture
.cloneNode(true);
3160 QUnit
.begin(storeFixture
);
3162 // Resets the fixture DOM element if available.
3163 function resetFixture() {
3164 if (config
.fixture
== null) {
3168 var fixture
= document
.getElementById("qunit-fixture");
3169 var resetFixtureType
= _typeof(config
.fixture
);
3170 if (resetFixtureType
=== "string") {
3172 // support user defined values for `config.fixture`
3173 var newFixture
= document
.createElement("div");
3174 newFixture
.setAttribute("id", "qunit-fixture");
3175 newFixture
.innerHTML
= config
.fixture
;
3176 fixture
.parentNode
.replaceChild(newFixture
, fixture
);
3178 var clonedFixture
= config
.fixture
.cloneNode(true);
3179 fixture
.parentNode
.replaceChild(clonedFixture
, fixture
);
3183 QUnit
.testStart(resetFixture
);
3188 // Only interact with URLs via window.location
3189 var location
= typeof window
!== "undefined" && window
.location
;
3194 var urlParams
= getUrlParams();
3196 QUnit
.urlParams
= urlParams
;
3198 // Match module/test by inclusion in an array
3199 QUnit
.config
.moduleId
= [].concat(urlParams
.moduleId
|| []);
3200 QUnit
.config
.testId
= [].concat(urlParams
.testId
|| []);
3202 // Exact case-insensitive match of the module name
3203 QUnit
.config
.module
= urlParams
.module
;
3205 // Regular expression or case-insenstive substring match against "moduleName: testName"
3206 QUnit
.config
.filter
= urlParams
.filter
;
3208 // Test order randomization
3209 if (urlParams
.seed
=== true) {
3211 // Generate a random seed if the option is specified without a value
3212 QUnit
.config
.seed
= Math
.random().toString(36).slice(2);
3213 } else if (urlParams
.seed
) {
3214 QUnit
.config
.seed
= urlParams
.seed
;
3217 // Add URL-parameter-mapped config values with UI form rendering data
3218 QUnit
.config
.urlConfig
.push({
3220 label
: "Hide passed tests",
3221 tooltip
: "Only show tests and assertions that fail. Stored as query-strings."
3224 label
: "Check for Globals",
3225 tooltip
: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
3228 label
: "No try-catch",
3229 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
3232 QUnit
.begin(function () {
3235 urlConfig
= QUnit
.config
.urlConfig
;
3237 for (i
= 0; i
< urlConfig
.length
; i
++) {
3239 // Options can be either strings or objects with nonempty "id" properties
3240 option
= QUnit
.config
.urlConfig
[i
];
3241 if (typeof option
!== "string") {
3245 if (QUnit
.config
[option
] === undefined) {
3246 QUnit
.config
[option
] = urlParams
[option
];
3251 function getUrlParams() {
3252 var i
, param
, name
, value
;
3253 var urlParams
= Object
.create(null);
3254 var params
= location
.search
.slice(1).split("&");
3255 var length
= params
.length
;
3257 for (i
= 0; i
< length
; i
++) {
3259 param
= params
[i
].split("=");
3260 name
= decodeQueryParam(param
[0]);
3262 // Allow just a key to turn on a flag, e.g., test.html?noglobals
3263 value
= param
.length
=== 1 || decodeQueryParam(param
.slice(1).join("="));
3264 if (name
in urlParams
) {
3265 urlParams
[name
] = [].concat(urlParams
[name
], value
);
3267 urlParams
[name
] = value
;
3275 function decodeQueryParam(param
) {
3276 return decodeURIComponent(param
.replace(/\+/g, "%20"));
3287 // Escape text for attribute or text content.
3288 function escapeText(s
) {
3294 // Both single quotes and double quotes (for attributes)
3295 return s
.replace(/['"<>&]/g, function (s
) {
3313 // Don't load the HTML Reporter on non-browser environments
3314 if (typeof window
=== "undefined" || !window
.document
) {
3318 var config
= QUnit
.config
,
3319 document
$$1 = window
.document
,
3320 collapseNext
= false,
3321 hasOwn
= Object
.prototype.hasOwnProperty
,
3322 unfilteredUrl
= setUrl({ filter
: undefined, module
: undefined,
3323 moduleId
: undefined, testId
: undefined }),
3326 function addEvent(elem
, type
, fn
) {
3327 elem
.addEventListener(type
, fn
, false);
3330 function removeEvent(elem
, type
, fn
) {
3331 elem
.removeEventListener(type
, fn
, false);
3334 function addEvents(elems
, type
, fn
) {
3335 var i
= elems
.length
;
3337 addEvent(elems
[i
], type
, fn
);
3341 function hasClass(elem
, name
) {
3342 return (" " + elem
.className
+ " ").indexOf(" " + name
+ " ") >= 0;
3345 function addClass(elem
, name
) {
3346 if (!hasClass(elem
, name
)) {
3347 elem
.className
+= (elem
.className
? " " : "") + name
;
3351 function toggleClass(elem
, name
, force
) {
3352 if (force
|| typeof force
=== "undefined" && !hasClass(elem
, name
)) {
3353 addClass(elem
, name
);
3355 removeClass(elem
, name
);
3359 function removeClass(elem
, name
) {
3360 var set = " " + elem
.className
+ " ";
3362 // Class name may appear multiple times
3363 while (set.indexOf(" " + name
+ " ") >= 0) {
3364 set = set.replace(" " + name
+ " ", " ");
3367 // Trim for prettiness
3368 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
3372 return document
$$1.getElementById
&& document
$$1.getElementById(name
);
3375 function abortTests() {
3376 var abortButton
= id("qunit-abort-tests-button");
3378 abortButton
.disabled
= true;
3379 abortButton
.innerHTML
= "Aborting...";
3381 QUnit
.config
.queue
.length
= 0;
3385 function interceptNavigation(ev
) {
3388 if (ev
&& ev
.preventDefault
) {
3389 ev
.preventDefault();
3395 function getUrlConfigHtml() {
3402 urlConfig
= config
.urlConfig
,
3405 for (i
= 0; i
< urlConfig
.length
; i
++) {
3407 // Options can be either strings or objects with nonempty "id" properties
3408 val
= config
.urlConfig
[i
];
3409 if (typeof val
=== "string") {
3416 escaped
= escapeText(val
.id
);
3417 escapedTooltip
= escapeText(val
.tooltip
);
3419 if (!val
.value
|| typeof val
.value
=== "string") {
3420 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+ "' title='" + escapedTooltip
+ "'><input id='qunit-urlconfig-" + escaped
+ "' name='" + escaped
+ "' type='checkbox'" + (val
.value
? " value='" + escapeText(val
.value
) + "'" : "") + (config
[val
.id
] ? " checked='checked'" : "") + " title='" + escapedTooltip
+ "' />" + escapeText(val
.label
) + "</label>";
3422 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+ "' title='" + escapedTooltip
+ "'>" + val
.label
+ ": </label><select id='qunit-urlconfig-" + escaped
+ "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
3424 if (QUnit
.is("array", val
.value
)) {
3425 for (j
= 0; j
< val
.value
.length
; j
++) {
3426 escaped
= escapeText(val
.value
[j
]);
3427 urlConfigHtml
+= "<option value='" + escaped
+ "'" + (config
[val
.id
] === val
.value
[j
] ? (selection
= true) && " selected='selected'" : "") + ">" + escaped
+ "</option>";
3430 for (j
in val
.value
) {
3431 if (hasOwn
.call(val
.value
, j
)) {
3432 urlConfigHtml
+= "<option value='" + escapeText(j
) + "'" + (config
[val
.id
] === j
? (selection
= true) && " selected='selected'" : "") + ">" + escapeText(val
.value
[j
]) + "</option>";
3436 if (config
[val
.id
] && !selection
) {
3437 escaped
= escapeText(config
[val
.id
]);
3438 urlConfigHtml
+= "<option value='" + escaped
+ "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
3440 urlConfigHtml
+= "</select>";
3444 return urlConfigHtml
;
3447 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3448 // Updates the URL with the new state of `config.urlConfig` values.
3449 function toolbarChanged() {
3456 // Detect if field is a select menu or a checkbox
3457 if ("selectedIndex" in field
) {
3458 value
= field
.options
[field
.selectedIndex
].value
|| undefined;
3460 value
= field
.checked
? field
.defaultValue
|| true : undefined;
3463 params
[field
.name
] = value
;
3464 updatedUrl
= setUrl(params
);
3466 // Check if we can apply the change without a page refresh
3467 if ("hidepassed" === field
.name
&& "replaceState" in window
.history
) {
3468 QUnit
.urlParams
[field
.name
] = value
;
3469 config
[field
.name
] = value
|| false;
3470 tests
= id("qunit-tests");
3472 toggleClass(tests
, "hidepass", value
|| false);
3474 window
.history
.replaceState(null, "", updatedUrl
);
3476 window
.location
= updatedUrl
;
3480 function setUrl(params
) {
3485 location
= window
.location
;
3487 params
= QUnit
.extend(QUnit
.extend({}, QUnit
.urlParams
), params
);
3489 for (key
in params
) {
3491 // Skip inherited or undefined properties
3492 if (hasOwn
.call(params
, key
) && params
[key
] !== undefined) {
3494 // Output a parameter for each value of this key
3495 // (but usually just one)
3496 arrValue
= [].concat(params
[key
]);
3497 for (i
= 0; i
< arrValue
.length
; i
++) {
3498 querystring
+= encodeURIComponent(key
);
3499 if (arrValue
[i
] !== true) {
3500 querystring
+= "=" + encodeURIComponent(arrValue
[i
]);
3506 return location
.protocol
+ "//" + location
.host
+ location
.pathname
+ querystring
.slice(0, -1);
3509 function applyUrlParams() {
3511 selectedModules
= [],
3512 modulesList
= id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
3513 filter
= id("qunit-filter-input").value
;
3515 for (i
= 0; i
< modulesList
.length
; i
++) {
3516 if (modulesList
[i
].checked
) {
3517 selectedModules
.push(modulesList
[i
].value
);
3521 window
.location
= setUrl({
3522 filter
: filter
=== "" ? undefined : filter
,
3523 moduleId
: selectedModules
.length
=== 0 ? undefined : selectedModules
,
3525 // Remove module and testId filter
3531 function toolbarUrlConfigContainer() {
3532 var urlConfigContainer
= document
$$1.createElement("span");
3534 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
3535 addClass(urlConfigContainer
, "qunit-url-config");
3537 addEvents(urlConfigContainer
.getElementsByTagName("input"), "change", toolbarChanged
);
3538 addEvents(urlConfigContainer
.getElementsByTagName("select"), "change", toolbarChanged
);
3540 return urlConfigContainer
;
3543 function abortTestsButton() {
3544 var button
= document
$$1.createElement("button");
3545 button
.id
= "qunit-abort-tests-button";
3546 button
.innerHTML
= "Abort";
3547 addEvent(button
, "click", abortTests
);
3551 function toolbarLooseFilter() {
3552 var filter
= document
$$1.createElement("form"),
3553 label
= document
$$1.createElement("label"),
3554 input
= document
$$1.createElement("input"),
3555 button
= document
$$1.createElement("button");
3557 addClass(filter
, "qunit-filter");
3559 label
.innerHTML
= "Filter: ";
3561 input
.type
= "text";
3562 input
.value
= config
.filter
|| "";
3563 input
.name
= "filter";
3564 input
.id
= "qunit-filter-input";
3566 button
.innerHTML
= "Go";
3568 label
.appendChild(input
);
3570 filter
.appendChild(label
);
3571 filter
.appendChild(document
$$1.createTextNode(" "));
3572 filter
.appendChild(button
);
3573 addEvent(filter
, "submit", interceptNavigation
);
3578 function moduleListHtml() {
3583 for (i
= 0; i
< config
.modules
.length
; i
++) {
3584 if (config
.modules
[i
].name
!== "") {
3585 checked
= config
.moduleId
.indexOf(config
.modules
[i
].moduleId
) > -1;
3586 html
+= "<li><label class='clickable" + (checked
? " checked" : "") + "'><input type='checkbox' " + "value='" + config
.modules
[i
].moduleId
+ "'" + (checked
? " checked='checked'" : "") + " />" + escapeText(config
.modules
[i
].name
) + "</label></li>";
3593 function toolbarModuleFilter() {
3597 moduleFilter
= document
$$1.createElement("form"),
3598 label
= document
$$1.createElement("label"),
3599 moduleSearch
= document
$$1.createElement("input"),
3600 dropDown
= document
$$1.createElement("div"),
3601 actions
= document
$$1.createElement("span"),
3602 dropDownList
= document
$$1.createElement("ul"),
3605 moduleSearch
.id
= "qunit-modulefilter-search";
3606 addEvent(moduleSearch
, "input", searchInput
);
3607 addEvent(moduleSearch
, "input", searchFocus
);
3608 addEvent(moduleSearch
, "focus", searchFocus
);
3609 addEvent(moduleSearch
, "click", searchFocus
);
3611 label
.id
= "qunit-modulefilter-search-container";
3612 label
.innerHTML
= "Module: ";
3613 label
.appendChild(moduleSearch
);
3615 actions
.id
= "qunit-modulefilter-actions";
3616 actions
.innerHTML
= "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + (config
.moduleId
.length
? "" : " checked") + "'><input type='checkbox'" + (config
.moduleId
.length
? "" : " checked='checked'") + ">All modules</label>";
3617 allCheckbox
= actions
.lastChild
.firstChild
;
3618 commit
= actions
.firstChild
;
3619 reset
= commit
.nextSibling
;
3620 addEvent(commit
, "click", applyUrlParams
);
3622 dropDownList
.id
= "qunit-modulefilter-dropdown-list";
3623 dropDownList
.innerHTML
= moduleListHtml();
3625 dropDown
.id
= "qunit-modulefilter-dropdown";
3626 dropDown
.style
.display
= "none";
3627 dropDown
.appendChild(actions
);
3628 dropDown
.appendChild(dropDownList
);
3629 addEvent(dropDown
, "change", selectionChange
);
3632 moduleFilter
.id
= "qunit-modulefilter";
3633 moduleFilter
.appendChild(label
);
3634 moduleFilter
.appendChild(dropDown
);
3635 addEvent(moduleFilter
, "submit", interceptNavigation
);
3636 addEvent(moduleFilter
, "reset", function () {
3638 // Let the reset happen, then update styles
3639 window
.setTimeout(selectionChange
);
3642 // Enables show/hide for the dropdown
3643 function searchFocus() {
3644 if (dropDown
.style
.display
!== "none") {
3648 dropDown
.style
.display
= "block";
3649 addEvent(document
$$1, "click", hideHandler
);
3650 addEvent(document
$$1, "keydown", hideHandler
);
3652 // Hide on Escape keydown or outside-container click
3653 function hideHandler(e
) {
3654 var inContainer
= moduleFilter
.contains(e
.target
);
3656 if (e
.keyCode
=== 27 || !inContainer
) {
3657 if (e
.keyCode
=== 27 && inContainer
) {
3658 moduleSearch
.focus();
3660 dropDown
.style
.display
= "none";
3661 removeEvent(document
$$1, "click", hideHandler
);
3662 removeEvent(document
$$1, "keydown", hideHandler
);
3663 moduleSearch
.value
= "";
3669 // Processes module search box input
3670 function searchInput() {
3673 searchText
= moduleSearch
.value
.toLowerCase(),
3674 listItems
= dropDownList
.children
;
3676 for (i
= 0; i
< listItems
.length
; i
++) {
3677 item
= listItems
[i
];
3678 if (!searchText
|| item
.textContent
.toLowerCase().indexOf(searchText
) > -1) {
3679 item
.style
.display
= "";
3681 item
.style
.display
= "none";
3686 // Processes selection changes
3687 function selectionChange(evt
) {
3690 checkbox
= evt
&& evt
.target
|| allCheckbox
,
3691 modulesList
= dropDownList
.getElementsByTagName("input"),
3694 toggleClass(checkbox
.parentNode
, "checked", checkbox
.checked
);
3697 if (checkbox
.checked
&& checkbox
!== allCheckbox
) {
3698 allCheckbox
.checked
= false;
3699 removeClass(allCheckbox
.parentNode
, "checked");
3701 for (i
= 0; i
< modulesList
.length
; i
++) {
3702 item
= modulesList
[i
];
3704 toggleClass(item
.parentNode
, "checked", item
.checked
);
3705 } else if (checkbox
=== allCheckbox
&& checkbox
.checked
) {
3706 item
.checked
= false;
3707 removeClass(item
.parentNode
, "checked");
3709 dirty
= dirty
|| item
.checked
!== item
.defaultChecked
;
3711 selectedNames
.push(item
.parentNode
.textContent
);
3715 commit
.style
.display
= reset
.style
.display
= dirty
? "" : "none";
3716 moduleSearch
.placeholder
= selectedNames
.join(", ") || allCheckbox
.parentNode
.textContent
;
3717 moduleSearch
.title
= "Type to filter list. Current selection:\n" + (selectedNames
.join("\n") || allCheckbox
.parentNode
.textContent
);
3720 return moduleFilter
;
3723 function appendToolbar() {
3724 var toolbar
= id("qunit-testrunner-toolbar");
3727 toolbar
.appendChild(toolbarUrlConfigContainer());
3728 toolbar
.appendChild(toolbarModuleFilter());
3729 toolbar
.appendChild(toolbarLooseFilter());
3730 toolbar
.appendChild(document
$$1.createElement("div")).className
= "clearfix";
3734 function appendHeader() {
3735 var header
= id("qunit-header");
3738 header
.innerHTML
= "<a href='" + escapeText(unfilteredUrl
) + "'>" + header
.innerHTML
+ "</a> ";
3742 function appendBanner() {
3743 var banner
= id("qunit-banner");
3746 banner
.className
= "";
3750 function appendTestResults() {
3751 var tests
= id("qunit-tests"),
3752 result
= id("qunit-testresult"),
3756 result
.parentNode
.removeChild(result
);
3760 tests
.innerHTML
= "";
3761 result
= document
$$1.createElement("p");
3762 result
.id
= "qunit-testresult";
3763 result
.className
= "result";
3764 tests
.parentNode
.insertBefore(result
, tests
);
3765 result
.innerHTML
= "<div id=\"qunit-testresult-display\">Running...<br /> </div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
3766 controls
= id("qunit-testresult-controls");
3770 controls
.appendChild(abortTestsButton());
3774 function appendFilteredTest() {
3775 var testId
= QUnit
.config
.testId
;
3776 if (!testId
|| testId
.length
<= 0) {
3779 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId
.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl
) + "'>Run all tests</a></div>";
3782 function appendUserAgent() {
3783 var userAgent
= id("qunit-userAgent");
3786 userAgent
.innerHTML
= "";
3787 userAgent
.appendChild(document
$$1.createTextNode("QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
));
3791 function appendInterface() {
3792 var qunit
= id("qunit");
3795 qunit
.innerHTML
= "<h1 id='qunit-header'>" + escapeText(document
$$1.title
) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
3800 appendTestResults();
3805 function appendTestsList(modules
) {
3806 var i
, l
, x
, z
, test
, moduleObj
;
3808 for (i
= 0, l
= modules
.length
; i
< l
; i
++) {
3809 moduleObj
= modules
[i
];
3811 for (x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++) {
3812 test
= moduleObj
.tests
[x
];
3814 appendTest(test
.name
, test
.testId
, moduleObj
.name
);
3819 function appendTest(name
, testId
, moduleName
) {
3824 tests
= id("qunit-tests");
3830 title
= document
$$1.createElement("strong");
3831 title
.innerHTML
= getNameHtml(name
, moduleName
);
3833 rerunTrigger
= document
$$1.createElement("a");
3834 rerunTrigger
.innerHTML
= "Rerun";
3835 rerunTrigger
.href
= setUrl({ testId
: testId
});
3837 testBlock
= document
$$1.createElement("li");
3838 testBlock
.appendChild(title
);
3839 testBlock
.appendChild(rerunTrigger
);
3840 testBlock
.id
= "qunit-test-output-" + testId
;
3842 assertList
= document
$$1.createElement("ol");
3843 assertList
.className
= "qunit-assert-list";
3845 testBlock
.appendChild(assertList
);
3847 tests
.appendChild(testBlock
);
3850 // HTML Reporter initialization and load
3851 QUnit
.begin(function (details
) {
3852 var i
, moduleObj
, tests
;
3854 // Sort modules by name for the picker
3855 for (i
= 0; i
< details
.modules
.length
; i
++) {
3856 moduleObj
= details
.modules
[i
];
3857 if (moduleObj
.name
) {
3858 modulesList
.push(moduleObj
.name
);
3861 modulesList
.sort(function (a
, b
) {
3862 return a
.localeCompare(b
);
3865 // Initialize QUnit elements
3867 appendTestsList(details
.modules
);
3868 tests
= id("qunit-tests");
3869 if (tests
&& config
.hidepassed
) {
3870 addClass(tests
, "hidepass");
3874 QUnit
.done(function (details
) {
3875 var banner
= id("qunit-banner"),
3876 tests
= id("qunit-tests"),
3877 abortButton
= id("qunit-abort-tests-button"),
3878 totalTests
= stats
.passedTests
+ stats
.skippedTests
+ stats
.todoTests
+ stats
.failedTests
,
3879 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(""),
3884 // Update remaing tests to aborted
3885 if (abortButton
&& abortButton
.disabled
) {
3886 html
= "Tests aborted after " + details
.runtime
+ " milliseconds.";
3888 for (var i
= 0; i
< tests
.children
.length
; i
++) {
3889 test
= tests
.children
[i
];
3890 if (test
.className
=== "" || test
.className
=== "running") {
3891 test
.className
= "aborted";
3892 assertList
= test
.getElementsByTagName("ol")[0];
3893 assertLi
= document
$$1.createElement("li");
3894 assertLi
.className
= "fail";
3895 assertLi
.innerHTML
= "Test aborted.";
3896 assertList
.appendChild(assertLi
);
3901 if (banner
&& (!abortButton
|| abortButton
.disabled
=== false)) {
3902 banner
.className
= stats
.failedTests
? "qunit-fail" : "qunit-pass";
3906 abortButton
.parentNode
.removeChild(abortButton
);
3910 id("qunit-testresult-display").innerHTML
= html
;
3913 if (config
.altertitle
&& document
$$1.title
) {
3915 // Show ✖ for good, ✔ for bad suite result in title
3916 // use escape sequences in case file gets loaded with non-utf-8
3918 document
$$1.title
= [stats
.failedTests
? "\u2716" : "\u2714", document
$$1.title
.replace(/^[\u2714\u2716] /i, "")].join(" ");
3921 // Scroll back to top to show results
3922 if (config
.scrolltop
&& window
.scrollTo
) {
3923 window
.scrollTo(0, 0);
3927 function getNameHtml(name
, module
) {
3931 nameHtml
= "<span class='module-name'>" + escapeText(module
) + "</span>: ";
3934 nameHtml
+= "<span class='test-name'>" + escapeText(name
) + "</span>";
3939 QUnit
.testStart(function (details
) {
3940 var running
, testBlock
, bad
;
3942 testBlock
= id("qunit-test-output-" + details
.testId
);
3944 testBlock
.className
= "running";
3947 // Report later registered tests
3948 appendTest(details
.name
, details
.testId
, details
.module
);
3951 running
= id("qunit-testresult-display");
3953 bad
= QUnit
.config
.reorder
&& details
.previousFailure
;
3955 running
.innerHTML
= [bad
? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details
.name
, details
.module
)].join("");
3959 function stripHtml(string
) {
3961 // Strip tags, html entity and whitespaces
3962 return string
.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, "");
3965 QUnit
.log(function (details
) {
3973 testItem
= id("qunit-test-output-" + details
.testId
);
3979 message
= escapeText(details
.message
) || (details
.result
? "okay" : "failed");
3980 message
= "<span class='test-message'>" + message
+ "</span>";
3981 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
3983 // The pushFailure doesn't provide details.expected
3984 // when it calls, it's implicit to also not show expected and diff stuff
3985 // Also, we need to check details.expected existence, as it can exist and be undefined
3986 if (!details
.result
&& hasOwn
.call(details
, "expected")) {
3987 if (details
.negative
) {
3988 expected
= "NOT " + QUnit
.dump
.parse(details
.expected
);
3990 expected
= QUnit
.dump
.parse(details
.expected
);
3993 actual
= QUnit
.dump
.parse(details
.actual
);
3994 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected
) + "</pre></td></tr>";
3996 if (actual
!== expected
) {
3998 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual
) + "</pre></td></tr>";
4000 if (typeof details
.actual
=== "number" && typeof details
.expected
=== "number") {
4001 if (!isNaN(details
.actual
) && !isNaN(details
.expected
)) {
4003 diff
= details
.actual
- details
.expected
;
4004 diff
= (diff
> 0 ? "+" : "") + diff
;
4006 } else if (typeof details
.actual
!== "boolean" && typeof details
.expected
!== "boolean") {
4007 diff
= QUnit
.diff(expected
, actual
);
4009 // don't show diff if there is zero overlap
4010 showDiff
= stripHtml(diff
).length
!== stripHtml(expected
).length
+ stripHtml(actual
).length
;
4014 message
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff
+ "</pre></td></tr>";
4016 } else if (expected
.indexOf("[object Array]") !== -1 || expected
.indexOf("[object Object]") !== -1) {
4017 message
+= "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the depth of object is more than current max depth (" + QUnit
.config
.maxDepth
+ ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " + " run with a higher max depth or <a href='" + escapeText(setUrl({ maxDepth
: -1 })) + "'>" + "Rerun</a> without max depth.</p></td></tr>";
4019 message
+= "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
4022 if (details
.source
) {
4023 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details
.source
) + "</pre></td></tr>";
4026 message
+= "</table>";
4028 // This occurs when pushFailure is set and we have an extracted stack trace
4029 } else if (!details
.result
&& details
.source
) {
4030 message
+= "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details
.source
) + "</pre></td></tr>" + "</table>";
4033 assertList
= testItem
.getElementsByTagName("ol")[0];
4035 assertLi
= document
$$1.createElement("li");
4036 assertLi
.className
= details
.result
? "pass" : "fail";
4037 assertLi
.innerHTML
= message
;
4038 assertList
.appendChild(assertLi
);
4041 QUnit
.testDone(function (details
) {
4051 tests
= id("qunit-tests");
4057 testItem
= id("qunit-test-output-" + details
.testId
);
4059 assertList
= testItem
.getElementsByTagName("ol")[0];
4061 good
= details
.passed
;
4062 bad
= details
.failed
;
4064 // This test passed if it has no unexpected failed assertions
4065 var testPassed
= details
.failed
> 0 ? details
.todo
: !details
.todo
;
4069 // Collapse the passing tests
4070 addClass(assertList
, "qunit-collapsed");
4071 } else if (config
.collapse
) {
4072 if (!collapseNext
) {
4074 // Skip collapsing the first failing test
4075 collapseNext
= true;
4078 // Collapse remaining tests
4079 addClass(assertList
, "qunit-collapsed");
4083 // The testItem.firstChild is the test name
4084 testTitle
= testItem
.firstChild
;
4086 testCounts
= bad
? "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " : "";
4088 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+ details
.assertions
.length
+ ")</b>";
4090 if (details
.skipped
) {
4091 stats
.skippedTests
++;
4093 testItem
.className
= "skipped";
4094 skipped
= document
$$1.createElement("em");
4095 skipped
.className
= "qunit-skipped-label";
4096 skipped
.innerHTML
= "skipped";
4097 testItem
.insertBefore(skipped
, testTitle
);
4099 addEvent(testTitle
, "click", function () {
4100 toggleClass(assertList
, "qunit-collapsed");
4103 testItem
.className
= testPassed
? "pass" : "fail";
4106 var todoLabel
= document
$$1.createElement("em");
4107 todoLabel
.className
= "qunit-todo-label";
4108 todoLabel
.innerHTML
= "todo";
4109 testItem
.className
+= " todo";
4110 testItem
.insertBefore(todoLabel
, testTitle
);
4113 time
= document
$$1.createElement("span");
4114 time
.className
= "runtime";
4115 time
.innerHTML
= details
.runtime
+ " ms";
4116 testItem
.insertBefore(time
, assertList
);
4119 stats
.failedTests
++;
4120 } else if (details
.todo
) {
4123 stats
.passedTests
++;
4127 // Show the source of the test when showing assertions
4128 if (details
.source
) {
4129 sourceName
= document
$$1.createElement("p");
4130 sourceName
.innerHTML
= "<strong>Source: </strong>" + details
.source
;
4131 addClass(sourceName
, "qunit-source");
4133 addClass(sourceName
, "qunit-collapsed");
4135 addEvent(testTitle
, "click", function () {
4136 toggleClass(sourceName
, "qunit-collapsed");
4138 testItem
.appendChild(sourceName
);
4142 // Avoid readyState issue with phantomjs
4144 var notPhantom = function (p
) {
4145 return !(p
&& p
.version
&& p
.version
.major
> 0);
4148 if (notPhantom
&& document
$$1.readyState
=== "complete") {
4151 addEvent(window
, "load", QUnit
.load
);
4154 // Wrap window.onerror. We will call the original window.onerror to see if
4155 // the existing handler fully handles the error; if not, we will call the
4156 // QUnit.onError function.
4157 var originalWindowOnError
= window
.onerror
;
4159 // Cover uncaught exceptions
4160 // Returning true will suppress the default browser handler,
4161 // returning false will let it run.
4162 window
.onerror = function (message
, fileName
, lineNumber
) {
4164 if (originalWindowOnError
) {
4165 for (var _len
= arguments
.length
, args
= Array(_len
> 3 ? _len
- 3 : 0), _key
= 3; _key
< _len
; _key
++) {
4166 args
[_key
- 3] = arguments
[_key
];
4169 ret
= originalWindowOnError
.call
.apply(originalWindowOnError
, [this, message
, fileName
, lineNumber
].concat(args
));
4172 // Treat return value as window.onerror itself does,
4173 // Only do our handling if not suppressed.
4178 lineNumber
: lineNumber
4181 ret
= QUnit
.onError(error
);
4187 // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
4188 window
.addEventListener("unhandledrejection", function (event
) {
4189 QUnit
.onUnhandledRejection(event
.reason
);
4194 * This file is a modified version of google-diff-match-patch's JavaScript implementation
4195 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
4196 * modifications are licensed as more fully set forth in LICENSE.txt.
4198 * The original source of google-diff-match-patch is attributable and licensed as follows:
4200 * Copyright 2006 Google Inc.
4201 * https://code.google.com/p/google-diff-match-patch/
4203 * Licensed under the Apache License, Version 2.0 (the "License");
4204 * you may not use this file except in compliance with the License.
4205 * You may obtain a copy of the License at
4207 * https://www.apache.org/licenses/LICENSE-2.0
4209 * Unless required by applicable law or agreed to in writing, software
4210 * distributed under the License is distributed on an "AS IS" BASIS,
4211 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4212 * See the License for the specific language governing permissions and
4213 * limitations under the License.
4216 * https://code.google.com/p/google-diff-match-patch/
4218 * Usage: QUnit.diff(expected, actual)
4221 QUnit
.diff = function () {
4222 function DiffMatchPatch() {}
4227 * The data structure representing a diff is an array of tuples:
4228 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
4229 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
4231 var DIFF_DELETE
= -1,
4236 * Find the differences between two texts. Simplifies the problem by stripping
4237 * any common prefix or suffix off the texts before diffing.
4238 * @param {string} text1 Old string to be diffed.
4239 * @param {string} text2 New string to be diffed.
4240 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
4241 * then don't run a line-level diff first to identify the changed areas.
4242 * Defaults to true, which does a faster, slightly less optimal diff.
4243 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4245 DiffMatchPatch
.prototype.DiffMain = function (text1
, text2
, optChecklines
) {
4246 var deadline
, checklines
, commonlength
, commonprefix
, commonsuffix
, diffs
;
4248 // The diff must be complete in up to 1 second.
4249 deadline
= new Date().getTime() + 1000;
4251 // Check for null inputs.
4252 if (text1
=== null || text2
=== null) {
4253 throw new Error("Null input. (DiffMain)");
4256 // Check for equality (speedup).
4257 if (text1
=== text2
) {
4259 return [[DIFF_EQUAL
, text1
]];
4264 if (typeof optChecklines
=== "undefined") {
4265 optChecklines
= true;
4268 checklines
= optChecklines
;
4270 // Trim off common prefix (speedup).
4271 commonlength
= this.diffCommonPrefix(text1
, text2
);
4272 commonprefix
= text1
.substring(0, commonlength
);
4273 text1
= text1
.substring(commonlength
);
4274 text2
= text2
.substring(commonlength
);
4276 // Trim off common suffix (speedup).
4277 commonlength
= this.diffCommonSuffix(text1
, text2
);
4278 commonsuffix
= text1
.substring(text1
.length
- commonlength
);
4279 text1
= text1
.substring(0, text1
.length
- commonlength
);
4280 text2
= text2
.substring(0, text2
.length
- commonlength
);
4282 // Compute the diff on the middle block.
4283 diffs
= this.diffCompute(text1
, text2
, checklines
, deadline
);
4285 // Restore the prefix and suffix.
4287 diffs
.unshift([DIFF_EQUAL
, commonprefix
]);
4290 diffs
.push([DIFF_EQUAL
, commonsuffix
]);
4292 this.diffCleanupMerge(diffs
);
4297 * Reduce the number of edits by eliminating operationally trivial equalities.
4298 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4300 DiffMatchPatch
.prototype.diffCleanupEfficiency = function (diffs
) {
4301 var changes
, equalities
, equalitiesLength
, lastequality
, pointer
, preIns
, preDel
, postIns
, postDel
;
4303 equalities
= []; // Stack of indices where equalities are found.
4304 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
4305 /** @type {?string} */
4306 lastequality
= null;
4308 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4309 pointer
= 0; // Index of current position.
4311 // Is there an insertion operation before the last equality.
4314 // Is there a deletion operation before the last equality.
4317 // Is there an insertion operation after the last equality.
4320 // Is there a deletion operation after the last equality.
4322 while (pointer
< diffs
.length
) {
4325 if (diffs
[pointer
][0] === DIFF_EQUAL
) {
4326 if (diffs
[pointer
][1].length
< 4 && (postIns
|| postDel
)) {
4329 equalities
[equalitiesLength
++] = pointer
;
4332 lastequality
= diffs
[pointer
][1];
4335 // Not a candidate, and can never become one.
4336 equalitiesLength
= 0;
4337 lastequality
= null;
4339 postIns
= postDel
= false;
4341 // An insertion or deletion.
4344 if (diffs
[pointer
][0] === DIFF_DELETE
) {
4351 * Five types to be split:
4352 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
4353 * <ins>A</ins>X<ins>C</ins><del>D</del>
4354 * <ins>A</ins><del>B</del>X<ins>C</ins>
4355 * <ins>A</del>X<ins>C</ins><del>D</del>
4356 * <ins>A</ins><del>B</del>X<del>C</del>
4358 if (lastequality
&& (preIns
&& preDel
&& postIns
&& postDel
|| lastequality
.length
< 2 && preIns
+ preDel
+ postIns
+ postDel
=== 3)) {
4360 // Duplicate record.
4361 diffs
.splice(equalities
[equalitiesLength
- 1], 0, [DIFF_DELETE
, lastequality
]);
4363 // Change second copy to insert.
4364 diffs
[equalities
[equalitiesLength
- 1] + 1][0] = DIFF_INSERT
;
4365 equalitiesLength
--; // Throw away the equality we just deleted;
4366 lastequality
= null;
4367 if (preIns
&& preDel
) {
4369 // No changes made which could affect previous entry, keep going.
4370 postIns
= postDel
= true;
4371 equalitiesLength
= 0;
4373 equalitiesLength
--; // Throw away the previous equality.
4374 pointer
= equalitiesLength
> 0 ? equalities
[equalitiesLength
- 1] : -1;
4375 postIns
= postDel
= false;
4384 this.diffCleanupMerge(diffs
);
4389 * Convert a diff array into a pretty HTML report.
4390 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4391 * @param {integer} string to be beautified.
4392 * @return {string} HTML representation.
4394 DiffMatchPatch
.prototype.diffPrettyHtml = function (diffs
) {
4399 for (x
= 0; x
< diffs
.length
; x
++) {
4400 op
= diffs
[x
][0]; // Operation (insert, delete, equal)
4401 data
= diffs
[x
][1]; // Text of change.
4404 html
[x
] = "<ins>" + escapeText(data
) + "</ins>";
4407 html
[x
] = "<del>" + escapeText(data
) + "</del>";
4410 html
[x
] = "<span>" + escapeText(data
) + "</span>";
4414 return html
.join("");
4418 * Determine the common prefix of two strings.
4419 * @param {string} text1 First string.
4420 * @param {string} text2 Second string.
4421 * @return {number} The number of characters common to the start of each
4424 DiffMatchPatch
.prototype.diffCommonPrefix = function (text1
, text2
) {
4425 var pointermid
, pointermax
, pointermin
, pointerstart
;
4427 // Quick check for common null cases.
4428 if (!text1
|| !text2
|| text1
.charAt(0) !== text2
.charAt(0)) {
4433 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4435 pointermax
= Math
.min(text1
.length
, text2
.length
);
4436 pointermid
= pointermax
;
4438 while (pointermin
< pointermid
) {
4439 if (text1
.substring(pointerstart
, pointermid
) === text2
.substring(pointerstart
, pointermid
)) {
4440 pointermin
= pointermid
;
4441 pointerstart
= pointermin
;
4443 pointermax
= pointermid
;
4445 pointermid
= Math
.floor((pointermax
- pointermin
) / 2 + pointermin
);
4451 * Determine the common suffix of two strings.
4452 * @param {string} text1 First string.
4453 * @param {string} text2 Second string.
4454 * @return {number} The number of characters common to the end of each string.
4456 DiffMatchPatch
.prototype.diffCommonSuffix = function (text1
, text2
) {
4457 var pointermid
, pointermax
, pointermin
, pointerend
;
4459 // Quick check for common null cases.
4460 if (!text1
|| !text2
|| text1
.charAt(text1
.length
- 1) !== text2
.charAt(text2
.length
- 1)) {
4465 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4467 pointermax
= Math
.min(text1
.length
, text2
.length
);
4468 pointermid
= pointermax
;
4470 while (pointermin
< pointermid
) {
4471 if (text1
.substring(text1
.length
- pointermid
, text1
.length
- pointerend
) === text2
.substring(text2
.length
- pointermid
, text2
.length
- pointerend
)) {
4472 pointermin
= pointermid
;
4473 pointerend
= pointermin
;
4475 pointermax
= pointermid
;
4477 pointermid
= Math
.floor((pointermax
- pointermin
) / 2 + pointermin
);
4483 * Find the differences between two texts. Assumes that the texts do not
4484 * have any common prefix or suffix.
4485 * @param {string} text1 Old string to be diffed.
4486 * @param {string} text2 New string to be diffed.
4487 * @param {boolean} checklines Speedup flag. If false, then don't run a
4488 * line-level diff first to identify the changed areas.
4489 * If true, then run a faster, slightly less optimal diff.
4490 * @param {number} deadline Time when the diff should be complete by.
4491 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4494 DiffMatchPatch
.prototype.diffCompute = function (text1
, text2
, checklines
, deadline
) {
4495 var diffs
, longtext
, shorttext
, i
, hm
, text1A
, text2A
, text1B
, text2B
, midCommon
, diffsA
, diffsB
;
4499 // Just add some text (speedup).
4500 return [[DIFF_INSERT
, text2
]];
4505 // Just delete some text (speedup).
4506 return [[DIFF_DELETE
, text1
]];
4509 longtext
= text1
.length
> text2
.length
? text1
: text2
;
4510 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
4511 i
= longtext
.indexOf(shorttext
);
4514 // Shorter text is inside the longer text (speedup).
4515 diffs
= [[DIFF_INSERT
, longtext
.substring(0, i
)], [DIFF_EQUAL
, shorttext
], [DIFF_INSERT
, longtext
.substring(i
+ shorttext
.length
)]];
4517 // Swap insertions for deletions if diff is reversed.
4518 if (text1
.length
> text2
.length
) {
4519 diffs
[0][0] = diffs
[2][0] = DIFF_DELETE
;
4524 if (shorttext
.length
=== 1) {
4526 // Single character string.
4527 // After the previous speedup, the character can't be an equality.
4528 return [[DIFF_DELETE
, text1
], [DIFF_INSERT
, text2
]];
4531 // Check to see if the problem can be split in two.
4532 hm
= this.diffHalfMatch(text1
, text2
);
4535 // A half-match was found, sort out the return data.
4542 // Send both pairs off for separate processing.
4543 diffsA
= this.DiffMain(text1A
, text2A
, checklines
, deadline
);
4544 diffsB
= this.DiffMain(text1B
, text2B
, checklines
, deadline
);
4546 // Merge the results.
4547 return diffsA
.concat([[DIFF_EQUAL
, midCommon
]], diffsB
);
4550 if (checklines
&& text1
.length
> 100 && text2
.length
> 100) {
4551 return this.diffLineMode(text1
, text2
, deadline
);
4554 return this.diffBisect(text1
, text2
, deadline
);
4558 * Do the two texts share a substring which is at least half the length of the
4560 * This speedup can produce non-minimal diffs.
4561 * @param {string} text1 First string.
4562 * @param {string} text2 Second string.
4563 * @return {Array.<string>} Five element Array, containing the prefix of
4564 * text1, the suffix of text1, the prefix of text2, the suffix of
4565 * text2 and the common middle. Or null if there was no match.
4568 DiffMatchPatch
.prototype.diffHalfMatch = function (text1
, text2
) {
4569 var longtext
, shorttext
, dmp
, text1A
, text2B
, text2A
, text1B
, midCommon
, hm1
, hm2
, hm
;
4571 longtext
= text1
.length
> text2
.length
? text1
: text2
;
4572 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
4573 if (longtext
.length
< 4 || shorttext
.length
* 2 < longtext
.length
) {
4574 return null; // Pointless.
4576 dmp
= this; // 'this' becomes 'window' in a closure.
4579 * Does a substring of shorttext exist within longtext such that the substring
4580 * is at least half the length of longtext?
4581 * Closure, but does not reference any external variables.
4582 * @param {string} longtext Longer string.
4583 * @param {string} shorttext Shorter string.
4584 * @param {number} i Start index of quarter length substring within longtext.
4585 * @return {Array.<string>} Five element Array, containing the prefix of
4586 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
4587 * of shorttext and the common middle. Or null if there was no match.
4590 function diffHalfMatchI(longtext
, shorttext
, i
) {
4591 var seed
, j
, bestCommon
, prefixLength
, suffixLength
, bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
;
4593 // Start with a 1/4 length substring at position i as a seed.
4594 seed
= longtext
.substring(i
, i
+ Math
.floor(longtext
.length
/ 4));
4597 while ((j
= shorttext
.indexOf(seed
, j
+ 1)) !== -1) {
4598 prefixLength
= dmp
.diffCommonPrefix(longtext
.substring(i
), shorttext
.substring(j
));
4599 suffixLength
= dmp
.diffCommonSuffix(longtext
.substring(0, i
), shorttext
.substring(0, j
));
4600 if (bestCommon
.length
< suffixLength
+ prefixLength
) {
4601 bestCommon
= shorttext
.substring(j
- suffixLength
, j
) + shorttext
.substring(j
, j
+ prefixLength
);
4602 bestLongtextA
= longtext
.substring(0, i
- suffixLength
);
4603 bestLongtextB
= longtext
.substring(i
+ prefixLength
);
4604 bestShorttextA
= shorttext
.substring(0, j
- suffixLength
);
4605 bestShorttextB
= shorttext
.substring(j
+ prefixLength
);
4608 if (bestCommon
.length
* 2 >= longtext
.length
) {
4609 return [bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
, bestCommon
];
4615 // First check if the second quarter is the seed for a half-match.
4616 hm1
= diffHalfMatchI(longtext
, shorttext
, Math
.ceil(longtext
.length
/ 4));
4618 // Check again based on the third quarter.
4619 hm2
= diffHalfMatchI(longtext
, shorttext
, Math
.ceil(longtext
.length
/ 2));
4628 // Both matched. Select the longest.
4629 hm
= hm1
[4].length
> hm2
[4].length
? hm1
: hm2
;
4632 // A half-match was found, sort out the return data.
4633 if (text1
.length
> text2
.length
) {
4645 return [text1A
, text1B
, text2A
, text2B
, midCommon
];
4649 * Do a quick line-level diff on both strings, then rediff the parts for
4651 * This speedup can produce non-minimal diffs.
4652 * @param {string} text1 Old string to be diffed.
4653 * @param {string} text2 New string to be diffed.
4654 * @param {number} deadline Time when the diff should be complete by.
4655 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4658 DiffMatchPatch
.prototype.diffLineMode = function (text1
, text2
, deadline
) {
4659 var a
, diffs
, linearray
, pointer
, countInsert
, countDelete
, textInsert
, textDelete
, j
;
4661 // Scan the text on a line-by-line basis first.
4662 a
= this.diffLinesToChars(text1
, text2
);
4665 linearray
= a
.lineArray
;
4667 diffs
= this.DiffMain(text1
, text2
, false, deadline
);
4669 // Convert the diff back to original text.
4670 this.diffCharsToLines(diffs
, linearray
);
4672 // Eliminate freak matches (e.g. blank lines)
4673 this.diffCleanupSemantic(diffs
);
4675 // Rediff any replacement blocks, this time character-by-character.
4676 // Add a dummy entry at the end.
4677 diffs
.push([DIFF_EQUAL
, ""]);
4683 while (pointer
< diffs
.length
) {
4684 switch (diffs
[pointer
][0]) {
4687 textInsert
+= diffs
[pointer
][1];
4691 textDelete
+= diffs
[pointer
][1];
4695 // Upon reaching an equality, check for prior redundancies.
4696 if (countDelete
>= 1 && countInsert
>= 1) {
4698 // Delete the offending records and add the merged ones.
4699 diffs
.splice(pointer
- countDelete
- countInsert
, countDelete
+ countInsert
);
4700 pointer
= pointer
- countDelete
- countInsert
;
4701 a
= this.DiffMain(textDelete
, textInsert
, false, deadline
);
4702 for (j
= a
.length
- 1; j
>= 0; j
--) {
4703 diffs
.splice(pointer
, 0, a
[j
]);
4705 pointer
= pointer
+ a
.length
;
4715 diffs
.pop(); // Remove the dummy entry at the end.
4721 * Find the 'middle snake' of a diff, split the problem in two
4722 * and return the recursively constructed diff.
4723 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
4724 * @param {string} text1 Old string to be diffed.
4725 * @param {string} text2 New string to be diffed.
4726 * @param {number} deadline Time at which to bail if not yet complete.
4727 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4730 DiffMatchPatch
.prototype.diffBisect = function (text1
, text2
, deadline
) {
4731 var text1Length
, text2Length
, maxD
, vOffset
, vLength
, v1
, v2
, x
, delta
, front
, k1start
, k1end
, k2start
, k2end
, k2Offset
, k1Offset
, x1
, x2
, y1
, y2
, d
, k1
, k2
;
4733 // Cache the text lengths to prevent multiple calls.
4734 text1Length
= text1
.length
;
4735 text2Length
= text2
.length
;
4736 maxD
= Math
.ceil((text1Length
+ text2Length
) / 2);
4739 v1
= new Array(vLength
);
4740 v2
= new Array(vLength
);
4742 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
4743 // integers and undefined.
4744 for (x
= 0; x
< vLength
; x
++) {
4748 v1
[vOffset
+ 1] = 0;
4749 v2
[vOffset
+ 1] = 0;
4750 delta
= text1Length
- text2Length
;
4752 // If the total number of characters is odd, then the front path will collide
4753 // with the reverse path.
4754 front
= delta
% 2 !== 0;
4756 // Offsets for start and end of k loop.
4757 // Prevents mapping of space beyond the grid.
4762 for (d
= 0; d
< maxD
; d
++) {
4764 // Bail out if deadline is reached.
4765 if (new Date().getTime() > deadline
) {
4769 // Walk the front path one step.
4770 for (k1
= -d
+ k1start
; k1
<= d
- k1end
; k1
+= 2) {
4771 k1Offset
= vOffset
+ k1
;
4772 if (k1
=== -d
|| k1
!== d
&& v1
[k1Offset
- 1] < v1
[k1Offset
+ 1]) {
4773 x1
= v1
[k1Offset
+ 1];
4775 x1
= v1
[k1Offset
- 1] + 1;
4778 while (x1
< text1Length
&& y1
< text2Length
&& text1
.charAt(x1
) === text2
.charAt(y1
)) {
4783 if (x1
> text1Length
) {
4785 // Ran off the right of the graph.
4787 } else if (y1
> text2Length
) {
4789 // Ran off the bottom of the graph.
4792 k2Offset
= vOffset
+ delta
- k1
;
4793 if (k2Offset
>= 0 && k2Offset
< vLength
&& v2
[k2Offset
] !== -1) {
4795 // Mirror x2 onto top-left coordinate system.
4796 x2
= text1Length
- v2
[k2Offset
];
4799 // Overlap detected.
4800 return this.diffBisectSplit(text1
, text2
, x1
, y1
, deadline
);
4806 // Walk the reverse path one step.
4807 for (k2
= -d
+ k2start
; k2
<= d
- k2end
; k2
+= 2) {
4808 k2Offset
= vOffset
+ k2
;
4809 if (k2
=== -d
|| k2
!== d
&& v2
[k2Offset
- 1] < v2
[k2Offset
+ 1]) {
4810 x2
= v2
[k2Offset
+ 1];
4812 x2
= v2
[k2Offset
- 1] + 1;
4815 while (x2
< text1Length
&& y2
< text2Length
&& text1
.charAt(text1Length
- x2
- 1) === text2
.charAt(text2Length
- y2
- 1)) {
4820 if (x2
> text1Length
) {
4822 // Ran off the left of the graph.
4824 } else if (y2
> text2Length
) {
4826 // Ran off the top of the graph.
4828 } else if (!front
) {
4829 k1Offset
= vOffset
+ delta
- k2
;
4830 if (k1Offset
>= 0 && k1Offset
< vLength
&& v1
[k1Offset
] !== -1) {
4832 y1
= vOffset
+ x1
- k1Offset
;
4834 // Mirror x2 onto top-left coordinate system.
4835 x2
= text1Length
- x2
;
4838 // Overlap detected.
4839 return this.diffBisectSplit(text1
, text2
, x1
, y1
, deadline
);
4846 // Diff took too long and hit the deadline or
4847 // number of diffs equals number of characters, no commonality at all.
4848 return [[DIFF_DELETE
, text1
], [DIFF_INSERT
, text2
]];
4852 * Given the location of the 'middle snake', split the diff in two parts
4854 * @param {string} text1 Old string to be diffed.
4855 * @param {string} text2 New string to be diffed.
4856 * @param {number} x Index of split point in text1.
4857 * @param {number} y Index of split point in text2.
4858 * @param {number} deadline Time at which to bail if not yet complete.
4859 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4862 DiffMatchPatch
.prototype.diffBisectSplit = function (text1
, text2
, x
, y
, deadline
) {
4863 var text1a
, text1b
, text2a
, text2b
, diffs
, diffsb
;
4864 text1a
= text1
.substring(0, x
);
4865 text2a
= text2
.substring(0, y
);
4866 text1b
= text1
.substring(x
);
4867 text2b
= text2
.substring(y
);
4869 // Compute both diffs serially.
4870 diffs
= this.DiffMain(text1a
, text2a
, false, deadline
);
4871 diffsb
= this.DiffMain(text1b
, text2b
, false, deadline
);
4873 return diffs
.concat(diffsb
);
4877 * Reduce the number of edits by eliminating semantically trivial equalities.
4878 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4880 DiffMatchPatch
.prototype.diffCleanupSemantic = function (diffs
) {
4881 var changes
, equalities
, equalitiesLength
, lastequality
, pointer
, lengthInsertions2
, lengthDeletions2
, lengthInsertions1
, lengthDeletions1
, deletion
, insertion
, overlapLength1
, overlapLength2
;
4883 equalities
= []; // Stack of indices where equalities are found.
4884 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
4885 /** @type {?string} */
4886 lastequality
= null;
4888 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4889 pointer
= 0; // Index of current position.
4891 // Number of characters that changed prior to the equality.
4892 lengthInsertions1
= 0;
4893 lengthDeletions1
= 0;
4895 // Number of characters that changed after the equality.
4896 lengthInsertions2
= 0;
4897 lengthDeletions2
= 0;
4898 while (pointer
< diffs
.length
) {
4899 if (diffs
[pointer
][0] === DIFF_EQUAL
) {
4901 equalities
[equalitiesLength
++] = pointer
;
4902 lengthInsertions1
= lengthInsertions2
;
4903 lengthDeletions1
= lengthDeletions2
;
4904 lengthInsertions2
= 0;
4905 lengthDeletions2
= 0;
4906 lastequality
= diffs
[pointer
][1];
4908 // An insertion or deletion.
4909 if (diffs
[pointer
][0] === DIFF_INSERT
) {
4910 lengthInsertions2
+= diffs
[pointer
][1].length
;
4912 lengthDeletions2
+= diffs
[pointer
][1].length
;
4915 // Eliminate an equality that is smaller or equal to the edits on both
4917 if (lastequality
&& lastequality
.length
<= Math
.max(lengthInsertions1
, lengthDeletions1
) && lastequality
.length
<= Math
.max(lengthInsertions2
, lengthDeletions2
)) {
4919 // Duplicate record.
4920 diffs
.splice(equalities
[equalitiesLength
- 1], 0, [DIFF_DELETE
, lastequality
]);
4922 // Change second copy to insert.
4923 diffs
[equalities
[equalitiesLength
- 1] + 1][0] = DIFF_INSERT
;
4925 // Throw away the equality we just deleted.
4928 // Throw away the previous equality (it needs to be reevaluated).
4930 pointer
= equalitiesLength
> 0 ? equalities
[equalitiesLength
- 1] : -1;
4932 // Reset the counters.
4933 lengthInsertions1
= 0;
4934 lengthDeletions1
= 0;
4935 lengthInsertions2
= 0;
4936 lengthDeletions2
= 0;
4937 lastequality
= null;
4944 // Normalize the diff.
4946 this.diffCleanupMerge(diffs
);
4949 // Find any overlaps between deletions and insertions.
4950 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
4951 // -> <del>abc</del>xxx<ins>def</ins>
4952 // e.g: <del>xxxabc</del><ins>defxxx</ins>
4953 // -> <ins>def</ins>xxx<del>abc</del>
4954 // Only extract an overlap if it is as big as the edit ahead or behind it.
4956 while (pointer
< diffs
.length
) {
4957 if (diffs
[pointer
- 1][0] === DIFF_DELETE
&& diffs
[pointer
][0] === DIFF_INSERT
) {
4958 deletion
= diffs
[pointer
- 1][1];
4959 insertion
= diffs
[pointer
][1];
4960 overlapLength1
= this.diffCommonOverlap(deletion
, insertion
);
4961 overlapLength2
= this.diffCommonOverlap(insertion
, deletion
);
4962 if (overlapLength1
>= overlapLength2
) {
4963 if (overlapLength1
>= deletion
.length
/ 2 || overlapLength1
>= insertion
.length
/ 2) {
4965 // Overlap found. Insert an equality and trim the surrounding edits.
4966 diffs
.splice(pointer
, 0, [DIFF_EQUAL
, insertion
.substring(0, overlapLength1
)]);
4967 diffs
[pointer
- 1][1] = deletion
.substring(0, deletion
.length
- overlapLength1
);
4968 diffs
[pointer
+ 1][1] = insertion
.substring(overlapLength1
);
4972 if (overlapLength2
>= deletion
.length
/ 2 || overlapLength2
>= insertion
.length
/ 2) {
4974 // Reverse overlap found.
4975 // Insert an equality and swap and trim the surrounding edits.
4976 diffs
.splice(pointer
, 0, [DIFF_EQUAL
, deletion
.substring(0, overlapLength2
)]);
4978 diffs
[pointer
- 1][0] = DIFF_INSERT
;
4979 diffs
[pointer
- 1][1] = insertion
.substring(0, insertion
.length
- overlapLength2
);
4980 diffs
[pointer
+ 1][0] = DIFF_DELETE
;
4981 diffs
[pointer
+ 1][1] = deletion
.substring(overlapLength2
);
4992 * Determine if the suffix of one string is the prefix of another.
4993 * @param {string} text1 First string.
4994 * @param {string} text2 Second string.
4995 * @return {number} The number of characters common to the end of the first
4996 * string and the start of the second string.
4999 DiffMatchPatch
.prototype.diffCommonOverlap = function (text1
, text2
) {
5000 var text1Length
, text2Length
, textLength
, best
, length
, pattern
, found
;
5002 // Cache the text lengths to prevent multiple calls.
5003 text1Length
= text1
.length
;
5004 text2Length
= text2
.length
;
5006 // Eliminate the null case.
5007 if (text1Length
=== 0 || text2Length
=== 0) {
5011 // Truncate the longer string.
5012 if (text1Length
> text2Length
) {
5013 text1
= text1
.substring(text1Length
- text2Length
);
5014 } else if (text1Length
< text2Length
) {
5015 text2
= text2
.substring(0, text1Length
);
5017 textLength
= Math
.min(text1Length
, text2Length
);
5019 // Quick check for the worst case.
5020 if (text1
=== text2
) {
5024 // Start by looking for a single character match
5025 // and increase length until no match is found.
5026 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
5030 pattern
= text1
.substring(textLength
- length
);
5031 found
= text2
.indexOf(pattern
);
5036 if (found
=== 0 || text1
.substring(textLength
- length
) === text2
.substring(0, length
)) {
5044 * Split two texts into an array of strings. Reduce the texts to a string of
5045 * hashes where each Unicode character represents one line.
5046 * @param {string} text1 First string.
5047 * @param {string} text2 Second string.
5048 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
5049 * An object containing the encoded text1, the encoded text2 and
5050 * the array of unique strings.
5051 * The zeroth element of the array of unique strings is intentionally blank.
5054 DiffMatchPatch
.prototype.diffLinesToChars = function (text1
, text2
) {
5055 var lineArray
, lineHash
, chars1
, chars2
;
5056 lineArray
= []; // E.g. lineArray[4] === 'Hello\n'
5057 lineHash
= {}; // E.g. lineHash['Hello\n'] === 4
5059 // '\x00' is a valid character, but various debuggers don't like it.
5060 // So we'll insert a junk entry to avoid generating a null character.
5064 * Split a text into an array of strings. Reduce the texts to a string of
5065 * hashes where each Unicode character represents one line.
5066 * Modifies linearray and linehash through being a closure.
5067 * @param {string} text String to encode.
5068 * @return {string} Encoded string.
5071 function diffLinesToCharsMunge(text
) {
5072 var chars
, lineStart
, lineEnd
, lineArrayLength
, line
;
5075 // Walk the text, pulling out a substring for each line.
5076 // text.split('\n') would would temporarily double our memory footprint.
5077 // Modifying text would create many large strings to garbage collect.
5081 // Keeping our own length variable is faster than looking it up.
5082 lineArrayLength
= lineArray
.length
;
5083 while (lineEnd
< text
.length
- 1) {
5084 lineEnd
= text
.indexOf("\n", lineStart
);
5085 if (lineEnd
=== -1) {
5086 lineEnd
= text
.length
- 1;
5088 line
= text
.substring(lineStart
, lineEnd
+ 1);
5089 lineStart
= lineEnd
+ 1;
5091 var lineHashExists
= lineHash
.hasOwnProperty
? lineHash
.hasOwnProperty(line
) : lineHash
[line
] !== undefined;
5093 if (lineHashExists
) {
5094 chars
+= String
.fromCharCode(lineHash
[line
]);
5096 chars
+= String
.fromCharCode(lineArrayLength
);
5097 lineHash
[line
] = lineArrayLength
;
5098 lineArray
[lineArrayLength
++] = line
;
5104 chars1
= diffLinesToCharsMunge(text1
);
5105 chars2
= diffLinesToCharsMunge(text2
);
5109 lineArray
: lineArray
5114 * Rehydrate the text in a diff from a string of line hashes to real lines of
5116 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5117 * @param {!Array.<string>} lineArray Array of unique strings.
5120 DiffMatchPatch
.prototype.diffCharsToLines = function (diffs
, lineArray
) {
5121 var x
, chars
, text
, y
;
5122 for (x
= 0; x
< diffs
.length
; x
++) {
5123 chars
= diffs
[x
][1];
5125 for (y
= 0; y
< chars
.length
; y
++) {
5126 text
[y
] = lineArray
[chars
.charCodeAt(y
)];
5128 diffs
[x
][1] = text
.join("");
5133 * Reorder and merge like edit sections. Merge equalities.
5134 * Any edit section can move as long as it doesn't cross an equality.
5135 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5137 DiffMatchPatch
.prototype.diffCleanupMerge = function (diffs
) {
5138 var pointer
, countDelete
, countInsert
, textInsert
, textDelete
, commonlength
, changes
, diffPointer
, position
;
5139 diffs
.push([DIFF_EQUAL
, ""]); // Add a dummy entry at the end.
5146 while (pointer
< diffs
.length
) {
5147 switch (diffs
[pointer
][0]) {
5150 textInsert
+= diffs
[pointer
][1];
5155 textDelete
+= diffs
[pointer
][1];
5160 // Upon reaching an equality, check for prior redundancies.
5161 if (countDelete
+ countInsert
> 1) {
5162 if (countDelete
!== 0 && countInsert
!== 0) {
5164 // Factor out any common prefixes.
5165 commonlength
= this.diffCommonPrefix(textInsert
, textDelete
);
5166 if (commonlength
!== 0) {
5167 if (pointer
- countDelete
- countInsert
> 0 && diffs
[pointer
- countDelete
- countInsert
- 1][0] === DIFF_EQUAL
) {
5168 diffs
[pointer
- countDelete
- countInsert
- 1][1] += textInsert
.substring(0, commonlength
);
5170 diffs
.splice(0, 0, [DIFF_EQUAL
, textInsert
.substring(0, commonlength
)]);
5173 textInsert
= textInsert
.substring(commonlength
);
5174 textDelete
= textDelete
.substring(commonlength
);
5177 // Factor out any common suffixies.
5178 commonlength
= this.diffCommonSuffix(textInsert
, textDelete
);
5179 if (commonlength
!== 0) {
5180 diffs
[pointer
][1] = textInsert
.substring(textInsert
.length
- commonlength
) + diffs
[pointer
][1];
5181 textInsert
= textInsert
.substring(0, textInsert
.length
- commonlength
);
5182 textDelete
= textDelete
.substring(0, textDelete
.length
- commonlength
);
5186 // Delete the offending records and add the merged ones.
5187 if (countDelete
=== 0) {
5188 diffs
.splice(pointer
- countInsert
, countDelete
+ countInsert
, [DIFF_INSERT
, textInsert
]);
5189 } else if (countInsert
=== 0) {
5190 diffs
.splice(pointer
- countDelete
, countDelete
+ countInsert
, [DIFF_DELETE
, textDelete
]);
5192 diffs
.splice(pointer
- countDelete
- countInsert
, countDelete
+ countInsert
, [DIFF_DELETE
, textDelete
], [DIFF_INSERT
, textInsert
]);
5194 pointer
= pointer
- countDelete
- countInsert
+ (countDelete
? 1 : 0) + (countInsert
? 1 : 0) + 1;
5195 } else if (pointer
!== 0 && diffs
[pointer
- 1][0] === DIFF_EQUAL
) {
5197 // Merge this equality with the previous one.
5198 diffs
[pointer
- 1][1] += diffs
[pointer
][1];
5199 diffs
.splice(pointer
, 1);
5210 if (diffs
[diffs
.length
- 1][1] === "") {
5211 diffs
.pop(); // Remove the dummy entry at the end.
5214 // Second pass: look for single edits surrounded on both sides by equalities
5215 // which can be shifted sideways to eliminate an equality.
5216 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
5220 // Intentionally ignore the first and last element (don't need checking).
5221 while (pointer
< diffs
.length
- 1) {
5222 if (diffs
[pointer
- 1][0] === DIFF_EQUAL
&& diffs
[pointer
+ 1][0] === DIFF_EQUAL
) {
5224 diffPointer
= diffs
[pointer
][1];
5225 position
= diffPointer
.substring(diffPointer
.length
- diffs
[pointer
- 1][1].length
);
5227 // This is a single edit surrounded by equalities.
5228 if (position
=== diffs
[pointer
- 1][1]) {
5230 // Shift the edit over the previous equality.
5231 diffs
[pointer
][1] = diffs
[pointer
- 1][1] + diffs
[pointer
][1].substring(0, diffs
[pointer
][1].length
- diffs
[pointer
- 1][1].length
);
5232 diffs
[pointer
+ 1][1] = diffs
[pointer
- 1][1] + diffs
[pointer
+ 1][1];
5233 diffs
.splice(pointer
- 1, 1);
5235 } else if (diffPointer
.substring(0, diffs
[pointer
+ 1][1].length
) === diffs
[pointer
+ 1][1]) {
5237 // Shift the edit over the next equality.
5238 diffs
[pointer
- 1][1] += diffs
[pointer
+ 1][1];
5239 diffs
[pointer
][1] = diffs
[pointer
][1].substring(diffs
[pointer
+ 1][1].length
) + diffs
[pointer
+ 1][1];
5240 diffs
.splice(pointer
+ 1, 1);
5247 // If shifts were made, the diff needs reordering and another shift sweep.
5249 this.diffCleanupMerge(diffs
);
5253 return function (o
, n
) {
5254 var diff
, output
, text
;
5255 diff
= new DiffMatchPatch();
5256 output
= diff
.DiffMain(o
, n
);
5257 diff
.diffCleanupEfficiency(output
);
5258 text
= diff
.diffPrettyHtml(output
);
5264 }((function() { return this; }())));