5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
9 * Date: 2018-08-19T19:37Z
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 SuiteReport = function () {
954 function SuiteReport(name, parentSuite) {
955 classCallCheck(this, SuiteReport);
958 this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
961 this.childSuites = [];
964 parentSuite.pushChildSuite(this);
968 createClass(SuiteReport, [{
970 value: function start(recordTime) {
972 this._startTime = Date.now();
977 fullName: this.fullName.slice(),
978 tests: this.tests.map(function (test) {
981 childSuites: this.childSuites.map(function (suite) {
982 return suite.start();
985 total: this.getTestCounts().total
991 value: function end(recordTime) {
993 this._endTime = Date.now();
998 fullName: this.fullName.slice(),
999 tests: this.tests.map(function (test) {
1002 childSuites: this.childSuites.map(function (suite) {
1005 testCounts: this.getTestCounts(),
1006 runtime: this.getRuntime(),
1007 status: this.getStatus()
1011 key: "pushChildSuite
",
1012 value: function pushChildSuite(suite) {
1013 this.childSuites.push(suite);
1017 value: function pushTest(test) {
1018 this.tests.push(test);
1022 value: function getRuntime() {
1023 return this._endTime - this._startTime;
1026 key: "getTestCounts
",
1027 value: function getTestCounts() {
1028 var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
1030 counts = this.tests.reduce(function (counts, test) {
1032 counts[test.getStatus()]++;
1039 return this.childSuites.reduce(function (counts, suite) {
1040 return suite.getTestCounts(counts);
1045 value: function getStatus() {
1046 var _getTestCounts = this.getTestCounts(),
1047 total = _getTestCounts.total,
1048 failed = _getTestCounts.failed,
1049 skipped = _getTestCounts.skipped,
1050 todo = _getTestCounts.todo;
1055 if (skipped === total) {
1057 } else if (todo === total) {
1068 var focused = false;
1070 var moduleStack = [];
1072 function createModule(name, testEnvironment, modifiers) {
1073 var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
1074 var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
1075 var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
1077 var skip = parentModule !== null && parentModule.skip || modifiers.skip;
1078 var todo = parentModule !== null && parentModule.todo || modifiers.todo;
1082 parentModule: parentModule,
1084 moduleId: generateHash(moduleName),
1086 unskippedTestsRun: 0,
1088 suiteReport: new SuiteReport(name, parentSuite),
1090 // Pass along `skip` and `todo` properties from parent module, in case
1091 // there is one, to childs. And use own otherwise.
1092 // This property will be used to mark own tests and tests of child suites
1093 // as either `skipped` or `todo`.
1095 todo: skip ? false : todo
1100 parentModule.childModules.push(module);
1101 extend(env, parentModule.testEnvironment);
1103 extend(env, testEnvironment);
1104 module.testEnvironment = env;
1106 config.modules.push(module);
1110 function processModule(name, options, executeNow) {
1111 var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
1113 if (objectType(options) === "function") {
1114 executeNow = options;
1115 options = undefined;
1118 var module = createModule(name, options, modifiers);
1120 // Move any hooks to a 'hooks' object
1121 var testEnvironment = module.testEnvironment;
1122 var hooks = module.hooks = {};
1124 setHookFromEnvironment(hooks, testEnvironment, "before
");
1125 setHookFromEnvironment(hooks, testEnvironment, "beforeEach
");
1126 setHookFromEnvironment(hooks, testEnvironment, "afterEach
");
1127 setHookFromEnvironment(hooks, testEnvironment, "after
");
1130 before: setHookFunction(module, "before
"),
1131 beforeEach: setHookFunction(module, "beforeEach
"),
1132 afterEach: setHookFunction(module, "afterEach
"),
1133 after: setHookFunction(module, "after
")
1136 var currentModule = config.currentModule;
1137 if (objectType(executeNow) === "function") {
1138 moduleStack.push(module);
1139 config.currentModule = module;
1140 executeNow.call(module.testEnvironment, moduleFns);
1142 module = module.parentModule || currentModule;
1145 config.currentModule = module;
1147 function setHookFromEnvironment(hooks, environment, name) {
1148 var potentialHook = environment[name];
1149 hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
1150 delete environment[name];
1153 function setHookFunction(module, hookName) {
1154 return function setHook(callback) {
1155 module.hooks[hookName].push(callback);
1160 function module$1(name, options, executeNow) {
1165 processModule(name, options, executeNow);
1168 module$1.only = function () {
1173 config.modules.length = 0;
1174 config.queue.length = 0;
1176 module$1.apply(undefined, arguments);
1181 module$1.skip = function (name, options, executeNow) {
1186 processModule(name, options, executeNow, { skip: true });
1189 module$1.todo = function (name, options, executeNow) {
1194 processModule(name, options, executeNow, { todo: true });
1197 var LISTENERS = Object.create(null);
1198 var SUPPORTED_EVENTS = ["runStart
", "suiteStart
", "testStart
", "assertion
", "testEnd
", "suiteEnd
", "runEnd
"];
1201 * Emits an event with the specified data to all currently registered listeners.
1202 * Callbacks will fire in the order in which they are registered (FIFO). This
1203 * function is not exposed publicly; it is used by QUnit internals to emit
1208 * @param {String} eventName
1209 * @param {Object} data
1212 function emit(eventName, data) {
1213 if (objectType(eventName) !== "string
") {
1214 throw new TypeError("eventName must be a string when emitting an event
");
1217 // Clone the callbacks in case one of them registers a new callback
1218 var originalCallbacks = LISTENERS[eventName];
1219 var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
1221 for (var i = 0; i < callbacks.length; i++) {
1227 * Registers a callback as a listener to the specified event.
1231 * @param {String} eventName
1232 * @param {Function} callback
1235 function on(eventName, callback) {
1236 if (objectType(eventName) !== "string
") {
1237 throw new TypeError("eventName must be a string when registering a listener
");
1238 } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
1239 var events = SUPPORTED_EVENTS.join(", ");
1240 throw new Error("\"" + eventName + "\" is not a valid event
; must be one
of: " + events + ".");
1241 } else if (objectType(callback) !== "function") {
1242 throw new TypeError("callback must be a
function when registering a listener
");
1245 if (!LISTENERS[eventName]) {
1246 LISTENERS[eventName] = [];
1249 // Don't register the same callback more than once
1250 if (!inArray(callback, LISTENERS[eventName])) {
1251 LISTENERS[eventName].push(callback);
1255 // Register logging callbacks
1256 function registerLoggingCallbacks(obj) {
1260 callbackNames = ["begin
", "done
", "log
", "testStart
", "testDone
", "moduleStart
", "moduleDone
"];
1262 function registerLoggingCallback(key) {
1263 var loggingCallback = function loggingCallback(callback) {
1264 if (objectType(callback) !== "function") {
1265 throw new Error("QUnit logging methods require a callback
function as their first parameters
.");
1268 config.callbacks[key].push(callback);
1271 return loggingCallback;
1274 for (i = 0, l = callbackNames.length; i < l; i++) {
1275 key = callbackNames[i];
1277 // Initialize key collection of logging callback
1278 if (objectType(config.callbacks[key]) === "undefined") {
1279 config.callbacks[key] = [];
1282 obj[key] = registerLoggingCallback(key);
1286 function runLoggingCallbacks(key, args) {
1287 var i, l, callbacks;
1289 callbacks = config.callbacks[key];
1290 for (i = 0, l = callbacks.length; i < l; i++) {
1295 // Doesn't support IE9, it will return undefined on these browsers
1296 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1297 var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
1299 function extractStacktrace(e, offset) {
1300 offset = offset === undefined ? 4 : offset;
1302 var stack, include, i;
1305 stack = e.stack.split("\n");
1306 if (/^error$/i.test(stack[0])) {
1311 for (i = offset; i < stack.length; i++) {
1312 if (stack[i].indexOf(fileName) !== -1) {
1315 include.push(stack[i]);
1317 if (include.length) {
1318 return include.join("\n");
1321 return stack[offset];
1325 function sourceFromStacktrace(offset) {
1326 var error = new Error();
1328 // Support: Safari <=7 only, IE <=10 - 11 only
1329 // Not all browsers generate the `stack` property for `new Error()`, see also #636
1338 return extractStacktrace(error, offset);
1341 var priorityCount = 0;
1342 var unitSampler = void 0;
1344 // This is a queue of functions that are tasks within a single test.
1345 // After tests are dequeued from config.queue they are expanded into
1346 // a set of tasks in this queue.
1350 * Advances the taskQueue to the next task. If the taskQueue is empty,
1351 * process the testQueue
1353 function advance() {
1356 if (!taskQueue.length) {
1362 * Advances the taskQueue to the next task if it is ready and not empty.
1364 function advanceTaskQueue() {
1366 config.depth = (config.depth || 0) + 1;
1368 while (taskQueue.length && !config.blocking) {
1369 var elapsedTime = now() - start;
1371 if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
1372 var task = taskQueue.shift();
1375 setTimeout(advance);
1384 * Advance the testQueue to the next test to process. Call done() if testQueue completes.
1386 function advanceTestQueue() {
1387 if (!config.blocking && !config.queue.length && config.depth === 0) {
1392 var testTasks = config.queue.shift();
1393 addToTaskQueue(testTasks());
1395 if (priorityCount > 0) {
1403 * Enqueue the tasks for a test into the task queue.
1404 * @param {Array} tasksArray
1406 function addToTaskQueue(tasksArray) {
1407 taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
1411 * Return the number of tasks remaining in the task queue to be processed.
1414 function taskQueueLength() {
1415 return taskQueue.length;
1419 * Adds a test to the TestQueue for execution.
1420 * @param {Function} testTasksFunc
1421 * @param {Boolean} prioritize
1422 * @param {String} seed
1424 function addToTestQueue(testTasksFunc, prioritize, seed) {
1426 config.queue.splice(priorityCount++, 0, testTasksFunc);
1429 unitSampler = unitSamplerGenerator(seed);
1432 // Insert into a random position after all prioritized items
1433 var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
1434 config.queue.splice(priorityCount + index, 0, testTasksFunc);
1436 config.queue.push(testTasksFunc);
1441 * Creates a seeded "sample
" generator which is used for randomizing tests.
1443 function unitSamplerGenerator(seed) {
1445 // 32-bit xorshift, requires only a nonzero seed
1446 // http://excamera.com/sphinx/article-xorshift.html
1447 var sample = parseInt(generateHash(seed), 16) || -1;
1448 return function () {
1449 sample ^= sample << 13;
1450 sample ^= sample >>> 17;
1451 sample ^= sample << 5;
1453 // ECMAScript has no unsigned number type
1455 sample += 0x100000000;
1458 return sample / 0x100000000;
1463 * This function is called when the ProcessingQueue is done processing all
1464 * items. It handles emitting the final run events.
1467 var storage = config.storage;
1469 ProcessingQueue.finished = true;
1471 var runtime = now() - config.started;
1472 var passed = config.stats.all - config.stats.bad;
1474 if (config.stats.all === 0) {
1476 if (config.filter && config.filter.length) {
1477 throw new Error("No tests matched the filter
\"" + config.filter + "\".");
1480 if (config.module && config.module.length) {
1481 throw new Error("No tests matched the module
\"" + config.module + "\".");
1484 if (config.moduleId && config.moduleId.length) {
1485 throw new Error("No tests matched the moduleId
\"" + config.moduleId + "\".");
1488 if (config.testId && config.testId.length) {
1489 throw new Error("No tests matched the testId
\"" + config.testId + "\".");
1492 throw new Error("No tests were run
.");
1495 emit("runEnd
", globalSuite.end(true));
1496 runLoggingCallbacks("done
", {
1498 failed: config.stats.bad,
1499 total: config.stats.all,
1503 // Clear own storage items if all tests passed
1504 if (storage && config.stats.bad === 0) {
1505 for (var i = storage.length - 1; i >= 0; i--) {
1506 var key = storage.key(i);
1508 if (key.indexOf("qunit
-test
-") === 0) {
1509 storage.removeItem(key);
1515 var ProcessingQueue = {
1517 add: addToTestQueue,
1519 taskCount: taskQueueLength
1522 var TestReport = function () {
1523 function TestReport(name, suite, options) {
1524 classCallCheck(this, TestReport);
1527 this.suiteName = suite.name;
1528 this.fullName = suite.fullName.concat(name);
1530 this.assertions = [];
1532 this.skipped = !!options.skip;
1533 this.todo = !!options.todo;
1535 this.valid = options.valid;
1537 this._startTime = 0;
1540 suite.pushTest(this);
1543 createClass(TestReport, [{
1545 value: function start(recordTime) {
1547 this._startTime = Date.now();
1552 suiteName: this.suiteName,
1553 fullName: this.fullName.slice()
1558 value: function end(recordTime) {
1560 this._endTime = Date.now();
1563 return extend(this.start(), {
1564 runtime: this.getRuntime(),
1565 status: this.getStatus(),
1566 errors: this.getFailedAssertions(),
1567 assertions: this.getAssertions()
1571 key: "pushAssertion
",
1572 value: function pushAssertion(assertion) {
1573 this.assertions.push(assertion);
1577 value: function getRuntime() {
1578 return this._endTime - this._startTime;
1582 value: function getStatus() {
1587 var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
1591 } else if (this.todo) {
1598 key: "getFailedAssertions
",
1599 value: function getFailedAssertions() {
1600 return this.assertions.filter(function (assertion) {
1601 return !assertion.passed;
1605 key: "getAssertions
",
1606 value: function getAssertions() {
1607 return this.assertions.slice();
1610 // Remove actual and expected values from assertions. This is to prevent
1611 // leaking memory throughout a test suite.
1614 key: "slimAssertions
",
1615 value: function slimAssertions() {
1616 this.assertions = this.assertions.map(function (assertion) {
1617 delete assertion.actual;
1618 delete assertion.expected;
1626 var focused$1 = false;
1628 function Test(settings) {
1633 this.expected = null;
1634 this.assertions = [];
1636 this.module = config.currentModule;
1637 this.stack = sourceFromStacktrace(3);
1639 this.timeout = undefined;
1641 // If a module is skipped, all its tests and the tests of the child suites
1642 // should be treated as skipped even if they are defined as `only` or `todo`.
1643 // As for `todo` module, all its tests will be treated as `todo` except for
1644 // tests defined as `skip` which will be left intact.
1646 // So, if a test is defined as `todo` and is inside a skipped module, we should
1647 // then treat that test as if was defined as `skip`.
1648 if (this.module.skip) {
1649 settings.skip = true;
1650 settings.todo = false;
1652 // Skipped tests should be left intact
1653 } else if (this.module.todo && !settings.skip) {
1654 settings.todo = true;
1657 extend(this, settings);
1659 this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
1660 todo: settings.todo,
1661 skip: settings.skip,
1665 // Register unique strings
1666 for (i = 0, l = this.module.tests; i < l.length; i++) {
1667 if (this.module.tests[i].name === this.testName) {
1668 this.testName += " ";
1672 this.testId = generateHash(this.module.name, this.testName);
1674 this.module.tests.push({
1675 name: this.testName,
1676 testId: this.testId,
1677 skip: !!settings.skip
1680 if (settings.skip) {
1682 // Skipped tests will fully ignore any sent callback
1683 this.callback = function () {};
1687 if (typeof this.callback !== "function") {
1688 var method = this.todo ? "todo
" : "test
";
1690 // eslint-disable-next-line max-len
1691 throw new TypeError("You must provide a
function as a test callback to QUnit
." + method + "(\"" + settings.testName + "\")");
1694 this.assert = new Assert(this);
1700 function getNotStartedModules(startModule) {
1701 var module = startModule,
1704 while (module && module.testsRun === 0) {
1705 modules.push(module);
1706 module = module.parentModule;
1713 before: function before() {
1716 module = this.module,
1717 notStartedModules = getNotStartedModules(module);
1719 for (i = notStartedModules.length - 1; i >= 0; i--) {
1720 startModule = notStartedModules[i];
1721 startModule.stats = { all: 0, bad: 0, started: now() };
1722 emit("suiteStart
", startModule.suiteReport.start(true));
1723 runLoggingCallbacks("moduleStart
", {
1724 name: startModule.name,
1725 tests: startModule.tests
1729 config.current = this;
1731 this.testEnvironment = extend({}, module.testEnvironment);
1733 this.started = now();
1734 emit("testStart
", this.testReport.start(true));
1735 runLoggingCallbacks("testStart
", {
1736 name: this.testName,
1737 module: module.name,
1738 testId: this.testId,
1739 previousFailure: this.previousFailure
1742 if (!config.pollution) {
1747 run: function run() {
1750 config.current = this;
1752 this.callbackStarted = now();
1754 if (config.notrycatch) {
1762 this.pushFailure("Died on test
#" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
1764 // Else next test will carry the responsibility
1767 // Restart the tests if they're blocking
1768 if (config.blocking) {
1769 internalRecover(this);
1773 function runTest(test) {
1774 promise = test.callback.call(test.testEnvironment, test.assert);
1775 test.resolvePromise(promise);
1777 // If the test has a "lock
" on it, but the timeout is 0, then we push a
1778 // failure as the test should be synchronous.
1779 if (test.timeout === 0 && test.semaphore !== 0) {
1780 pushFailure("Test did not finish synchronously even though assert
.timeout( 0 ) was used
.", sourceFromStacktrace(2));
1785 after: function after() {
1789 queueHook: function queueHook(hook, hookName, hookOwner) {
1792 var callHook = function callHook() {
1793 var promise = hook.call(_this.testEnvironment, _this.assert);
1794 _this.resolvePromise(promise, hookName);
1797 var runHook = function runHook() {
1798 if (hookName === "before
") {
1799 if (hookOwner.unskippedTestsRun !== 0) {
1803 _this.preserveEnvironment = true;
1806 // The 'after' hook should only execute when there are not tests left and
1807 // when the 'after' and 'finish' tasks are the only tasks left to process
1808 if (hookName === "after
" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) {
1812 config.current = _this;
1813 if (config.notrycatch) {
1820 _this.pushFailure(hookName + " failed on
" + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
1828 // Currently only used for module level hooks, can be used to add global level ones
1829 hooks: function hooks(handler) {
1832 function processHooks(test, module) {
1833 if (module.parentModule) {
1834 processHooks(test, module.parentModule);
1837 if (module.hooks[handler].length) {
1838 for (var i = 0; i < module.hooks[handler].length; i++) {
1839 hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
1844 // Hooks are ignored on skipped tests
1846 processHooks(this, this.module);
1853 finish: function finish() {
1854 config.current = this;
1856 // Release the test callback to ensure that anything referenced has been
1857 // released to be garbage collected.
1858 this.callback = undefined;
1860 if (this.steps.length) {
1861 var stepsList = this.steps.join(", ");
1862 this.pushFailure("Expected assert
.verifySteps() to be called before end
of test
" + ("after using assert
.step(). Unverified steps
: " + stepsList), this.stack);
1865 if (config.requireExpects && this.expected === null) {
1866 this.pushFailure("Expected number
of assertions to be defined
, but
expect() was
" + "not called
.", this.stack);
1867 } else if (this.expected !== null && this.expected !== this.assertions.length) {
1868 this.pushFailure("Expected
" + this.expected + " assertions
, but
" + this.assertions.length + " were run
", this.stack);
1869 } else if (this.expected === null && !this.assertions.length) {
1870 this.pushFailure("Expected at least one assertion
, but none were run
- call
" + "expect(0) to accept zero assertions
.", this.stack);
1874 module = this.module,
1875 moduleName = module.name,
1876 testName = this.testName,
1877 skipped = !!this.skip,
1880 storage = config.storage;
1882 this.runtime = now() - this.started;
1884 config.stats.all += this.assertions.length;
1885 module.stats.all += this.assertions.length;
1887 for (i = 0; i < this.assertions.length; i++) {
1888 if (!this.assertions[i].result) {
1895 notifyTestsRan(module, skipped);
1897 // Store result when possible
1900 storage.setItem("qunit
-test
-" + moduleName + "-" + testName, bad);
1902 storage.removeItem("qunit
-test
-" + moduleName + "-" + testName);
1906 // After emitting the js-reporters event we cleanup the assertion data to
1907 // avoid leaking it. It is not used by the legacy testDone callbacks.
1908 emit("testEnd
", this.testReport.end(true));
1909 this.testReport.slimAssertions();
1911 runLoggingCallbacks("testDone
", {
1917 passed: this.assertions.length - bad,
1918 total: this.assertions.length,
1919 runtime: skipped ? 0 : this.runtime,
1921 // HTML Reporter use
1922 assertions: this.assertions,
1923 testId: this.testId,
1929 if (module.testsRun === numberOfTests(module)) {
1930 logSuiteEnd(module);
1932 // Check if the parent modules, iteratively, are done. If that the case,
1933 // we emit the `suiteEnd` event and trigger `moduleDone` callback.
1934 var parent = module.parentModule;
1935 while (parent && parent.testsRun === numberOfTests(parent)) {
1936 logSuiteEnd(parent);
1937 parent = parent.parentModule;
1941 config.current = undefined;
1943 function logSuiteEnd(module) {
1945 // Reset `module.hooks` to ensure that anything referenced in these hooks
1946 // has been released to be garbage collected.
1949 emit("suiteEnd
", module.suiteReport.end(true));
1950 runLoggingCallbacks("moduleDone
", {
1952 tests: module.tests,
1953 failed: module.stats.bad,
1954 passed: module.stats.all - module.stats.bad,
1955 total: module.stats.all,
1956 runtime: now() - module.stats.started
1961 preserveTestEnvironment: function preserveTestEnvironment() {
1962 if (this.preserveEnvironment) {
1963 this.module.testEnvironment = this.testEnvironment;
1964 this.testEnvironment = extend({}, this.module.testEnvironment);
1968 queue: function queue() {
1971 if (!this.valid()) {
1975 function runTest() {
1976 return [function () {
1978 }].concat(toConsumableArray(test.hooks("before
")), [function () {
1979 test.preserveTestEnvironment();
1980 }], toConsumableArray(test.hooks("beforeEach
")), [function () {
1982 }], toConsumableArray(test.hooks("afterEach
").reverse()), toConsumableArray(test.hooks("after
").reverse()), [function () {
1989 var previousFailCount = config.storage && +config.storage.getItem("qunit
-test
-" + this.module.name + "-" + this.testName);
1991 // Prioritize previously failed tests, detected from storage
1992 var prioritize = config.reorder && !!previousFailCount;
1994 this.previousFailure = !!previousFailCount;
1996 ProcessingQueue.add(runTest, prioritize, config.seed);
1998 // If the queue has already finished, we manually process the new test
1999 if (ProcessingQueue.finished) {
2000 ProcessingQueue.advance();
2005 pushResult: function pushResult(resultInfo) {
2006 if (this !== config.current) {
2007 throw new Error("Assertion occurred after test had finished
.");
2010 // Destructure of resultInfo = { result, actual, expected, message, negative }
2013 module: this.module.name,
2014 name: this.testName,
2015 result: resultInfo.result,
2016 message: resultInfo.message,
2017 actual: resultInfo.actual,
2018 testId: this.testId,
2019 negative: resultInfo.negative || false,
2020 runtime: now() - this.started,
2024 if (hasOwn.call(resultInfo, "expected
")) {
2025 details.expected = resultInfo.expected;
2028 if (!resultInfo.result) {
2029 source = resultInfo.source || sourceFromStacktrace();
2032 details.source = source;
2036 this.logAssertion(details);
2038 this.assertions.push({
2039 result: !!resultInfo.result,
2040 message: resultInfo.message
2044 pushFailure: function pushFailure(message, source, actual) {
2045 if (!(this instanceof Test)) {
2046 throw new Error("pushFailure() assertion outside test context
, was
" + sourceFromStacktrace(2));
2051 message: message || "error
",
2052 actual: actual || null,
2058 * Log assertion details using both the old QUnit.log interface and
2059 * QUnit.on( "assertion
" ) interface.
2063 logAssertion: function logAssertion(details) {
2064 runLoggingCallbacks("log
", details);
2067 passed: details.result,
2068 actual: details.actual,
2069 expected: details.expected,
2070 message: details.message,
2071 stack: details.source,
2074 this.testReport.pushAssertion(assertion);
2075 emit("assertion
", assertion);
2079 resolvePromise: function resolvePromise(promise, phase) {
2084 if (promise != null) {
2085 then = promise.then;
2086 if (objectType(then) === "function") {
2087 resume = internalStop(test);
2088 if (config.notrycatch) {
2089 then.call(promise, function () {
2093 then.call(promise, function () {
2095 }, function (error) {
2096 message = "Promise rejected
" + (!phase ? "during
" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
2097 test.pushFailure(message, extractStacktrace(error, 0));
2099 // Else next test will carry the responsibility
2103 internalRecover(test);
2110 valid: function valid() {
2111 var filter = config.filter,
2112 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
2113 module = config.module && config.module.toLowerCase(),
2114 fullName = this.module.name + ": " + this.testName;
2116 function moduleChainNameMatch(testModule) {
2117 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
2118 if (testModuleName === module) {
2120 } else if (testModule.parentModule) {
2121 return moduleChainNameMatch(testModule.parentModule);
2127 function moduleChainIdMatch(testModule) {
2128 return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
2131 // Internally-generated tests are always valid
2132 if (this.callback && this.callback.validTest) {
2136 if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
2141 if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
2146 if (module && !moduleChainNameMatch(this.module)) {
2154 return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
2157 regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
2158 var regex = new RegExp(pattern, flags);
2159 var match = regex.test(fullName);
2161 return match !== exclude;
2164 stringFilter: function stringFilter(filter, fullName) {
2165 filter = filter.toLowerCase();
2166 fullName = fullName.toLowerCase();
2168 var include = filter.charAt(0) !== "!";
2170 filter = filter.slice(1);
2173 // If the filter matches, we need to honour include
2174 if (fullName.indexOf(filter) !== -1) {
2178 // Otherwise, do the opposite
2183 function pushFailure() {
2184 if (!config.current) {
2185 throw new Error("pushFailure() assertion outside test context
, in " + sourceFromStacktrace(2));
2188 // Gets current test obj
2189 var currentTest = config.current;
2191 return currentTest.pushFailure.apply(currentTest, arguments);
2194 function saveGlobal() {
2195 config.pollution = [];
2197 if (config.noglobals) {
2198 for (var key in global$1) {
2199 if (hasOwn.call(global$1, key)) {
2201 // In Opera sometimes DOM element ids show up here, ignore them
2202 if (/^qunit-test-output/.test(key)) {
2205 config.pollution.push(key);
2211 function checkPollution() {
2214 old = config.pollution;
2218 newGlobals = diff(config.pollution, old);
2219 if (newGlobals.length > 0) {
2220 pushFailure("Introduced global
variable(s
): " + newGlobals.join(", "));
2223 deletedGlobals = diff(old, config.pollution);
2224 if (deletedGlobals.length > 0) {
2225 pushFailure("Deleted global
variable(s
): " + deletedGlobals.join(", "));
2229 // Will be exposed as QUnit.test
2230 function test(testName, callback) {
2235 var newTest = new Test({
2243 function todo(testName, callback) {
2248 var newTest = new Test({
2257 // Will be exposed as QUnit.skip
2258 function skip(testName) {
2263 var test = new Test({
2271 // Will be exposed as QUnit.only
2272 function only(testName, callback) {
2277 config.queue.length = 0;
2280 var newTest = new Test({
2288 // Put a hold on processing and return a function that will release it.
2289 function internalStop(test) {
2290 test.semaphore += 1;
2291 config.blocking = true;
2293 // Set a recovery timeout, if so configured.
2294 if (defined.setTimeout) {
2295 var timeoutDuration = void 0;
2297 if (typeof test.timeout === "number
") {
2298 timeoutDuration = test.timeout;
2299 } else if (typeof config.testTimeout === "number
") {
2300 timeoutDuration = config.testTimeout;
2303 if (typeof timeoutDuration === "number
" && timeoutDuration > 0) {
2304 clearTimeout(config.timeout);
2305 config.timeout = setTimeout(function () {
2306 pushFailure("Test took longer than
" + timeoutDuration + "ms
; test timed out
.", sourceFromStacktrace(2));
2307 internalRecover(test);
2308 }, timeoutDuration);
2312 var released = false;
2313 return function resume() {
2319 test.semaphore -= 1;
2320 internalStart(test);
2324 // Forcefully release all processing holds.
2325 function internalRecover(test) {
2327 internalStart(test);
2330 // Release a processing hold, scheduling a resumption attempt if no holds remain.
2331 function internalStart(test) {
2333 // If semaphore is non-numeric, throw error
2334 if (isNaN(test.semaphore)) {
2337 pushFailure("Invalid value on test
.semaphore
", sourceFromStacktrace(2));
2341 // Don't start until equal number of stop-calls
2342 if (test.semaphore > 0) {
2346 // Throw an Error if start is called more often than stop
2347 if (test.semaphore < 0) {
2350 pushFailure("Tried to restart test
while already
started (test
's semaphore was 0 already)", sourceFromStacktrace(2));
2354 // Add a slight delay to allow more assertions etc.
2355 if (defined.setTimeout) {
2356 if (config.timeout) {
2357 clearTimeout(config.timeout);
2359 config.timeout = setTimeout(function () {
2360 if (test.semaphore > 0) {
2364 if (config.timeout) {
2365 clearTimeout(config.timeout);
2375 function collectTests(module) {
2376 var tests = [].concat(module.tests);
2377 var modules = [].concat(toConsumableArray(module.childModules));
2379 // Do a breadth-first traversal of the child modules
2380 while (modules.length) {
2381 var nextModule = modules.shift();
2382 tests.push.apply(tests, nextModule.tests);
2383 modules.push.apply(modules, toConsumableArray(nextModule.childModules));
2389 function numberOfTests(module) {
2390 return collectTests(module).length;
2393 function numberOfUnskippedTests(module) {
2394 return collectTests(module).filter(function (test) {
2399 function notifyTestsRan(module, skipped) {
2402 module.unskippedTestsRun++;
2404 while (module = module.parentModule) {
2407 module.unskippedTestsRun++;
2413 * Returns a function that proxies to the given method name on the globals
2414 * console object. The proxy will also detect if the console doesn't exist and
2415 * will appropriately no
-op
. This allows support
for IE9
, which doesn
't have a
2416 * console if the developer tools are not open.
2418 function consoleProxy(method) {
2419 return function () {
2421 console[method].apply(console, arguments);
2427 warn: consoleProxy("warn")
2430 var Assert = function () {
2431 function Assert(testContext) {
2432 classCallCheck(this, Assert);
2434 this.test = testContext;
2439 createClass(Assert, [{
2441 value: function timeout(duration) {
2442 if (typeof duration !== "number") {
2443 throw new Error("You must pass a number as the duration to assert.timeout");
2446 this.test.timeout = duration;
2449 // Documents a "step", which is a string value, in a test as a passing assertion
2453 value: function step(message) {
2454 var assertionMessage = message;
2455 var result = !!message;
2457 this.test.steps.push(message);
2459 if (objectType(message) === "undefined" || message === "") {
2460 assertionMessage = "You must provide a message to assert.step";
2461 } else if (objectType(message) !== "string") {
2462 assertionMessage = "You must provide a string value to assert.step";
2468 message: assertionMessage
2472 // Verifies the steps in a test match a given array of string values
2476 value: function verifySteps(steps, message) {
2478 // Since the steps array is just string values, we can clone with slice
2479 var actualStepsClone = this.test.steps.slice();
2480 this.deepEqual(actualStepsClone, steps, message);
2481 this.test.steps.length = 0;
2484 // Specify the number of expected assertions to guarantee that failed test
2485 // (no assertions are run at all) don't slip through
.
2489 value
: function expect(asserts
) {
2490 if (arguments
.length
=== 1) {
2491 this.test
.expected
= asserts
;
2493 return this.test
.expected
;
2497 // Put a hold on processing and return a function that will release it a maximum of once.
2501 value
: function async(count
) {
2502 var test
$$1 = this.test
;
2505 acceptCallCount
= count
;
2507 if (typeof acceptCallCount
=== "undefined") {
2508 acceptCallCount
= 1;
2511 var resume
= internalStop(test
$$1);
2513 return function done() {
2514 if (config
.current
!== test
$$1) {
2515 throw Error("assert.async callback called after test finished.");
2519 test
$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
2523 acceptCallCount
-= 1;
2524 if (acceptCallCount
> 0) {
2533 // Exports test.push() to the user API
2534 // Alias of pushResult.
2538 value
: function push(result
, actual
, expected
, message
, negative
) {
2539 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).");
2541 var currentAssert
= this instanceof Assert
? this : config
.current
.assert
;
2542 return currentAssert
.pushResult({
2552 value
: function pushResult(resultInfo
) {
2554 // Destructure of resultInfo = { result, actual, expected, message, negative }
2556 var currentTest
= assert
instanceof Assert
&& assert
.test
|| config
.current
;
2558 // Backwards compatibility fix.
2559 // Allows the direct use of global exported assertions and QUnit.assert.*
2560 // Although, it's use is not recommended as it can leak assertions
2561 // to other tests from async tests, because we only get a reference to the current test,
2562 // not exactly the test where assertion were intended to be called.
2564 throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
2567 if (!(assert
instanceof Assert
)) {
2568 assert
= currentTest
.assert
;
2571 return assert
.test
.pushResult(resultInfo
);
2575 value
: function ok(result
, message
) {
2577 message
= result
? "okay" : "failed, expected argument to be truthy, was: " + dump
.parse(result
);
2589 value
: function notOk(result
, message
) {
2591 message
= !result
? "okay" : "failed, expected argument to be falsy, was: " + dump
.parse(result
);
2603 value
: function equal(actual
, expected
, message
) {
2605 // eslint-disable-next-line eqeqeq
2606 var result
= expected
== actual
;
2617 value
: function notEqual(actual
, expected
, message
) {
2619 // eslint-disable-next-line eqeqeq
2620 var result
= expected
!= actual
;
2632 value
: function propEqual(actual
, expected
, message
) {
2633 actual
= objectValues(actual
);
2634 expected
= objectValues(expected
);
2637 result
: equiv(actual
, expected
),
2644 key
: "notPropEqual",
2645 value
: function notPropEqual(actual
, expected
, message
) {
2646 actual
= objectValues(actual
);
2647 expected
= objectValues(expected
);
2650 result
: !equiv(actual
, expected
),
2659 value
: function deepEqual(actual
, expected
, message
) {
2661 result
: equiv(actual
, expected
),
2668 key
: "notDeepEqual",
2669 value
: function notDeepEqual(actual
, expected
, message
) {
2671 result
: !equiv(actual
, expected
),
2680 value
: function strictEqual(actual
, expected
, message
) {
2682 result
: expected
=== actual
,
2689 key
: "notStrictEqual",
2690 value
: function notStrictEqual(actual
, expected
, message
) {
2692 result
: expected
!== actual
,
2701 value
: function throws(block
, expected
, message
) {
2702 var actual
= void 0,
2705 var currentTest
= this instanceof Assert
&& this.test
|| config
.current
;
2707 // 'expected' is optional unless doing string comparison
2708 if (objectType(expected
) === "string") {
2709 if (message
== null) {
2713 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.");
2717 currentTest
.ignoreGlobalErrors
= true;
2719 block
.call(currentTest
.testEnvironment
);
2723 currentTest
.ignoreGlobalErrors
= false;
2726 var expectedType
= objectType(expected
);
2728 // We don't want to validate thrown error
2733 // Expected is a regexp
2734 } else if (expectedType
=== "regexp") {
2735 result
= expected
.test(errorString(actual
));
2737 // Expected is a constructor, maybe an Error constructor
2738 } else if (expectedType
=== "function" && actual
instanceof expected
) {
2741 // Expected is an Error object
2742 } else if (expectedType
=== "object") {
2743 result
= actual
instanceof expected
.constructor && actual
.name
=== expected
.name
&& actual
.message
=== expected
.message
;
2745 // Expected is a validation function which returns true if validation passed
2746 } else if (expectedType
=== "function" && expected
.call({}, actual
) === true) {
2752 currentTest
.assert
.pushResult({
2761 value
: function rejects(promise
, expected
, message
) {
2764 var currentTest
= this instanceof Assert
&& this.test
|| config
.current
;
2766 // 'expected' is optional unless doing string comparison
2767 if (objectType(expected
) === "string") {
2768 if (message
=== undefined) {
2770 expected
= undefined;
2772 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.";
2774 currentTest
.assert
.pushResult({
2783 var then
= promise
&& promise
.then
;
2784 if (objectType(then
) !== "function") {
2785 var _message
= "The value provided to `assert.rejects` in " + "\"" + currentTest
.testName
+ "\" was not a promise.";
2787 currentTest
.assert
.pushResult({
2796 var done
= this.async();
2798 return then
.call(promise
, function handleFulfillment() {
2799 var message
= "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest
.testName
+ "\" did not reject.";
2801 currentTest
.assert
.pushResult({
2808 }, function handleRejection(actual
) {
2809 var expectedType
= objectType(expected
);
2811 // We don't want to validate
2812 if (expected
=== undefined) {
2816 // Expected is a regexp
2817 } else if (expectedType
=== "regexp") {
2818 result
= expected
.test(errorString(actual
));
2820 // Expected is a constructor, maybe an Error constructor
2821 } else if (expectedType
=== "function" && actual
instanceof expected
) {
2824 // Expected is an Error object
2825 } else if (expectedType
=== "object") {
2826 result
= actual
instanceof expected
.constructor && actual
.name
=== expected
.name
&& actual
.message
=== expected
.message
;
2828 // Expected is a validation function which returns true if validation passed
2830 if (expectedType
=== "function") {
2831 result
= expected
.call({}, actual
) === true;
2834 // Expected is some other invalid type
2837 message
= "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest
.testName
+ "\": " + expectedType
+ ".";
2841 currentTest
.assert
.pushResult({
2855 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
2856 // Known to us are: Closure Compiler, Narwhal
2857 // eslint-disable-next-line dot-notation
2860 Assert
.prototype.raises
= Assert
.prototype["throws"];
2863 * Converts an error into a simple string for comparisons.
2865 * @param {Error} error
2868 function errorString(error
) {
2869 var resultErrorString
= error
.toString();
2871 if (resultErrorString
.substring(0, 7) === "[object") {
2872 var name
= error
.name
? error
.name
.toString() : "Error";
2873 var message
= error
.message
? error
.message
.toString() : "";
2875 if (name
&& message
) {
2876 return name
+ ": " + message
;
2879 } else if (message
) {
2885 return resultErrorString
;
2889 /* global module, exports, define */
2890 function exportQUnit(QUnit
) {
2892 if (defined
.document
) {
2894 // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
2895 if (window
.QUnit
&& window
.QUnit
.version
) {
2896 throw new Error("QUnit has already been defined.");
2899 window
.QUnit
= QUnit
;
2903 if (typeof module
!== "undefined" && module
&& module
.exports
) {
2904 module
.exports
= QUnit
;
2906 // For consistency with CommonJS environments' exports
2907 module
.exports
.QUnit
= QUnit
;
2910 // For CommonJS with exports, but without module.exports, like Rhino
2911 if (typeof exports
!== "undefined" && exports
) {
2912 exports
.QUnit
= QUnit
;
2915 if (typeof define
=== "function" && define
.amd
) {
2916 define(function () {
2919 QUnit
.config
.autostart
= false;
2922 // For Web/Service Workers
2923 if (self
$1 && self
$1.WorkerGlobalScope
&& self
$1 instanceof self
$1.WorkerGlobalScope
) {
2924 self
$1.QUnit
= QUnit
;
2928 // Handle an unhandled exception. By convention, returns true if further
2929 // error handling should be suppressed and false otherwise.
2930 // In this case, we will only suppress further error handling if the
2931 // "ignoreGlobalErrors" configuration option is enabled.
2932 function onError(error
) {
2933 for (var _len
= arguments
.length
, args
= Array(_len
> 1 ? _len
- 1 : 0), _key
= 1; _key
< _len
; _key
++) {
2934 args
[_key
- 1] = arguments
[_key
];
2937 if (config
.current
) {
2938 if (config
.current
.ignoreGlobalErrors
) {
2941 pushFailure
.apply(undefined, [error
.message
, error
.fileName
+ ":" + error
.lineNumber
].concat(args
));
2943 test("global failure", extend(function () {
2944 pushFailure
.apply(undefined, [error
.message
, error
.fileName
+ ":" + error
.lineNumber
].concat(args
));
2945 }, { validTest
: true }));
2951 // Handle an unhandled rejection
2952 function onUnhandledRejection(reason
) {
2955 message
: reason
.message
|| "error",
2957 source
: reason
.stack
|| sourceFromStacktrace(3)
2960 var currentTest
= config
.current
;
2962 currentTest
.assert
.pushResult(resultInfo
);
2964 test("global failure", extend(function (assert
) {
2965 assert
.pushResult(resultInfo
);
2966 }, { validTest
: true }));
2971 var globalSuite
= new SuiteReport();
2973 // The initial "currentModule" represents the global (or top-level) module that
2974 // is not explicitly defined by the user, therefore we add the "globalSuite" to
2975 // it since each module has a suiteReport associated with it.
2976 config
.currentModule
.suiteReport
= globalSuite
;
2978 var globalStartCalled
= false;
2979 var runStarted
= false;
2981 // Figure out if we're running the tests from a server or not
2982 QUnit
.isLocal
= !(defined
.document
&& window
.location
.protocol
!== "file:");
2984 // Expose the current QUnit version
2985 QUnit
.version
= "2.6.2";
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();
3133 if (typeof window
=== "undefined" || typeof document
=== "undefined") {
3137 var config
= QUnit
.config
,
3138 hasOwn
= Object
.prototype.hasOwnProperty
;
3140 // Stores fixture HTML for resetting later
3141 function storeFixture() {
3143 // Avoid overwriting user-defined values
3144 if (hasOwn
.call(config
, "fixture")) {
3148 var fixture
= document
.getElementById("qunit-fixture");
3150 config
.fixture
= fixture
.cloneNode(true);
3154 QUnit
.begin(storeFixture
);
3156 // Resets the fixture DOM element if available.
3157 function resetFixture() {
3158 if (config
.fixture
== null) {
3162 var fixture
= document
.getElementById("qunit-fixture");
3163 var resetFixtureType
= _typeof(config
.fixture
);
3164 if (resetFixtureType
=== "string") {
3166 // support user defined values for `config.fixture`
3167 var newFixture
= document
.createElement("div");
3168 newFixture
.setAttribute("id", "qunit-fixture");
3169 newFixture
.innerHTML
= config
.fixture
;
3170 fixture
.parentNode
.replaceChild(newFixture
, fixture
);
3172 var clonedFixture
= config
.fixture
.cloneNode(true);
3173 fixture
.parentNode
.replaceChild(clonedFixture
, fixture
);
3177 QUnit
.testStart(resetFixture
);
3182 // Only interact with URLs via window.location
3183 var location
= typeof window
!== "undefined" && window
.location
;
3188 var urlParams
= getUrlParams();
3190 QUnit
.urlParams
= urlParams
;
3192 // Match module/test by inclusion in an array
3193 QUnit
.config
.moduleId
= [].concat(urlParams
.moduleId
|| []);
3194 QUnit
.config
.testId
= [].concat(urlParams
.testId
|| []);
3196 // Exact case-insensitive match of the module name
3197 QUnit
.config
.module
= urlParams
.module
;
3199 // Regular expression or case-insenstive substring match against "moduleName: testName"
3200 QUnit
.config
.filter
= urlParams
.filter
;
3202 // Test order randomization
3203 if (urlParams
.seed
=== true) {
3205 // Generate a random seed if the option is specified without a value
3206 QUnit
.config
.seed
= Math
.random().toString(36).slice(2);
3207 } else if (urlParams
.seed
) {
3208 QUnit
.config
.seed
= urlParams
.seed
;
3211 // Add URL-parameter-mapped config values with UI form rendering data
3212 QUnit
.config
.urlConfig
.push({
3214 label
: "Hide passed tests",
3215 tooltip
: "Only show tests and assertions that fail. Stored as query-strings."
3218 label
: "Check for Globals",
3219 tooltip
: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
3222 label
: "No try-catch",
3223 tooltip
: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
3226 QUnit
.begin(function () {
3229 urlConfig
= QUnit
.config
.urlConfig
;
3231 for (i
= 0; i
< urlConfig
.length
; i
++) {
3233 // Options can be either strings or objects with nonempty "id" properties
3234 option
= QUnit
.config
.urlConfig
[i
];
3235 if (typeof option
!== "string") {
3239 if (QUnit
.config
[option
] === undefined) {
3240 QUnit
.config
[option
] = urlParams
[option
];
3245 function getUrlParams() {
3246 var i
, param
, name
, value
;
3247 var urlParams
= Object
.create(null);
3248 var params
= location
.search
.slice(1).split("&");
3249 var length
= params
.length
;
3251 for (i
= 0; i
< length
; i
++) {
3253 param
= params
[i
].split("=");
3254 name
= decodeQueryParam(param
[0]);
3256 // Allow just a key to turn on a flag, e.g., test.html?noglobals
3257 value
= param
.length
=== 1 || decodeQueryParam(param
.slice(1).join("="));
3258 if (name
in urlParams
) {
3259 urlParams
[name
] = [].concat(urlParams
[name
], value
);
3261 urlParams
[name
] = value
;
3269 function decodeQueryParam(param
) {
3270 return decodeURIComponent(param
.replace(/\+/g, "%20"));
3281 // Escape text for attribute or text content.
3282 function escapeText(s
) {
3288 // Both single quotes and double quotes (for attributes)
3289 return s
.replace(/['"<>&]/g, function (s
) {
3307 // Don't load the HTML Reporter on non-browser environments
3308 if (typeof window
=== "undefined" || !window
.document
) {
3312 var config
= QUnit
.config
,
3313 document
$$1 = window
.document
,
3314 collapseNext
= false,
3315 hasOwn
= Object
.prototype.hasOwnProperty
,
3316 unfilteredUrl
= setUrl({ filter
: undefined, module
: undefined,
3317 moduleId
: undefined, testId
: undefined }),
3320 function addEvent(elem
, type
, fn
) {
3321 elem
.addEventListener(type
, fn
, false);
3324 function removeEvent(elem
, type
, fn
) {
3325 elem
.removeEventListener(type
, fn
, false);
3328 function addEvents(elems
, type
, fn
) {
3329 var i
= elems
.length
;
3331 addEvent(elems
[i
], type
, fn
);
3335 function hasClass(elem
, name
) {
3336 return (" " + elem
.className
+ " ").indexOf(" " + name
+ " ") >= 0;
3339 function addClass(elem
, name
) {
3340 if (!hasClass(elem
, name
)) {
3341 elem
.className
+= (elem
.className
? " " : "") + name
;
3345 function toggleClass(elem
, name
, force
) {
3346 if (force
|| typeof force
=== "undefined" && !hasClass(elem
, name
)) {
3347 addClass(elem
, name
);
3349 removeClass(elem
, name
);
3353 function removeClass(elem
, name
) {
3354 var set = " " + elem
.className
+ " ";
3356 // Class name may appear multiple times
3357 while (set.indexOf(" " + name
+ " ") >= 0) {
3358 set = set.replace(" " + name
+ " ", " ");
3361 // Trim for prettiness
3362 elem
.className
= typeof set.trim
=== "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
3366 return document
$$1.getElementById
&& document
$$1.getElementById(name
);
3369 function abortTests() {
3370 var abortButton
= id("qunit-abort-tests-button");
3372 abortButton
.disabled
= true;
3373 abortButton
.innerHTML
= "Aborting...";
3375 QUnit
.config
.queue
.length
= 0;
3379 function interceptNavigation(ev
) {
3382 if (ev
&& ev
.preventDefault
) {
3383 ev
.preventDefault();
3389 function getUrlConfigHtml() {
3396 urlConfig
= config
.urlConfig
,
3399 for (i
= 0; i
< urlConfig
.length
; i
++) {
3401 // Options can be either strings or objects with nonempty "id" properties
3402 val
= config
.urlConfig
[i
];
3403 if (typeof val
=== "string") {
3410 escaped
= escapeText(val
.id
);
3411 escapedTooltip
= escapeText(val
.tooltip
);
3413 if (!val
.value
|| typeof val
.value
=== "string") {
3414 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>";
3416 urlConfigHtml
+= "<label for='qunit-urlconfig-" + escaped
+ "' title='" + escapedTooltip
+ "'>" + val
.label
+ ": </label><select id='qunit-urlconfig-" + escaped
+ "' name='" + escaped
+ "' title='" + escapedTooltip
+ "'><option></option>";
3418 if (QUnit
.is("array", val
.value
)) {
3419 for (j
= 0; j
< val
.value
.length
; j
++) {
3420 escaped
= escapeText(val
.value
[j
]);
3421 urlConfigHtml
+= "<option value='" + escaped
+ "'" + (config
[val
.id
] === val
.value
[j
] ? (selection
= true) && " selected='selected'" : "") + ">" + escaped
+ "</option>";
3424 for (j
in val
.value
) {
3425 if (hasOwn
.call(val
.value
, j
)) {
3426 urlConfigHtml
+= "<option value='" + escapeText(j
) + "'" + (config
[val
.id
] === j
? (selection
= true) && " selected='selected'" : "") + ">" + escapeText(val
.value
[j
]) + "</option>";
3430 if (config
[val
.id
] && !selection
) {
3431 escaped
= escapeText(config
[val
.id
]);
3432 urlConfigHtml
+= "<option value='" + escaped
+ "' selected='selected' disabled='disabled'>" + escaped
+ "</option>";
3434 urlConfigHtml
+= "</select>";
3438 return urlConfigHtml
;
3441 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3442 // Updates the URL with the new state of `config.urlConfig` values.
3443 function toolbarChanged() {
3450 // Detect if field is a select menu or a checkbox
3451 if ("selectedIndex" in field
) {
3452 value
= field
.options
[field
.selectedIndex
].value
|| undefined;
3454 value
= field
.checked
? field
.defaultValue
|| true : undefined;
3457 params
[field
.name
] = value
;
3458 updatedUrl
= setUrl(params
);
3460 // Check if we can apply the change without a page refresh
3461 if ("hidepassed" === field
.name
&& "replaceState" in window
.history
) {
3462 QUnit
.urlParams
[field
.name
] = value
;
3463 config
[field
.name
] = value
|| false;
3464 tests
= id("qunit-tests");
3466 toggleClass(tests
, "hidepass", value
|| false);
3468 window
.history
.replaceState(null, "", updatedUrl
);
3470 window
.location
= updatedUrl
;
3474 function setUrl(params
) {
3479 location
= window
.location
;
3481 params
= QUnit
.extend(QUnit
.extend({}, QUnit
.urlParams
), params
);
3483 for (key
in params
) {
3485 // Skip inherited or undefined properties
3486 if (hasOwn
.call(params
, key
) && params
[key
] !== undefined) {
3488 // Output a parameter for each value of this key
3489 // (but usually just one)
3490 arrValue
= [].concat(params
[key
]);
3491 for (i
= 0; i
< arrValue
.length
; i
++) {
3492 querystring
+= encodeURIComponent(key
);
3493 if (arrValue
[i
] !== true) {
3494 querystring
+= "=" + encodeURIComponent(arrValue
[i
]);
3500 return location
.protocol
+ "//" + location
.host
+ location
.pathname
+ querystring
.slice(0, -1);
3503 function applyUrlParams() {
3505 selectedModules
= [],
3506 modulesList
= id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
3507 filter
= id("qunit-filter-input").value
;
3509 for (i
= 0; i
< modulesList
.length
; i
++) {
3510 if (modulesList
[i
].checked
) {
3511 selectedModules
.push(modulesList
[i
].value
);
3515 window
.location
= setUrl({
3516 filter
: filter
=== "" ? undefined : filter
,
3517 moduleId
: selectedModules
.length
=== 0 ? undefined : selectedModules
,
3519 // Remove module and testId filter
3525 function toolbarUrlConfigContainer() {
3526 var urlConfigContainer
= document
$$1.createElement("span");
3528 urlConfigContainer
.innerHTML
= getUrlConfigHtml();
3529 addClass(urlConfigContainer
, "qunit-url-config");
3531 addEvents(urlConfigContainer
.getElementsByTagName("input"), "change", toolbarChanged
);
3532 addEvents(urlConfigContainer
.getElementsByTagName("select"), "change", toolbarChanged
);
3534 return urlConfigContainer
;
3537 function abortTestsButton() {
3538 var button
= document
$$1.createElement("button");
3539 button
.id
= "qunit-abort-tests-button";
3540 button
.innerHTML
= "Abort";
3541 addEvent(button
, "click", abortTests
);
3545 function toolbarLooseFilter() {
3546 var filter
= document
$$1.createElement("form"),
3547 label
= document
$$1.createElement("label"),
3548 input
= document
$$1.createElement("input"),
3549 button
= document
$$1.createElement("button");
3551 addClass(filter
, "qunit-filter");
3553 label
.innerHTML
= "Filter: ";
3555 input
.type
= "text";
3556 input
.value
= config
.filter
|| "";
3557 input
.name
= "filter";
3558 input
.id
= "qunit-filter-input";
3560 button
.innerHTML
= "Go";
3562 label
.appendChild(input
);
3564 filter
.appendChild(label
);
3565 filter
.appendChild(document
$$1.createTextNode(" "));
3566 filter
.appendChild(button
);
3567 addEvent(filter
, "submit", interceptNavigation
);
3572 function moduleListHtml() {
3577 for (i
= 0; i
< config
.modules
.length
; i
++) {
3578 if (config
.modules
[i
].name
!== "") {
3579 checked
= config
.moduleId
.indexOf(config
.modules
[i
].moduleId
) > -1;
3580 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>";
3587 function toolbarModuleFilter() {
3591 moduleFilter
= document
$$1.createElement("form"),
3592 label
= document
$$1.createElement("label"),
3593 moduleSearch
= document
$$1.createElement("input"),
3594 dropDown
= document
$$1.createElement("div"),
3595 actions
= document
$$1.createElement("span"),
3596 dropDownList
= document
$$1.createElement("ul"),
3599 moduleSearch
.id
= "qunit-modulefilter-search";
3600 moduleSearch
.autocomplete
= "off";
3601 addEvent(moduleSearch
, "input", searchInput
);
3602 addEvent(moduleSearch
, "input", searchFocus
);
3603 addEvent(moduleSearch
, "focus", searchFocus
);
3604 addEvent(moduleSearch
, "click", searchFocus
);
3606 label
.id
= "qunit-modulefilter-search-container";
3607 label
.innerHTML
= "Module: ";
3608 label
.appendChild(moduleSearch
);
3610 actions
.id
= "qunit-modulefilter-actions";
3611 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>";
3612 allCheckbox
= actions
.lastChild
.firstChild
;
3613 commit
= actions
.firstChild
;
3614 reset
= commit
.nextSibling
;
3615 addEvent(commit
, "click", applyUrlParams
);
3617 dropDownList
.id
= "qunit-modulefilter-dropdown-list";
3618 dropDownList
.innerHTML
= moduleListHtml();
3620 dropDown
.id
= "qunit-modulefilter-dropdown";
3621 dropDown
.style
.display
= "none";
3622 dropDown
.appendChild(actions
);
3623 dropDown
.appendChild(dropDownList
);
3624 addEvent(dropDown
, "change", selectionChange
);
3627 moduleFilter
.id
= "qunit-modulefilter";
3628 moduleFilter
.appendChild(label
);
3629 moduleFilter
.appendChild(dropDown
);
3630 addEvent(moduleFilter
, "submit", interceptNavigation
);
3631 addEvent(moduleFilter
, "reset", function () {
3633 // Let the reset happen, then update styles
3634 window
.setTimeout(selectionChange
);
3637 // Enables show/hide for the dropdown
3638 function searchFocus() {
3639 if (dropDown
.style
.display
!== "none") {
3643 dropDown
.style
.display
= "block";
3644 addEvent(document
$$1, "click", hideHandler
);
3645 addEvent(document
$$1, "keydown", hideHandler
);
3647 // Hide on Escape keydown or outside-container click
3648 function hideHandler(e
) {
3649 var inContainer
= moduleFilter
.contains(e
.target
);
3651 if (e
.keyCode
=== 27 || !inContainer
) {
3652 if (e
.keyCode
=== 27 && inContainer
) {
3653 moduleSearch
.focus();
3655 dropDown
.style
.display
= "none";
3656 removeEvent(document
$$1, "click", hideHandler
);
3657 removeEvent(document
$$1, "keydown", hideHandler
);
3658 moduleSearch
.value
= "";
3664 // Processes module search box input
3665 function searchInput() {
3668 searchText
= moduleSearch
.value
.toLowerCase(),
3669 listItems
= dropDownList
.children
;
3671 for (i
= 0; i
< listItems
.length
; i
++) {
3672 item
= listItems
[i
];
3673 if (!searchText
|| item
.textContent
.toLowerCase().indexOf(searchText
) > -1) {
3674 item
.style
.display
= "";
3676 item
.style
.display
= "none";
3681 // Processes selection changes
3682 function selectionChange(evt
) {
3685 checkbox
= evt
&& evt
.target
|| allCheckbox
,
3686 modulesList
= dropDownList
.getElementsByTagName("input"),
3689 toggleClass(checkbox
.parentNode
, "checked", checkbox
.checked
);
3692 if (checkbox
.checked
&& checkbox
!== allCheckbox
) {
3693 allCheckbox
.checked
= false;
3694 removeClass(allCheckbox
.parentNode
, "checked");
3696 for (i
= 0; i
< modulesList
.length
; i
++) {
3697 item
= modulesList
[i
];
3699 toggleClass(item
.parentNode
, "checked", item
.checked
);
3700 } else if (checkbox
=== allCheckbox
&& checkbox
.checked
) {
3701 item
.checked
= false;
3702 removeClass(item
.parentNode
, "checked");
3704 dirty
= dirty
|| item
.checked
!== item
.defaultChecked
;
3706 selectedNames
.push(item
.parentNode
.textContent
);
3710 commit
.style
.display
= reset
.style
.display
= dirty
? "" : "none";
3711 moduleSearch
.placeholder
= selectedNames
.join(", ") || allCheckbox
.parentNode
.textContent
;
3712 moduleSearch
.title
= "Type to filter list. Current selection:\n" + (selectedNames
.join("\n") || allCheckbox
.parentNode
.textContent
);
3715 return moduleFilter
;
3718 function appendToolbar() {
3719 var toolbar
= id("qunit-testrunner-toolbar");
3722 toolbar
.appendChild(toolbarUrlConfigContainer());
3723 toolbar
.appendChild(toolbarModuleFilter());
3724 toolbar
.appendChild(toolbarLooseFilter());
3725 toolbar
.appendChild(document
$$1.createElement("div")).className
= "clearfix";
3729 function appendHeader() {
3730 var header
= id("qunit-header");
3733 header
.innerHTML
= "<a href='" + escapeText(unfilteredUrl
) + "'>" + header
.innerHTML
+ "</a> ";
3737 function appendBanner() {
3738 var banner
= id("qunit-banner");
3741 banner
.className
= "";
3745 function appendTestResults() {
3746 var tests
= id("qunit-tests"),
3747 result
= id("qunit-testresult"),
3751 result
.parentNode
.removeChild(result
);
3755 tests
.innerHTML
= "";
3756 result
= document
$$1.createElement("p");
3757 result
.id
= "qunit-testresult";
3758 result
.className
= "result";
3759 tests
.parentNode
.insertBefore(result
, tests
);
3760 result
.innerHTML
= "<div id=\"qunit-testresult-display\">Running...<br /> </div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
3761 controls
= id("qunit-testresult-controls");
3765 controls
.appendChild(abortTestsButton());
3769 function appendFilteredTest() {
3770 var testId
= QUnit
.config
.testId
;
3771 if (!testId
|| testId
.length
<= 0) {
3774 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId
.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl
) + "'>Run all tests</a></div>";
3777 function appendUserAgent() {
3778 var userAgent
= id("qunit-userAgent");
3781 userAgent
.innerHTML
= "";
3782 userAgent
.appendChild(document
$$1.createTextNode("QUnit " + QUnit
.version
+ "; " + navigator
.userAgent
));
3786 function appendInterface() {
3787 var qunit
= id("qunit");
3790 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>";
3795 appendTestResults();
3800 function appendTestsList(modules
) {
3801 var i
, l
, x
, z
, test
, moduleObj
;
3803 for (i
= 0, l
= modules
.length
; i
< l
; i
++) {
3804 moduleObj
= modules
[i
];
3806 for (x
= 0, z
= moduleObj
.tests
.length
; x
< z
; x
++) {
3807 test
= moduleObj
.tests
[x
];
3809 appendTest(test
.name
, test
.testId
, moduleObj
.name
);
3814 function appendTest(name
, testId
, moduleName
) {
3819 tests
= id("qunit-tests");
3825 title
= document
$$1.createElement("strong");
3826 title
.innerHTML
= getNameHtml(name
, moduleName
);
3828 rerunTrigger
= document
$$1.createElement("a");
3829 rerunTrigger
.innerHTML
= "Rerun";
3830 rerunTrigger
.href
= setUrl({ testId
: testId
});
3832 testBlock
= document
$$1.createElement("li");
3833 testBlock
.appendChild(title
);
3834 testBlock
.appendChild(rerunTrigger
);
3835 testBlock
.id
= "qunit-test-output-" + testId
;
3837 assertList
= document
$$1.createElement("ol");
3838 assertList
.className
= "qunit-assert-list";
3840 testBlock
.appendChild(assertList
);
3842 tests
.appendChild(testBlock
);
3845 // HTML Reporter initialization and load
3846 QUnit
.begin(function (details
) {
3847 var i
, moduleObj
, tests
;
3849 // Sort modules by name for the picker
3850 for (i
= 0; i
< details
.modules
.length
; i
++) {
3851 moduleObj
= details
.modules
[i
];
3852 if (moduleObj
.name
) {
3853 modulesList
.push(moduleObj
.name
);
3856 modulesList
.sort(function (a
, b
) {
3857 return a
.localeCompare(b
);
3860 // Initialize QUnit elements
3862 appendTestsList(details
.modules
);
3863 tests
= id("qunit-tests");
3864 if (tests
&& config
.hidepassed
) {
3865 addClass(tests
, "hidepass");
3869 QUnit
.done(function (details
) {
3870 var banner
= id("qunit-banner"),
3871 tests
= id("qunit-tests"),
3872 abortButton
= id("qunit-abort-tests-button"),
3873 totalTests
= stats
.passedTests
+ stats
.skippedTests
+ stats
.todoTests
+ stats
.failedTests
,
3874 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(""),
3879 // Update remaing tests to aborted
3880 if (abortButton
&& abortButton
.disabled
) {
3881 html
= "Tests aborted after " + details
.runtime
+ " milliseconds.";
3883 for (var i
= 0; i
< tests
.children
.length
; i
++) {
3884 test
= tests
.children
[i
];
3885 if (test
.className
=== "" || test
.className
=== "running") {
3886 test
.className
= "aborted";
3887 assertList
= test
.getElementsByTagName("ol")[0];
3888 assertLi
= document
$$1.createElement("li");
3889 assertLi
.className
= "fail";
3890 assertLi
.innerHTML
= "Test aborted.";
3891 assertList
.appendChild(assertLi
);
3896 if (banner
&& (!abortButton
|| abortButton
.disabled
=== false)) {
3897 banner
.className
= stats
.failedTests
? "qunit-fail" : "qunit-pass";
3901 abortButton
.parentNode
.removeChild(abortButton
);
3905 id("qunit-testresult-display").innerHTML
= html
;
3908 if (config
.altertitle
&& document
$$1.title
) {
3910 // Show ✖ for good, ✔ for bad suite result in title
3911 // use escape sequences in case file gets loaded with non-utf-8
3913 document
$$1.title
= [stats
.failedTests
? "\u2716" : "\u2714", document
$$1.title
.replace(/^[\u2714\u2716] /i, "")].join(" ");
3916 // Scroll back to top to show results
3917 if (config
.scrolltop
&& window
.scrollTo
) {
3918 window
.scrollTo(0, 0);
3922 function getNameHtml(name
, module
) {
3926 nameHtml
= "<span class='module-name'>" + escapeText(module
) + "</span>: ";
3929 nameHtml
+= "<span class='test-name'>" + escapeText(name
) + "</span>";
3934 QUnit
.testStart(function (details
) {
3935 var running
, testBlock
, bad
;
3937 testBlock
= id("qunit-test-output-" + details
.testId
);
3939 testBlock
.className
= "running";
3942 // Report later registered tests
3943 appendTest(details
.name
, details
.testId
, details
.module
);
3946 running
= id("qunit-testresult-display");
3948 bad
= QUnit
.config
.reorder
&& details
.previousFailure
;
3950 running
.innerHTML
= [bad
? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details
.name
, details
.module
)].join("");
3954 function stripHtml(string
) {
3956 // Strip tags, html entity and whitespaces
3957 return string
.replace(/<\/?[^>]+(>|$)/g, "").replace(/"/g, "").replace(/\s+/g, "");
3960 QUnit
.log(function (details
) {
3968 testItem
= id("qunit-test-output-" + details
.testId
);
3974 message
= escapeText(details
.message
) || (details
.result
? "okay" : "failed");
3975 message
= "<span class='test-message'>" + message
+ "</span>";
3976 message
+= "<span class='runtime'>@ " + details
.runtime
+ " ms</span>";
3978 // The pushFailure doesn't provide details.expected
3979 // when it calls, it's implicit to also not show expected and diff stuff
3980 // Also, we need to check details.expected existence, as it can exist and be undefined
3981 if (!details
.result
&& hasOwn
.call(details
, "expected")) {
3982 if (details
.negative
) {
3983 expected
= "NOT " + QUnit
.dump
.parse(details
.expected
);
3985 expected
= QUnit
.dump
.parse(details
.expected
);
3988 actual
= QUnit
.dump
.parse(details
.actual
);
3989 message
+= "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected
) + "</pre></td></tr>";
3991 if (actual
!== expected
) {
3993 message
+= "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual
) + "</pre></td></tr>";
3995 if (typeof details
.actual
=== "number" && typeof details
.expected
=== "number") {
3996 if (!isNaN(details
.actual
) && !isNaN(details
.expected
)) {
3998 diff
= details
.actual
- details
.expected
;
3999 diff
= (diff
> 0 ? "+" : "") + diff
;
4001 } else if (typeof details
.actual
!== "boolean" && typeof details
.expected
!== "boolean") {
4002 diff
= QUnit
.diff(expected
, actual
);
4004 // don't show diff if there is zero overlap
4005 showDiff
= stripHtml(diff
).length
!== stripHtml(expected
).length
+ stripHtml(actual
).length
;
4009 message
+= "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff
+ "</pre></td></tr>";
4011 } else if (expected
.indexOf("[object Array]") !== -1 || expected
.indexOf("[object Object]") !== -1) {
4012 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>";
4014 message
+= "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
4017 if (details
.source
) {
4018 message
+= "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details
.source
) + "</pre></td></tr>";
4021 message
+= "</table>";
4023 // This occurs when pushFailure is set and we have an extracted stack trace
4024 } else if (!details
.result
&& details
.source
) {
4025 message
+= "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details
.source
) + "</pre></td></tr>" + "</table>";
4028 assertList
= testItem
.getElementsByTagName("ol")[0];
4030 assertLi
= document
$$1.createElement("li");
4031 assertLi
.className
= details
.result
? "pass" : "fail";
4032 assertLi
.innerHTML
= message
;
4033 assertList
.appendChild(assertLi
);
4036 QUnit
.testDone(function (details
) {
4046 tests
= id("qunit-tests");
4052 testItem
= id("qunit-test-output-" + details
.testId
);
4054 assertList
= testItem
.getElementsByTagName("ol")[0];
4056 good
= details
.passed
;
4057 bad
= details
.failed
;
4059 // This test passed if it has no unexpected failed assertions
4060 var testPassed
= details
.failed
> 0 ? details
.todo
: !details
.todo
;
4064 // Collapse the passing tests
4065 addClass(assertList
, "qunit-collapsed");
4066 } else if (config
.collapse
) {
4067 if (!collapseNext
) {
4069 // Skip collapsing the first failing test
4070 collapseNext
= true;
4073 // Collapse remaining tests
4074 addClass(assertList
, "qunit-collapsed");
4078 // The testItem.firstChild is the test name
4079 testTitle
= testItem
.firstChild
;
4081 testCounts
= bad
? "<b class='failed'>" + bad
+ "</b>, " + "<b class='passed'>" + good
+ "</b>, " : "";
4083 testTitle
.innerHTML
+= " <b class='counts'>(" + testCounts
+ details
.assertions
.length
+ ")</b>";
4085 if (details
.skipped
) {
4086 stats
.skippedTests
++;
4088 testItem
.className
= "skipped";
4089 skipped
= document
$$1.createElement("em");
4090 skipped
.className
= "qunit-skipped-label";
4091 skipped
.innerHTML
= "skipped";
4092 testItem
.insertBefore(skipped
, testTitle
);
4094 addEvent(testTitle
, "click", function () {
4095 toggleClass(assertList
, "qunit-collapsed");
4098 testItem
.className
= testPassed
? "pass" : "fail";
4101 var todoLabel
= document
$$1.createElement("em");
4102 todoLabel
.className
= "qunit-todo-label";
4103 todoLabel
.innerHTML
= "todo";
4104 testItem
.className
+= " todo";
4105 testItem
.insertBefore(todoLabel
, testTitle
);
4108 time
= document
$$1.createElement("span");
4109 time
.className
= "runtime";
4110 time
.innerHTML
= details
.runtime
+ " ms";
4111 testItem
.insertBefore(time
, assertList
);
4114 stats
.failedTests
++;
4115 } else if (details
.todo
) {
4118 stats
.passedTests
++;
4122 // Show the source of the test when showing assertions
4123 if (details
.source
) {
4124 sourceName
= document
$$1.createElement("p");
4125 sourceName
.innerHTML
= "<strong>Source: </strong>" + details
.source
;
4126 addClass(sourceName
, "qunit-source");
4128 addClass(sourceName
, "qunit-collapsed");
4130 addEvent(testTitle
, "click", function () {
4131 toggleClass(sourceName
, "qunit-collapsed");
4133 testItem
.appendChild(sourceName
);
4137 // Avoid readyState issue with phantomjs
4139 var notPhantom = function (p
) {
4140 return !(p
&& p
.version
&& p
.version
.major
> 0);
4143 if (notPhantom
&& document
$$1.readyState
=== "complete") {
4146 addEvent(window
, "load", QUnit
.load
);
4149 // Wrap window.onerror. We will call the original window.onerror to see if
4150 // the existing handler fully handles the error; if not, we will call the
4151 // QUnit.onError function.
4152 var originalWindowOnError
= window
.onerror
;
4154 // Cover uncaught exceptions
4155 // Returning true will suppress the default browser handler,
4156 // returning false will let it run.
4157 window
.onerror = function (message
, fileName
, lineNumber
) {
4159 if (originalWindowOnError
) {
4160 for (var _len
= arguments
.length
, args
= Array(_len
> 3 ? _len
- 3 : 0), _key
= 3; _key
< _len
; _key
++) {
4161 args
[_key
- 3] = arguments
[_key
];
4164 ret
= originalWindowOnError
.call
.apply(originalWindowOnError
, [this, message
, fileName
, lineNumber
].concat(args
));
4167 // Treat return value as window.onerror itself does,
4168 // Only do our handling if not suppressed.
4173 lineNumber
: lineNumber
4176 ret
= QUnit
.onError(error
);
4182 // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
4183 window
.addEventListener("unhandledrejection", function (event
) {
4184 QUnit
.onUnhandledRejection(event
.reason
);
4189 * This file is a modified version of google-diff-match-patch's JavaScript implementation
4190 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
4191 * modifications are licensed as more fully set forth in LICENSE.txt.
4193 * The original source of google-diff-match-patch is attributable and licensed as follows:
4195 * Copyright 2006 Google Inc.
4196 * https://code.google.com/p/google-diff-match-patch/
4198 * Licensed under the Apache License, Version 2.0 (the "License");
4199 * you may not use this file except in compliance with the License.
4200 * You may obtain a copy of the License at
4202 * https://www.apache.org/licenses/LICENSE-2.0
4204 * Unless required by applicable law or agreed to in writing, software
4205 * distributed under the License is distributed on an "AS IS" BASIS,
4206 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4207 * See the License for the specific language governing permissions and
4208 * limitations under the License.
4211 * https://code.google.com/p/google-diff-match-patch/
4213 * Usage: QUnit.diff(expected, actual)
4216 QUnit
.diff = function () {
4217 function DiffMatchPatch() {}
4222 * The data structure representing a diff is an array of tuples:
4223 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
4224 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
4226 var DIFF_DELETE
= -1,
4231 * Find the differences between two texts. Simplifies the problem by stripping
4232 * any common prefix or suffix off the texts before diffing.
4233 * @param {string} text1 Old string to be diffed.
4234 * @param {string} text2 New string to be diffed.
4235 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
4236 * then don't run a line-level diff first to identify the changed areas.
4237 * Defaults to true, which does a faster, slightly less optimal diff.
4238 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4240 DiffMatchPatch
.prototype.DiffMain = function (text1
, text2
, optChecklines
) {
4241 var deadline
, checklines
, commonlength
, commonprefix
, commonsuffix
, diffs
;
4243 // The diff must be complete in up to 1 second.
4244 deadline
= new Date().getTime() + 1000;
4246 // Check for null inputs.
4247 if (text1
=== null || text2
=== null) {
4248 throw new Error("Null input. (DiffMain)");
4251 // Check for equality (speedup).
4252 if (text1
=== text2
) {
4254 return [[DIFF_EQUAL
, text1
]];
4259 if (typeof optChecklines
=== "undefined") {
4260 optChecklines
= true;
4263 checklines
= optChecklines
;
4265 // Trim off common prefix (speedup).
4266 commonlength
= this.diffCommonPrefix(text1
, text2
);
4267 commonprefix
= text1
.substring(0, commonlength
);
4268 text1
= text1
.substring(commonlength
);
4269 text2
= text2
.substring(commonlength
);
4271 // Trim off common suffix (speedup).
4272 commonlength
= this.diffCommonSuffix(text1
, text2
);
4273 commonsuffix
= text1
.substring(text1
.length
- commonlength
);
4274 text1
= text1
.substring(0, text1
.length
- commonlength
);
4275 text2
= text2
.substring(0, text2
.length
- commonlength
);
4277 // Compute the diff on the middle block.
4278 diffs
= this.diffCompute(text1
, text2
, checklines
, deadline
);
4280 // Restore the prefix and suffix.
4282 diffs
.unshift([DIFF_EQUAL
, commonprefix
]);
4285 diffs
.push([DIFF_EQUAL
, commonsuffix
]);
4287 this.diffCleanupMerge(diffs
);
4292 * Reduce the number of edits by eliminating operationally trivial equalities.
4293 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4295 DiffMatchPatch
.prototype.diffCleanupEfficiency = function (diffs
) {
4296 var changes
, equalities
, equalitiesLength
, lastequality
, pointer
, preIns
, preDel
, postIns
, postDel
;
4298 equalities
= []; // Stack of indices where equalities are found.
4299 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
4300 /** @type {?string} */
4301 lastequality
= null;
4303 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4304 pointer
= 0; // Index of current position.
4306 // Is there an insertion operation before the last equality.
4309 // Is there a deletion operation before the last equality.
4312 // Is there an insertion operation after the last equality.
4315 // Is there a deletion operation after the last equality.
4317 while (pointer
< diffs
.length
) {
4320 if (diffs
[pointer
][0] === DIFF_EQUAL
) {
4321 if (diffs
[pointer
][1].length
< 4 && (postIns
|| postDel
)) {
4324 equalities
[equalitiesLength
++] = pointer
;
4327 lastequality
= diffs
[pointer
][1];
4330 // Not a candidate, and can never become one.
4331 equalitiesLength
= 0;
4332 lastequality
= null;
4334 postIns
= postDel
= false;
4336 // An insertion or deletion.
4339 if (diffs
[pointer
][0] === DIFF_DELETE
) {
4346 * Five types to be split:
4347 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
4348 * <ins>A</ins>X<ins>C</ins><del>D</del>
4349 * <ins>A</ins><del>B</del>X<ins>C</ins>
4350 * <ins>A</del>X<ins>C</ins><del>D</del>
4351 * <ins>A</ins><del>B</del>X<del>C</del>
4353 if (lastequality
&& (preIns
&& preDel
&& postIns
&& postDel
|| lastequality
.length
< 2 && preIns
+ preDel
+ postIns
+ postDel
=== 3)) {
4355 // Duplicate record.
4356 diffs
.splice(equalities
[equalitiesLength
- 1], 0, [DIFF_DELETE
, lastequality
]);
4358 // Change second copy to insert.
4359 diffs
[equalities
[equalitiesLength
- 1] + 1][0] = DIFF_INSERT
;
4360 equalitiesLength
--; // Throw away the equality we just deleted;
4361 lastequality
= null;
4362 if (preIns
&& preDel
) {
4364 // No changes made which could affect previous entry, keep going.
4365 postIns
= postDel
= true;
4366 equalitiesLength
= 0;
4368 equalitiesLength
--; // Throw away the previous equality.
4369 pointer
= equalitiesLength
> 0 ? equalities
[equalitiesLength
- 1] : -1;
4370 postIns
= postDel
= false;
4379 this.diffCleanupMerge(diffs
);
4384 * Convert a diff array into a pretty HTML report.
4385 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4386 * @param {integer} string to be beautified.
4387 * @return {string} HTML representation.
4389 DiffMatchPatch
.prototype.diffPrettyHtml = function (diffs
) {
4394 for (x
= 0; x
< diffs
.length
; x
++) {
4395 op
= diffs
[x
][0]; // Operation (insert, delete, equal)
4396 data
= diffs
[x
][1]; // Text of change.
4399 html
[x
] = "<ins>" + escapeText(data
) + "</ins>";
4402 html
[x
] = "<del>" + escapeText(data
) + "</del>";
4405 html
[x
] = "<span>" + escapeText(data
) + "</span>";
4409 return html
.join("");
4413 * Determine the common prefix of two strings.
4414 * @param {string} text1 First string.
4415 * @param {string} text2 Second string.
4416 * @return {number} The number of characters common to the start of each
4419 DiffMatchPatch
.prototype.diffCommonPrefix = function (text1
, text2
) {
4420 var pointermid
, pointermax
, pointermin
, pointerstart
;
4422 // Quick check for common null cases.
4423 if (!text1
|| !text2
|| text1
.charAt(0) !== text2
.charAt(0)) {
4428 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4430 pointermax
= Math
.min(text1
.length
, text2
.length
);
4431 pointermid
= pointermax
;
4433 while (pointermin
< pointermid
) {
4434 if (text1
.substring(pointerstart
, pointermid
) === text2
.substring(pointerstart
, pointermid
)) {
4435 pointermin
= pointermid
;
4436 pointerstart
= pointermin
;
4438 pointermax
= pointermid
;
4440 pointermid
= Math
.floor((pointermax
- pointermin
) / 2 + pointermin
);
4446 * Determine the common suffix of two strings.
4447 * @param {string} text1 First string.
4448 * @param {string} text2 Second string.
4449 * @return {number} The number of characters common to the end of each string.
4451 DiffMatchPatch
.prototype.diffCommonSuffix = function (text1
, text2
) {
4452 var pointermid
, pointermax
, pointermin
, pointerend
;
4454 // Quick check for common null cases.
4455 if (!text1
|| !text2
|| text1
.charAt(text1
.length
- 1) !== text2
.charAt(text2
.length
- 1)) {
4460 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4462 pointermax
= Math
.min(text1
.length
, text2
.length
);
4463 pointermid
= pointermax
;
4465 while (pointermin
< pointermid
) {
4466 if (text1
.substring(text1
.length
- pointermid
, text1
.length
- pointerend
) === text2
.substring(text2
.length
- pointermid
, text2
.length
- pointerend
)) {
4467 pointermin
= pointermid
;
4468 pointerend
= pointermin
;
4470 pointermax
= pointermid
;
4472 pointermid
= Math
.floor((pointermax
- pointermin
) / 2 + pointermin
);
4478 * Find the differences between two texts. Assumes that the texts do not
4479 * have any common prefix or suffix.
4480 * @param {string} text1 Old string to be diffed.
4481 * @param {string} text2 New string to be diffed.
4482 * @param {boolean} checklines Speedup flag. If false, then don't run a
4483 * line-level diff first to identify the changed areas.
4484 * If true, then run a faster, slightly less optimal diff.
4485 * @param {number} deadline Time when the diff should be complete by.
4486 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4489 DiffMatchPatch
.prototype.diffCompute = function (text1
, text2
, checklines
, deadline
) {
4490 var diffs
, longtext
, shorttext
, i
, hm
, text1A
, text2A
, text1B
, text2B
, midCommon
, diffsA
, diffsB
;
4494 // Just add some text (speedup).
4495 return [[DIFF_INSERT
, text2
]];
4500 // Just delete some text (speedup).
4501 return [[DIFF_DELETE
, text1
]];
4504 longtext
= text1
.length
> text2
.length
? text1
: text2
;
4505 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
4506 i
= longtext
.indexOf(shorttext
);
4509 // Shorter text is inside the longer text (speedup).
4510 diffs
= [[DIFF_INSERT
, longtext
.substring(0, i
)], [DIFF_EQUAL
, shorttext
], [DIFF_INSERT
, longtext
.substring(i
+ shorttext
.length
)]];
4512 // Swap insertions for deletions if diff is reversed.
4513 if (text1
.length
> text2
.length
) {
4514 diffs
[0][0] = diffs
[2][0] = DIFF_DELETE
;
4519 if (shorttext
.length
=== 1) {
4521 // Single character string.
4522 // After the previous speedup, the character can't be an equality.
4523 return [[DIFF_DELETE
, text1
], [DIFF_INSERT
, text2
]];
4526 // Check to see if the problem can be split in two.
4527 hm
= this.diffHalfMatch(text1
, text2
);
4530 // A half-match was found, sort out the return data.
4537 // Send both pairs off for separate processing.
4538 diffsA
= this.DiffMain(text1A
, text2A
, checklines
, deadline
);
4539 diffsB
= this.DiffMain(text1B
, text2B
, checklines
, deadline
);
4541 // Merge the results.
4542 return diffsA
.concat([[DIFF_EQUAL
, midCommon
]], diffsB
);
4545 if (checklines
&& text1
.length
> 100 && text2
.length
> 100) {
4546 return this.diffLineMode(text1
, text2
, deadline
);
4549 return this.diffBisect(text1
, text2
, deadline
);
4553 * Do the two texts share a substring which is at least half the length of the
4555 * This speedup can produce non-minimal diffs.
4556 * @param {string} text1 First string.
4557 * @param {string} text2 Second string.
4558 * @return {Array.<string>} Five element Array, containing the prefix of
4559 * text1, the suffix of text1, the prefix of text2, the suffix of
4560 * text2 and the common middle. Or null if there was no match.
4563 DiffMatchPatch
.prototype.diffHalfMatch = function (text1
, text2
) {
4564 var longtext
, shorttext
, dmp
, text1A
, text2B
, text2A
, text1B
, midCommon
, hm1
, hm2
, hm
;
4566 longtext
= text1
.length
> text2
.length
? text1
: text2
;
4567 shorttext
= text1
.length
> text2
.length
? text2
: text1
;
4568 if (longtext
.length
< 4 || shorttext
.length
* 2 < longtext
.length
) {
4569 return null; // Pointless.
4571 dmp
= this; // 'this' becomes 'window' in a closure.
4574 * Does a substring of shorttext exist within longtext such that the substring
4575 * is at least half the length of longtext?
4576 * Closure, but does not reference any external variables.
4577 * @param {string} longtext Longer string.
4578 * @param {string} shorttext Shorter string.
4579 * @param {number} i Start index of quarter length substring within longtext.
4580 * @return {Array.<string>} Five element Array, containing the prefix of
4581 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
4582 * of shorttext and the common middle. Or null if there was no match.
4585 function diffHalfMatchI(longtext
, shorttext
, i
) {
4586 var seed
, j
, bestCommon
, prefixLength
, suffixLength
, bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
;
4588 // Start with a 1/4 length substring at position i as a seed.
4589 seed
= longtext
.substring(i
, i
+ Math
.floor(longtext
.length
/ 4));
4592 while ((j
= shorttext
.indexOf(seed
, j
+ 1)) !== -1) {
4593 prefixLength
= dmp
.diffCommonPrefix(longtext
.substring(i
), shorttext
.substring(j
));
4594 suffixLength
= dmp
.diffCommonSuffix(longtext
.substring(0, i
), shorttext
.substring(0, j
));
4595 if (bestCommon
.length
< suffixLength
+ prefixLength
) {
4596 bestCommon
= shorttext
.substring(j
- suffixLength
, j
) + shorttext
.substring(j
, j
+ prefixLength
);
4597 bestLongtextA
= longtext
.substring(0, i
- suffixLength
);
4598 bestLongtextB
= longtext
.substring(i
+ prefixLength
);
4599 bestShorttextA
= shorttext
.substring(0, j
- suffixLength
);
4600 bestShorttextB
= shorttext
.substring(j
+ prefixLength
);
4603 if (bestCommon
.length
* 2 >= longtext
.length
) {
4604 return [bestLongtextA
, bestLongtextB
, bestShorttextA
, bestShorttextB
, bestCommon
];
4610 // First check if the second quarter is the seed for a half-match.
4611 hm1
= diffHalfMatchI(longtext
, shorttext
, Math
.ceil(longtext
.length
/ 4));
4613 // Check again based on the third quarter.
4614 hm2
= diffHalfMatchI(longtext
, shorttext
, Math
.ceil(longtext
.length
/ 2));
4623 // Both matched. Select the longest.
4624 hm
= hm1
[4].length
> hm2
[4].length
? hm1
: hm2
;
4627 // A half-match was found, sort out the return data.
4628 if (text1
.length
> text2
.length
) {
4640 return [text1A
, text1B
, text2A
, text2B
, midCommon
];
4644 * Do a quick line-level diff on both strings, then rediff the parts for
4646 * This speedup can produce non-minimal diffs.
4647 * @param {string} text1 Old string to be diffed.
4648 * @param {string} text2 New string to be diffed.
4649 * @param {number} deadline Time when the diff should be complete by.
4650 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4653 DiffMatchPatch
.prototype.diffLineMode = function (text1
, text2
, deadline
) {
4654 var a
, diffs
, linearray
, pointer
, countInsert
, countDelete
, textInsert
, textDelete
, j
;
4656 // Scan the text on a line-by-line basis first.
4657 a
= this.diffLinesToChars(text1
, text2
);
4660 linearray
= a
.lineArray
;
4662 diffs
= this.DiffMain(text1
, text2
, false, deadline
);
4664 // Convert the diff back to original text.
4665 this.diffCharsToLines(diffs
, linearray
);
4667 // Eliminate freak matches (e.g. blank lines)
4668 this.diffCleanupSemantic(diffs
);
4670 // Rediff any replacement blocks, this time character-by-character.
4671 // Add a dummy entry at the end.
4672 diffs
.push([DIFF_EQUAL
, ""]);
4678 while (pointer
< diffs
.length
) {
4679 switch (diffs
[pointer
][0]) {
4682 textInsert
+= diffs
[pointer
][1];
4686 textDelete
+= diffs
[pointer
][1];
4690 // Upon reaching an equality, check for prior redundancies.
4691 if (countDelete
>= 1 && countInsert
>= 1) {
4693 // Delete the offending records and add the merged ones.
4694 diffs
.splice(pointer
- countDelete
- countInsert
, countDelete
+ countInsert
);
4695 pointer
= pointer
- countDelete
- countInsert
;
4696 a
= this.DiffMain(textDelete
, textInsert
, false, deadline
);
4697 for (j
= a
.length
- 1; j
>= 0; j
--) {
4698 diffs
.splice(pointer
, 0, a
[j
]);
4700 pointer
= pointer
+ a
.length
;
4710 diffs
.pop(); // Remove the dummy entry at the end.
4716 * Find the 'middle snake' of a diff, split the problem in two
4717 * and return the recursively constructed diff.
4718 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
4719 * @param {string} text1 Old string to be diffed.
4720 * @param {string} text2 New string to be diffed.
4721 * @param {number} deadline Time at which to bail if not yet complete.
4722 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4725 DiffMatchPatch
.prototype.diffBisect = function (text1
, text2
, deadline
) {
4726 var text1Length
, text2Length
, maxD
, vOffset
, vLength
, v1
, v2
, x
, delta
, front
, k1start
, k1end
, k2start
, k2end
, k2Offset
, k1Offset
, x1
, x2
, y1
, y2
, d
, k1
, k2
;
4728 // Cache the text lengths to prevent multiple calls.
4729 text1Length
= text1
.length
;
4730 text2Length
= text2
.length
;
4731 maxD
= Math
.ceil((text1Length
+ text2Length
) / 2);
4734 v1
= new Array(vLength
);
4735 v2
= new Array(vLength
);
4737 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
4738 // integers and undefined.
4739 for (x
= 0; x
< vLength
; x
++) {
4743 v1
[vOffset
+ 1] = 0;
4744 v2
[vOffset
+ 1] = 0;
4745 delta
= text1Length
- text2Length
;
4747 // If the total number of characters is odd, then the front path will collide
4748 // with the reverse path.
4749 front
= delta
% 2 !== 0;
4751 // Offsets for start and end of k loop.
4752 // Prevents mapping of space beyond the grid.
4757 for (d
= 0; d
< maxD
; d
++) {
4759 // Bail out if deadline is reached.
4760 if (new Date().getTime() > deadline
) {
4764 // Walk the front path one step.
4765 for (k1
= -d
+ k1start
; k1
<= d
- k1end
; k1
+= 2) {
4766 k1Offset
= vOffset
+ k1
;
4767 if (k1
=== -d
|| k1
!== d
&& v1
[k1Offset
- 1] < v1
[k1Offset
+ 1]) {
4768 x1
= v1
[k1Offset
+ 1];
4770 x1
= v1
[k1Offset
- 1] + 1;
4773 while (x1
< text1Length
&& y1
< text2Length
&& text1
.charAt(x1
) === text2
.charAt(y1
)) {
4778 if (x1
> text1Length
) {
4780 // Ran off the right of the graph.
4782 } else if (y1
> text2Length
) {
4784 // Ran off the bottom of the graph.
4787 k2Offset
= vOffset
+ delta
- k1
;
4788 if (k2Offset
>= 0 && k2Offset
< vLength
&& v2
[k2Offset
] !== -1) {
4790 // Mirror x2 onto top-left coordinate system.
4791 x2
= text1Length
- v2
[k2Offset
];
4794 // Overlap detected.
4795 return this.diffBisectSplit(text1
, text2
, x1
, y1
, deadline
);
4801 // Walk the reverse path one step.
4802 for (k2
= -d
+ k2start
; k2
<= d
- k2end
; k2
+= 2) {
4803 k2Offset
= vOffset
+ k2
;
4804 if (k2
=== -d
|| k2
!== d
&& v2
[k2Offset
- 1] < v2
[k2Offset
+ 1]) {
4805 x2
= v2
[k2Offset
+ 1];
4807 x2
= v2
[k2Offset
- 1] + 1;
4810 while (x2
< text1Length
&& y2
< text2Length
&& text1
.charAt(text1Length
- x2
- 1) === text2
.charAt(text2Length
- y2
- 1)) {
4815 if (x2
> text1Length
) {
4817 // Ran off the left of the graph.
4819 } else if (y2
> text2Length
) {
4821 // Ran off the top of the graph.
4823 } else if (!front
) {
4824 k1Offset
= vOffset
+ delta
- k2
;
4825 if (k1Offset
>= 0 && k1Offset
< vLength
&& v1
[k1Offset
] !== -1) {
4827 y1
= vOffset
+ x1
- k1Offset
;
4829 // Mirror x2 onto top-left coordinate system.
4830 x2
= text1Length
- x2
;
4833 // Overlap detected.
4834 return this.diffBisectSplit(text1
, text2
, x1
, y1
, deadline
);
4841 // Diff took too long and hit the deadline or
4842 // number of diffs equals number of characters, no commonality at all.
4843 return [[DIFF_DELETE
, text1
], [DIFF_INSERT
, text2
]];
4847 * Given the location of the 'middle snake', split the diff in two parts
4849 * @param {string} text1 Old string to be diffed.
4850 * @param {string} text2 New string to be diffed.
4851 * @param {number} x Index of split point in text1.
4852 * @param {number} y Index of split point in text2.
4853 * @param {number} deadline Time at which to bail if not yet complete.
4854 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4857 DiffMatchPatch
.prototype.diffBisectSplit = function (text1
, text2
, x
, y
, deadline
) {
4858 var text1a
, text1b
, text2a
, text2b
, diffs
, diffsb
;
4859 text1a
= text1
.substring(0, x
);
4860 text2a
= text2
.substring(0, y
);
4861 text1b
= text1
.substring(x
);
4862 text2b
= text2
.substring(y
);
4864 // Compute both diffs serially.
4865 diffs
= this.DiffMain(text1a
, text2a
, false, deadline
);
4866 diffsb
= this.DiffMain(text1b
, text2b
, false, deadline
);
4868 return diffs
.concat(diffsb
);
4872 * Reduce the number of edits by eliminating semantically trivial equalities.
4873 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4875 DiffMatchPatch
.prototype.diffCleanupSemantic = function (diffs
) {
4876 var changes
, equalities
, equalitiesLength
, lastequality
, pointer
, lengthInsertions2
, lengthDeletions2
, lengthInsertions1
, lengthDeletions1
, deletion
, insertion
, overlapLength1
, overlapLength2
;
4878 equalities
= []; // Stack of indices where equalities are found.
4879 equalitiesLength
= 0; // Keeping our own length var is faster in JS.
4880 /** @type {?string} */
4881 lastequality
= null;
4883 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4884 pointer
= 0; // Index of current position.
4886 // Number of characters that changed prior to the equality.
4887 lengthInsertions1
= 0;
4888 lengthDeletions1
= 0;
4890 // Number of characters that changed after the equality.
4891 lengthInsertions2
= 0;
4892 lengthDeletions2
= 0;
4893 while (pointer
< diffs
.length
) {
4894 if (diffs
[pointer
][0] === DIFF_EQUAL
) {
4896 equalities
[equalitiesLength
++] = pointer
;
4897 lengthInsertions1
= lengthInsertions2
;
4898 lengthDeletions1
= lengthDeletions2
;
4899 lengthInsertions2
= 0;
4900 lengthDeletions2
= 0;
4901 lastequality
= diffs
[pointer
][1];
4903 // An insertion or deletion.
4904 if (diffs
[pointer
][0] === DIFF_INSERT
) {
4905 lengthInsertions2
+= diffs
[pointer
][1].length
;
4907 lengthDeletions2
+= diffs
[pointer
][1].length
;
4910 // Eliminate an equality that is smaller or equal to the edits on both
4912 if (lastequality
&& lastequality
.length
<= Math
.max(lengthInsertions1
, lengthDeletions1
) && lastequality
.length
<= Math
.max(lengthInsertions2
, lengthDeletions2
)) {
4914 // Duplicate record.
4915 diffs
.splice(equalities
[equalitiesLength
- 1], 0, [DIFF_DELETE
, lastequality
]);
4917 // Change second copy to insert.
4918 diffs
[equalities
[equalitiesLength
- 1] + 1][0] = DIFF_INSERT
;
4920 // Throw away the equality we just deleted.
4923 // Throw away the previous equality (it needs to be reevaluated).
4925 pointer
= equalitiesLength
> 0 ? equalities
[equalitiesLength
- 1] : -1;
4927 // Reset the counters.
4928 lengthInsertions1
= 0;
4929 lengthDeletions1
= 0;
4930 lengthInsertions2
= 0;
4931 lengthDeletions2
= 0;
4932 lastequality
= null;
4939 // Normalize the diff.
4941 this.diffCleanupMerge(diffs
);
4944 // Find any overlaps between deletions and insertions.
4945 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
4946 // -> <del>abc</del>xxx<ins>def</ins>
4947 // e.g: <del>xxxabc</del><ins>defxxx</ins>
4948 // -> <ins>def</ins>xxx<del>abc</del>
4949 // Only extract an overlap if it is as big as the edit ahead or behind it.
4951 while (pointer
< diffs
.length
) {
4952 if (diffs
[pointer
- 1][0] === DIFF_DELETE
&& diffs
[pointer
][0] === DIFF_INSERT
) {
4953 deletion
= diffs
[pointer
- 1][1];
4954 insertion
= diffs
[pointer
][1];
4955 overlapLength1
= this.diffCommonOverlap(deletion
, insertion
);
4956 overlapLength2
= this.diffCommonOverlap(insertion
, deletion
);
4957 if (overlapLength1
>= overlapLength2
) {
4958 if (overlapLength1
>= deletion
.length
/ 2 || overlapLength1
>= insertion
.length
/ 2) {
4960 // Overlap found. Insert an equality and trim the surrounding edits.
4961 diffs
.splice(pointer
, 0, [DIFF_EQUAL
, insertion
.substring(0, overlapLength1
)]);
4962 diffs
[pointer
- 1][1] = deletion
.substring(0, deletion
.length
- overlapLength1
);
4963 diffs
[pointer
+ 1][1] = insertion
.substring(overlapLength1
);
4967 if (overlapLength2
>= deletion
.length
/ 2 || overlapLength2
>= insertion
.length
/ 2) {
4969 // Reverse overlap found.
4970 // Insert an equality and swap and trim the surrounding edits.
4971 diffs
.splice(pointer
, 0, [DIFF_EQUAL
, deletion
.substring(0, overlapLength2
)]);
4973 diffs
[pointer
- 1][0] = DIFF_INSERT
;
4974 diffs
[pointer
- 1][1] = insertion
.substring(0, insertion
.length
- overlapLength2
);
4975 diffs
[pointer
+ 1][0] = DIFF_DELETE
;
4976 diffs
[pointer
+ 1][1] = deletion
.substring(overlapLength2
);
4987 * Determine if the suffix of one string is the prefix of another.
4988 * @param {string} text1 First string.
4989 * @param {string} text2 Second string.
4990 * @return {number} The number of characters common to the end of the first
4991 * string and the start of the second string.
4994 DiffMatchPatch
.prototype.diffCommonOverlap = function (text1
, text2
) {
4995 var text1Length
, text2Length
, textLength
, best
, length
, pattern
, found
;
4997 // Cache the text lengths to prevent multiple calls.
4998 text1Length
= text1
.length
;
4999 text2Length
= text2
.length
;
5001 // Eliminate the null case.
5002 if (text1Length
=== 0 || text2Length
=== 0) {
5006 // Truncate the longer string.
5007 if (text1Length
> text2Length
) {
5008 text1
= text1
.substring(text1Length
- text2Length
);
5009 } else if (text1Length
< text2Length
) {
5010 text2
= text2
.substring(0, text1Length
);
5012 textLength
= Math
.min(text1Length
, text2Length
);
5014 // Quick check for the worst case.
5015 if (text1
=== text2
) {
5019 // Start by looking for a single character match
5020 // and increase length until no match is found.
5021 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
5025 pattern
= text1
.substring(textLength
- length
);
5026 found
= text2
.indexOf(pattern
);
5031 if (found
=== 0 || text1
.substring(textLength
- length
) === text2
.substring(0, length
)) {
5039 * Split two texts into an array of strings. Reduce the texts to a string of
5040 * hashes where each Unicode character represents one line.
5041 * @param {string} text1 First string.
5042 * @param {string} text2 Second string.
5043 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
5044 * An object containing the encoded text1, the encoded text2 and
5045 * the array of unique strings.
5046 * The zeroth element of the array of unique strings is intentionally blank.
5049 DiffMatchPatch
.prototype.diffLinesToChars = function (text1
, text2
) {
5050 var lineArray
, lineHash
, chars1
, chars2
;
5051 lineArray
= []; // E.g. lineArray[4] === 'Hello\n'
5052 lineHash
= {}; // E.g. lineHash['Hello\n'] === 4
5054 // '\x00' is a valid character, but various debuggers don't like it.
5055 // So we'll insert a junk entry to avoid generating a null character.
5059 * Split a text into an array of strings. Reduce the texts to a string of
5060 * hashes where each Unicode character represents one line.
5061 * Modifies linearray and linehash through being a closure.
5062 * @param {string} text String to encode.
5063 * @return {string} Encoded string.
5066 function diffLinesToCharsMunge(text
) {
5067 var chars
, lineStart
, lineEnd
, lineArrayLength
, line
;
5070 // Walk the text, pulling out a substring for each line.
5071 // text.split('\n') would would temporarily double our memory footprint.
5072 // Modifying text would create many large strings to garbage collect.
5076 // Keeping our own length variable is faster than looking it up.
5077 lineArrayLength
= lineArray
.length
;
5078 while (lineEnd
< text
.length
- 1) {
5079 lineEnd
= text
.indexOf("\n", lineStart
);
5080 if (lineEnd
=== -1) {
5081 lineEnd
= text
.length
- 1;
5083 line
= text
.substring(lineStart
, lineEnd
+ 1);
5084 lineStart
= lineEnd
+ 1;
5086 var lineHashExists
= lineHash
.hasOwnProperty
? lineHash
.hasOwnProperty(line
) : lineHash
[line
] !== undefined;
5088 if (lineHashExists
) {
5089 chars
+= String
.fromCharCode(lineHash
[line
]);
5091 chars
+= String
.fromCharCode(lineArrayLength
);
5092 lineHash
[line
] = lineArrayLength
;
5093 lineArray
[lineArrayLength
++] = line
;
5099 chars1
= diffLinesToCharsMunge(text1
);
5100 chars2
= diffLinesToCharsMunge(text2
);
5104 lineArray
: lineArray
5109 * Rehydrate the text in a diff from a string of line hashes to real lines of
5111 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5112 * @param {!Array.<string>} lineArray Array of unique strings.
5115 DiffMatchPatch
.prototype.diffCharsToLines = function (diffs
, lineArray
) {
5116 var x
, chars
, text
, y
;
5117 for (x
= 0; x
< diffs
.length
; x
++) {
5118 chars
= diffs
[x
][1];
5120 for (y
= 0; y
< chars
.length
; y
++) {
5121 text
[y
] = lineArray
[chars
.charCodeAt(y
)];
5123 diffs
[x
][1] = text
.join("");
5128 * Reorder and merge like edit sections. Merge equalities.
5129 * Any edit section can move as long as it doesn't cross an equality.
5130 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5132 DiffMatchPatch
.prototype.diffCleanupMerge = function (diffs
) {
5133 var pointer
, countDelete
, countInsert
, textInsert
, textDelete
, commonlength
, changes
, diffPointer
, position
;
5134 diffs
.push([DIFF_EQUAL
, ""]); // Add a dummy entry at the end.
5141 while (pointer
< diffs
.length
) {
5142 switch (diffs
[pointer
][0]) {
5145 textInsert
+= diffs
[pointer
][1];
5150 textDelete
+= diffs
[pointer
][1];
5155 // Upon reaching an equality, check for prior redundancies.
5156 if (countDelete
+ countInsert
> 1) {
5157 if (countDelete
!== 0 && countInsert
!== 0) {
5159 // Factor out any common prefixes.
5160 commonlength
= this.diffCommonPrefix(textInsert
, textDelete
);
5161 if (commonlength
!== 0) {
5162 if (pointer
- countDelete
- countInsert
> 0 && diffs
[pointer
- countDelete
- countInsert
- 1][0] === DIFF_EQUAL
) {
5163 diffs
[pointer
- countDelete
- countInsert
- 1][1] += textInsert
.substring(0, commonlength
);
5165 diffs
.splice(0, 0, [DIFF_EQUAL
, textInsert
.substring(0, commonlength
)]);
5168 textInsert
= textInsert
.substring(commonlength
);
5169 textDelete
= textDelete
.substring(commonlength
);
5172 // Factor out any common suffixies.
5173 commonlength
= this.diffCommonSuffix(textInsert
, textDelete
);
5174 if (commonlength
!== 0) {
5175 diffs
[pointer
][1] = textInsert
.substring(textInsert
.length
- commonlength
) + diffs
[pointer
][1];
5176 textInsert
= textInsert
.substring(0, textInsert
.length
- commonlength
);
5177 textDelete
= textDelete
.substring(0, textDelete
.length
- commonlength
);
5181 // Delete the offending records and add the merged ones.
5182 if (countDelete
=== 0) {
5183 diffs
.splice(pointer
- countInsert
, countDelete
+ countInsert
, [DIFF_INSERT
, textInsert
]);
5184 } else if (countInsert
=== 0) {
5185 diffs
.splice(pointer
- countDelete
, countDelete
+ countInsert
, [DIFF_DELETE
, textDelete
]);
5187 diffs
.splice(pointer
- countDelete
- countInsert
, countDelete
+ countInsert
, [DIFF_DELETE
, textDelete
], [DIFF_INSERT
, textInsert
]);
5189 pointer
= pointer
- countDelete
- countInsert
+ (countDelete
? 1 : 0) + (countInsert
? 1 : 0) + 1;
5190 } else if (pointer
!== 0 && diffs
[pointer
- 1][0] === DIFF_EQUAL
) {
5192 // Merge this equality with the previous one.
5193 diffs
[pointer
- 1][1] += diffs
[pointer
][1];
5194 diffs
.splice(pointer
, 1);
5205 if (diffs
[diffs
.length
- 1][1] === "") {
5206 diffs
.pop(); // Remove the dummy entry at the end.
5209 // Second pass: look for single edits surrounded on both sides by equalities
5210 // which can be shifted sideways to eliminate an equality.
5211 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
5215 // Intentionally ignore the first and last element (don't need checking).
5216 while (pointer
< diffs
.length
- 1) {
5217 if (diffs
[pointer
- 1][0] === DIFF_EQUAL
&& diffs
[pointer
+ 1][0] === DIFF_EQUAL
) {
5219 diffPointer
= diffs
[pointer
][1];
5220 position
= diffPointer
.substring(diffPointer
.length
- diffs
[pointer
- 1][1].length
);
5222 // This is a single edit surrounded by equalities.
5223 if (position
=== diffs
[pointer
- 1][1]) {
5225 // Shift the edit over the previous equality.
5226 diffs
[pointer
][1] = diffs
[pointer
- 1][1] + diffs
[pointer
][1].substring(0, diffs
[pointer
][1].length
- diffs
[pointer
- 1][1].length
);
5227 diffs
[pointer
+ 1][1] = diffs
[pointer
- 1][1] + diffs
[pointer
+ 1][1];
5228 diffs
.splice(pointer
- 1, 1);
5230 } else if (diffPointer
.substring(0, diffs
[pointer
+ 1][1].length
) === diffs
[pointer
+ 1][1]) {
5232 // Shift the edit over the next equality.
5233 diffs
[pointer
- 1][1] += diffs
[pointer
+ 1][1];
5234 diffs
[pointer
][1] = diffs
[pointer
][1].substring(diffs
[pointer
+ 1][1].length
) + diffs
[pointer
+ 1][1];
5235 diffs
.splice(pointer
+ 1, 1);
5242 // If shifts were made, the diff needs reordering and another shift sweep.
5244 this.diffCleanupMerge(diffs
);
5248 return function (o
, n
) {
5249 var diff
, output
, text
;
5250 diff
= new DiffMatchPatch();
5251 output
= diff
.DiffMain(o
, n
);
5252 diff
.diffCleanupEfficiency(output
);
5253 text
= diff
.diffPrettyHtml(output
);
5259 }((function() { return this; }())));