Bump qunit from 2.6.0 to 2.6.2
[lhc/web/wiklou.git] / resources / lib / qunitjs / qunit.js
1 /*!
2 * QUnit 2.6.2
3 * https://qunitjs.com/
4 *
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * https://jquery.org/license
8 *
9 * Date: 2018-08-19T19:37Z
10 */
11 (function (global$1) {
12 'use strict';
13
14 global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1;
15
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;
21
22 var document = window && window.document;
23 var navigator = window && window.navigator;
24
25 var localSessionStorage = function () {
26 var x = "qunit-test-string";
27 try {
28 global$1.sessionStorage.setItem(x, x);
29 global$1.sessionStorage.removeItem(x);
30 return global$1.sessionStorage;
31 } catch (e) {
32 return undefined;
33 }
34 }();
35
36 var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
37 return typeof obj;
38 } : function (obj) {
39 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
40 };
41
42
43
44
45
46
47
48
49
50
51
52 var classCallCheck = function (instance, Constructor) {
53 if (!(instance instanceof Constructor)) {
54 throw new TypeError("Cannot call a class as a function");
55 }
56 };
57
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);
66 }
67 }
68
69 return function (Constructor, protoProps, staticProps) {
70 if (protoProps) defineProperties(Constructor.prototype, protoProps);
71 if (staticProps) defineProperties(Constructor, staticProps);
72 return Constructor;
73 };
74 }();
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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];
119
120 return arr2;
121 } else {
122 return Array.from(arr);
123 }
124 };
125
126 var toString = Object.prototype.toString;
127 var hasOwn = Object.prototype.hasOwnProperty;
128 var now = Date.now || function () {
129 return new Date().getTime();
130 };
131
132 var defined = {
133 document: window && window.document !== undefined,
134 setTimeout: setTimeout !== undefined
135 };
136
137 // Returns a new Array with the elements that are in a but not in b
138 function diff(a, b) {
139 var i,
140 j,
141 result = a.slice();
142
143 for (i = 0; i < result.length; i++) {
144 for (j = 0; j < b.length; j++) {
145 if (result[i] === b[j]) {
146 result.splice(i, 1);
147 i--;
148 break;
149 }
150 }
151 }
152 return result;
153 }
154
155 /**
156 * Determines whether an element exists in a given array or not.
157 *
158 * @method inArray
159 * @param {Any} elem
160 * @param {Array} array
161 * @return {Boolean}
162 */
163 function inArray(elem, array) {
164 return array.indexOf(elem) !== -1;
165 }
166
167 /**
168 * Makes a clone of an object using only Array or Object as base,
169 * and copies over the own enumerable properties.
170 *
171 * @param {Object} obj
172 * @return {Object} New object with only the own properties (recursively).
173 */
174 function objectValues(obj) {
175 var key,
176 val,
177 vals = is("array", obj) ? [] : {};
178 for (key in obj) {
179 if (hasOwn.call(obj, key)) {
180 val = obj[key];
181 vals[key] = val === Object(val) ? objectValues(val) : val;
182 }
183 }
184 return vals;
185 }
186
187 function extend(a, b, undefOnly) {
188 for (var prop in b) {
189 if (hasOwn.call(b, prop)) {
190 if (b[prop] === undefined) {
191 delete a[prop];
192 } else if (!(undefOnly && typeof a[prop] !== "undefined")) {
193 a[prop] = b[prop];
194 }
195 }
196 }
197
198 return a;
199 }
200
201 function objectType(obj) {
202 if (typeof obj === "undefined") {
203 return "undefined";
204 }
205
206 // Consider: typeof null === object
207 if (obj === null) {
208 return "null";
209 }
210
211 var match = toString.call(obj).match(/^\[object\s(.*)\]$/),
212 type = match && match[1];
213
214 switch (type) {
215 case "Number":
216 if (isNaN(obj)) {
217 return "nan";
218 }
219 return "number";
220 case "String":
221 case "Boolean":
222 case "Array":
223 case "Set":
224 case "Map":
225 case "Date":
226 case "RegExp":
227 case "Function":
228 case "Symbol":
229 return type.toLowerCase();
230 default:
231 return typeof obj === "undefined" ? "undefined" : _typeof(obj);
232 }
233 }
234
235 // Safe object type checking
236 function is(type, obj) {
237 return objectType(obj) === type;
238 }
239
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;
244 var hash = 0;
245
246 for (var i = 0; i < str.length; i++) {
247 hash = (hash << 5) - hash + str.charCodeAt(i);
248 hash |= 0;
249 }
250
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;
256 }
257
258 return hex.slice(-8);
259 }
260
261 // Test for equality any JavaScript type.
262 // Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
263 var equiv = (function () {
264
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 }.
268 var pairs = [];
269
270 var getProto = Object.getPrototypeOf || function (obj) {
271 return obj.__proto__;
272 };
273
274 function useStrictEquality(a, b) {
275
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:
278 // `var i = 1;`
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") {
282 a = a.valueOf();
283 }
284 if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
285 b = b.valueOf();
286 }
287
288 return a === b;
289 }
290
291 function compareConstructors(a, b) {
292 var protoA = getProto(a);
293 var protoB = getProto(b);
294
295 // Comparing constructors is more strict than using `instanceof`
296 if (a.constructor === b.constructor) {
297 return true;
298 }
299
300 // Ref #851
301 // If the obj prototype descends from a null constructor, treat it
302 // as a null prototype.
303 if (protoA && protoA.constructor === null) {
304 protoA = null;
305 }
306 if (protoB && protoB.constructor === null) {
307 protoB = null;
308 }
309
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) {
313 return true;
314 }
315
316 return false;
317 }
318
319 function getRegExpFlags(regexp) {
320 return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
321 }
322
323 function isContainer(val) {
324 return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
325 }
326
327 function breadthFirstCompareChild(a, b) {
328
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
331 // over the pair.
332 if (a === b) {
333 return true;
334 }
335 if (!isContainer(a)) {
336 return typeEquiv(a, b);
337 }
338 if (pairs.every(function (pair) {
339 return pair.a !== a || pair.b !== b;
340 })) {
341
342 // Not yet started comparing this pair
343 pairs.push({ a: a, b: b });
344 }
345 return true;
346 }
347
348 var callbacks = {
349 "string": useStrictEquality,
350 "boolean": useStrictEquality,
351 "number": useStrictEquality,
352 "null": useStrictEquality,
353 "undefined": useStrictEquality,
354 "symbol": useStrictEquality,
355 "date": useStrictEquality,
356
357 "nan": function nan() {
358 return true;
359 },
360
361 "regexp": function regexp(a, b) {
362 return a.source === b.source &&
363
364 // Include flags in the comparison
365 getRegExpFlags(a) === getRegExpFlags(b);
366 },
367
368 // abort (identical references / instance methods were skipped earlier)
369 "function": function _function() {
370 return false;
371 },
372
373 "array": function array(a, b) {
374 var i, len;
375
376 len = a.length;
377 if (len !== b.length) {
378
379 // Safe and faster
380 return false;
381 }
382
383 for (i = 0; i < len; i++) {
384
385 // Compare non-containers; queue non-reference-equal containers
386 if (!breadthFirstCompareChild(a[i], b[i])) {
387 return false;
388 }
389 }
390 return true;
391 },
392
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) {
399 var innerEq,
400 outerEq = true;
401
402 if (a.size !== b.size) {
403
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.
408 return false;
409 }
410
411 a.forEach(function (aVal) {
412
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
416 // Set is unused)
417 if (!outerEq) {
418 return;
419 }
420
421 innerEq = false;
422
423 b.forEach(function (bVal) {
424 var parentPairs;
425
426 // Likewise, short-circuit if the result is already known
427 if (innerEq) {
428 return;
429 }
430
431 // Swap out the global pairs list, as the nested call to
432 // innerEquiv will clobber its contents
433 parentPairs = pairs;
434 if (innerEquiv(bVal, aVal)) {
435 innerEq = true;
436 }
437
438 // Replace the global pairs list
439 pairs = parentPairs;
440 });
441
442 if (!innerEq) {
443 outerEq = false;
444 }
445 });
446
447 return outerEq;
448 },
449
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) {
457 var innerEq,
458 outerEq = true;
459
460 if (a.size !== b.size) {
461
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.
466 return false;
467 }
468
469 a.forEach(function (aVal, aKey) {
470
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
474 // Map is unused)
475 if (!outerEq) {
476 return;
477 }
478
479 innerEq = false;
480
481 b.forEach(function (bVal, bKey) {
482 var parentPairs;
483
484 // Likewise, short-circuit if the result is already known
485 if (innerEq) {
486 return;
487 }
488
489 // Swap out the global pairs list, as the nested call to
490 // innerEquiv will clobber its contents
491 parentPairs = pairs;
492 if (innerEquiv([bVal, bKey], [aVal, aKey])) {
493 innerEq = true;
494 }
495
496 // Replace the global pairs list
497 pairs = parentPairs;
498 });
499
500 if (!innerEq) {
501 outerEq = false;
502 }
503 });
504
505 return outerEq;
506 },
507
508 "object": function object(a, b) {
509 var i,
510 aProperties = [],
511 bProperties = [];
512
513 if (compareConstructors(a, b) === false) {
514 return false;
515 }
516
517 // Be strict: don't ensure hasOwnProperty and go deep
518 for (i in a) {
519
520 // Collect a's properties
521 aProperties.push(i);
522
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()) {
525 continue;
526 }
527
528 // Compare non-containers; queue non-reference-equal containers
529 if (!breadthFirstCompareChild(a[i], b[i])) {
530 return false;
531 }
532 }
533
534 for (i in b) {
535
536 // Collect b's properties
537 bProperties.push(i);
538 }
539
540 // Ensures identical properties name
541 return typeEquiv(aProperties.sort(), bProperties.sort());
542 }
543 };
544
545 function typeEquiv(a, b) {
546 var type = objectType(a);
547
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).
552 //
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);
557 }
558
559 function innerEquiv(a, b) {
560 var i, pair;
561
562 // We're done when there's nothing more to compare
563 if (arguments.length < 2) {
564 return true;
565 }
566
567 // Clear the global pair queue and add the top-level values being compared
568 pairs = [{ a: a, b: b }];
569
570 for (i = 0; i < pairs.length; i++) {
571 pair = pairs[i];
572
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)) {
581 return false;
582 }
583 }
584
585 // ...across all consecutive argument pairs
586 return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
587 }
588
589 return function () {
590 var result = innerEquiv.apply(undefined, arguments);
591
592 // Release any retained objects
593 pairs.length = 0;
594 return result;
595 };
596 })();
597
598 /**
599 * Config object: Maintain internal state
600 * Later exposed as QUnit.config
601 * `config` initialized at top of scope
602 */
603 var config = {
604
605 // The queue of tests to run
606 queue: [],
607
608 // Block until document ready
609 blocking: true,
610
611 // By default, run previously failed tests first
612 // very useful in combination with "Hide passed tests" checked
613 reorder: true,
614
615 // By default, modify document.title when suite is done
616 altertitle: true,
617
618 // HTML Reporter: collapse every test except the first failing test
619 // If false, all failing tests will be expanded
620 collapse: true,
621
622 // By default, scroll to top of the page when suite is done
623 scrolltop: true,
624
625 // Depth up-to which object will be dumped
626 maxDepth: 5,
627
628 // When enabled, all tests must call expect()
629 requireExpects: false,
630
631 // Placeholder for user-configurable form-exposed URL parameters
632 urlConfig: [],
633
634 // Set of all modules.
635 modules: [],
636
637 // The first unnamed module
638 currentModule: {
639 name: "",
640 tests: [],
641 childModules: [],
642 testsRun: 0,
643 unskippedTestsRun: 0,
644 hooks: {
645 before: [],
646 beforeEach: [],
647 afterEach: [],
648 after: []
649 }
650 },
651
652 callbacks: {},
653
654 // The storage module to use for reordering tests
655 storage: localSessionStorage
656 };
657
658 // take a predefined QUnit.config and extend the defaults
659 var globalConfig = window && window.QUnit && window.QUnit.config;
660
661 // only extend the global config if there is no QUnit overload
662 if (window && window.QUnit && !window.QUnit.version) {
663 extend(config, globalConfig);
664 }
665
666 // Push a loose unnamed module to the modules collection
667 config.modules.push(config.currentModule);
668
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, "\\\"") + "\"";
674 }
675 function literal(o) {
676 return o + "";
677 }
678 function join(pre, arr, post) {
679 var s = dump.separator(),
680 base = dump.indent(),
681 inner = dump.indent(1);
682 if (arr.join) {
683 arr = arr.join("," + s + inner);
684 }
685 if (!arr) {
686 return pre + post;
687 }
688 return [pre, inner + arr, base + post].join(s);
689 }
690 function array(arr, stack) {
691 var i = arr.length,
692 ret = new Array(i);
693
694 if (dump.maxDepth && dump.depth > dump.maxDepth) {
695 return "[object Array]";
696 }
697
698 this.up();
699 while (i--) {
700 ret[i] = this.parse(arr[i], undefined, stack);
701 }
702 this.down();
703 return join("[", ret, "]");
704 }
705
706 function isArray(obj) {
707 return (
708
709 //Native Arrays
710 toString.call(obj) === "[object Array]" ||
711
712 // NodeList objects
713 typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
714 );
715 }
716
717 var reName = /^function (\w+)/,
718 dump = {
719
720 // The objType is used mostly internally, you can fix a (custom) type in advance
721 parse: function parse(obj, objType, stack) {
722 stack = stack || [];
723 var res,
724 parser,
725 parserType,
726 objIndex = stack.indexOf(obj);
727
728 if (objIndex !== -1) {
729 return "recursion(" + (objIndex - stack.length) + ")";
730 }
731
732 objType = objType || this.typeOf(obj);
733 parser = this.parsers[objType];
734 parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
735
736 if (parserType === "function") {
737 stack.push(obj);
738 res = parser.call(this, obj, stack);
739 stack.pop();
740 return res;
741 }
742 return parserType === "string" ? parser : this.parsers.error;
743 },
744 typeOf: function typeOf(obj) {
745 var type;
746
747 if (obj === null) {
748 type = "null";
749 } else if (typeof obj === "undefined") {
750 type = "undefined";
751 } else if (is("regexp", obj)) {
752 type = "regexp";
753 } else if (is("date", obj)) {
754 type = "date";
755 } else if (is("function", obj)) {
756 type = "function";
757 } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
758 type = "window";
759 } else if (obj.nodeType === 9) {
760 type = "document";
761 } else if (obj.nodeType) {
762 type = "node";
763 } else if (isArray(obj)) {
764 type = "array";
765 } else if (obj.constructor === Error.prototype.constructor) {
766 type = "error";
767 } else {
768 type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
769 }
770 return type;
771 },
772
773 separator: function separator() {
774 if (this.multiline) {
775 return this.HTML ? "<br />" : "\n";
776 } else {
777 return this.HTML ? "&#160;" : " ";
778 }
779 },
780
781 // Extra can be a number, shortcut for increasing-calling-decreasing
782 indent: function indent(extra) {
783 if (!this.multiline) {
784 return "";
785 }
786 var chr = this.indentChar;
787 if (this.HTML) {
788 chr = chr.replace(/\t/g, " ").replace(/ /g, "&#160;");
789 }
790 return new Array(this.depth + (extra || 0)).join(chr);
791 },
792 up: function up(a) {
793 this.depth += a || 1;
794 },
795 down: function down(a) {
796 this.depth -= a || 1;
797 },
798 setParser: function setParser(name, parser) {
799 this.parsers[name] = parser;
800 },
801
802 // The next 3 are exposed so you can use them
803 quote: quote,
804 literal: literal,
805 join: join,
806 depth: 1,
807 maxDepth: config.maxDepth,
808
809 // This is the list of parsers, to modify them, use dump.setParser
810 parsers: {
811 window: "[Window]",
812 document: "[Document]",
813 error: function error(_error) {
814 return "Error(\"" + _error.message + "\")";
815 },
816 unknown: "[Unknown]",
817 "null": "null",
818 "undefined": "undefined",
819 "function": function _function(fn) {
820 var ret = "function",
821
822
823 // Functions never have name in IE
824 name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
825
826 if (name) {
827 ret += " " + name;
828 }
829 ret += "(";
830
831 ret = [ret, dump.parse(fn, "functionArgs"), "){"].join("");
832 return join(ret, dump.parse(fn, "functionCode"), "}");
833 },
834 array: array,
835 nodelist: array,
836 "arguments": array,
837 object: function object(map, stack) {
838 var keys,
839 key,
840 val,
841 i,
842 nonEnumerableProperties,
843 ret = [];
844
845 if (dump.maxDepth && dump.depth > dump.maxDepth) {
846 return "[object Object]";
847 }
848
849 dump.up();
850 keys = [];
851 for (key in map) {
852 keys.push(key);
853 }
854
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)) {
860 keys.push(key);
861 }
862 }
863 keys.sort();
864 for (i = 0; i < keys.length; i++) {
865 key = keys[i];
866 val = map[key];
867 ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack));
868 }
869 dump.down();
870 return join("{", ret, "}");
871 },
872 node: function node(_node) {
873 var len,
874 i,
875 val,
876 open = dump.HTML ? "&lt;" : "<",
877 close = dump.HTML ? "&gt;" : ">",
878 tag = _node.nodeName.toLowerCase(),
879 ret = open + tag,
880 attrs = _node.attributes;
881
882 if (attrs) {
883 for (i = 0, len = attrs.length; i < len; i++) {
884 val = attrs[i].nodeValue;
885
886 // IE6 includes all attributes in .attributes, even ones not explicitly
887 // set. Those have values like undefined, null, 0, false, "" or
888 // "inherit".
889 if (val && val !== "inherit") {
890 ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute");
891 }
892 }
893 }
894 ret += close;
895
896 // Show content of TextNode or CDATASection
897 if (_node.nodeType === 3 || _node.nodeType === 4) {
898 ret += _node.nodeValue;
899 }
900
901 return ret + open + "/" + tag + close;
902 },
903
904 // Function calls it internally, it's the arguments part of the function
905 functionArgs: function functionArgs(fn) {
906 var args,
907 l = fn.length;
908
909 if (!l) {
910 return "";
911 }
912
913 args = new Array(l);
914 while (l--) {
915
916 // 97 is 'a'
917 args[l] = String.fromCharCode(97 + l);
918 }
919 return " " + args.join(", ") + " ";
920 },
921
922 // Object calls it internally, the key part of an item in a map
923 key: quote,
924
925 // Function calls it internally, it's the content of the function
926 functionCode: "[code]",
927
928 // Node calls it internally, it's a html attribute value
929 attribute: quote,
930 string: quote,
931 date: quote,
932 regexp: literal,
933 number: literal,
934 "boolean": literal,
935 symbol: function symbol(sym) {
936 return sym.toString();
937 }
938 },
939
940 // If true, entities are escaped ( <, >, \t, space and \n )
941 HTML: false,
942
943 // Indentation unit
944 indentChar: " ",
945
946 // If true, items in a collection, are separated by a \n, else just a space.
947 multiline: true
948 };
949
950 return dump;
951 })();
952
953 var SuiteReport = function () {
954 function SuiteReport(name, parentSuite) {
955 classCallCheck(this, SuiteReport);
956
957 this.name = name;
958 this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
959
960 this.tests = [];
961 this.childSuites = [];
962
963 if (parentSuite) {
964 parentSuite.pushChildSuite(this);
965 }
966 }
967
968 createClass(SuiteReport, [{
969 key: "start",
970 value: function start(recordTime) {
971 if (recordTime) {
972 this._startTime = Date.now();
973 }
974
975 return {
976 name: this.name,
977 fullName: this.fullName.slice(),
978 tests: this.tests.map(function (test) {
979 return test.start();
980 }),
981 childSuites: this.childSuites.map(function (suite) {
982 return suite.start();
983 }),
984 testCounts: {
985 total: this.getTestCounts().total
986 }
987 };
988 }
989 }, {
990 key: "end",
991 value: function end(recordTime) {
992 if (recordTime) {
993 this._endTime = Date.now();
994 }
995
996 return {
997 name: this.name,
998 fullName: this.fullName.slice(),
999 tests: this.tests.map(function (test) {
1000 return test.end();
1001 }),
1002 childSuites: this.childSuites.map(function (suite) {
1003 return suite.end();
1004 }),
1005 testCounts: this.getTestCounts(),
1006 runtime: this.getRuntime(),
1007 status: this.getStatus()
1008 };
1009 }
1010 }, {
1011 key: "pushChildSuite",
1012 value: function pushChildSuite(suite) {
1013 this.childSuites.push(suite);
1014 }
1015 }, {
1016 key: "pushTest",
1017 value: function pushTest(test) {
1018 this.tests.push(test);
1019 }
1020 }, {
1021 key: "getRuntime",
1022 value: function getRuntime() {
1023 return this._endTime - this._startTime;
1024 }
1025 }, {
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 };
1029
1030 counts = this.tests.reduce(function (counts, test) {
1031 if (test.valid) {
1032 counts[test.getStatus()]++;
1033 counts.total++;
1034 }
1035
1036 return counts;
1037 }, counts);
1038
1039 return this.childSuites.reduce(function (counts, suite) {
1040 return suite.getTestCounts(counts);
1041 }, counts);
1042 }
1043 }, {
1044 key: "getStatus",
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;
1051
1052 if (failed) {
1053 return "failed";
1054 } else {
1055 if (skipped === total) {
1056 return "skipped";
1057 } else if (todo === total) {
1058 return "todo";
1059 } else {
1060 return "passed";
1061 }
1062 }
1063 }
1064 }]);
1065 return SuiteReport;
1066 }();
1067
1068 var focused = false;
1069
1070 var moduleStack = [];
1071
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;
1076
1077 var skip = parentModule !== null && parentModule.skip || modifiers.skip;
1078 var todo = parentModule !== null && parentModule.todo || modifiers.todo;
1079
1080 var module = {
1081 name: moduleName,
1082 parentModule: parentModule,
1083 tests: [],
1084 moduleId: generateHash(moduleName),
1085 testsRun: 0,
1086 unskippedTestsRun: 0,
1087 childModules: [],
1088 suiteReport: new SuiteReport(name, parentSuite),
1089
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`.
1094 skip: skip,
1095 todo: skip ? false : todo
1096 };
1097
1098 var env = {};
1099 if (parentModule) {
1100 parentModule.childModules.push(module);
1101 extend(env, parentModule.testEnvironment);
1102 }
1103 extend(env, testEnvironment);
1104 module.testEnvironment = env;
1105
1106 config.modules.push(module);
1107 return module;
1108 }
1109
1110 function processModule(name, options, executeNow) {
1111 var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
1112
1113 if (objectType(options) === "function") {
1114 executeNow = options;
1115 options = undefined;
1116 }
1117
1118 var module = createModule(name, options, modifiers);
1119
1120 // Move any hooks to a 'hooks' object
1121 var testEnvironment = module.testEnvironment;
1122 var hooks = module.hooks = {};
1123
1124 setHookFromEnvironment(hooks, testEnvironment, "before");
1125 setHookFromEnvironment(hooks, testEnvironment, "beforeEach");
1126 setHookFromEnvironment(hooks, testEnvironment, "afterEach");
1127 setHookFromEnvironment(hooks, testEnvironment, "after");
1128
1129 var moduleFns = {
1130 before: setHookFunction(module, "before"),
1131 beforeEach: setHookFunction(module, "beforeEach"),
1132 afterEach: setHookFunction(module, "afterEach"),
1133 after: setHookFunction(module, "after")
1134 };
1135
1136 var currentModule = config.currentModule;
1137 if (objectType(executeNow) === "function") {
1138 moduleStack.push(module);
1139 config.currentModule = module;
1140 executeNow.call(module.testEnvironment, moduleFns);
1141 moduleStack.pop();
1142 module = module.parentModule || currentModule;
1143 }
1144
1145 config.currentModule = module;
1146
1147 function setHookFromEnvironment(hooks, environment, name) {
1148 var potentialHook = environment[name];
1149 hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
1150 delete environment[name];
1151 }
1152
1153 function setHookFunction(module, hookName) {
1154 return function setHook(callback) {
1155 module.hooks[hookName].push(callback);
1156 };
1157 }
1158 }
1159
1160 function module$1(name, options, executeNow) {
1161 if (focused) {
1162 return;
1163 }
1164
1165 processModule(name, options, executeNow);
1166 }
1167
1168 module$1.only = function () {
1169 if (focused) {
1170 return;
1171 }
1172
1173 config.modules.length = 0;
1174 config.queue.length = 0;
1175
1176 module$1.apply(undefined, arguments);
1177
1178 focused = true;
1179 };
1180
1181 module$1.skip = function (name, options, executeNow) {
1182 if (focused) {
1183 return;
1184 }
1185
1186 processModule(name, options, executeNow, { skip: true });
1187 };
1188
1189 module$1.todo = function (name, options, executeNow) {
1190 if (focused) {
1191 return;
1192 }
1193
1194 processModule(name, options, executeNow, { todo: true });
1195 };
1196
1197 var LISTENERS = Object.create(null);
1198 var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
1199
1200 /**
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
1204 * logging events.
1205 *
1206 * @private
1207 * @method emit
1208 * @param {String} eventName
1209 * @param {Object} data
1210 * @return {Void}
1211 */
1212 function emit(eventName, data) {
1213 if (objectType(eventName) !== "string") {
1214 throw new TypeError("eventName must be a string when emitting an event");
1215 }
1216
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)) : [];
1220
1221 for (var i = 0; i < callbacks.length; i++) {
1222 callbacks[i](data);
1223 }
1224 }
1225
1226 /**
1227 * Registers a callback as a listener to the specified event.
1228 *
1229 * @public
1230 * @method on
1231 * @param {String} eventName
1232 * @param {Function} callback
1233 * @return {Void}
1234 */
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");
1243 }
1244
1245 if (!LISTENERS[eventName]) {
1246 LISTENERS[eventName] = [];
1247 }
1248
1249 // Don't register the same callback more than once
1250 if (!inArray(callback, LISTENERS[eventName])) {
1251 LISTENERS[eventName].push(callback);
1252 }
1253 }
1254
1255 // Register logging callbacks
1256 function registerLoggingCallbacks(obj) {
1257 var i,
1258 l,
1259 key,
1260 callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
1261
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.");
1266 }
1267
1268 config.callbacks[key].push(callback);
1269 };
1270
1271 return loggingCallback;
1272 }
1273
1274 for (i = 0, l = callbackNames.length; i < l; i++) {
1275 key = callbackNames[i];
1276
1277 // Initialize key collection of logging callback
1278 if (objectType(config.callbacks[key]) === "undefined") {
1279 config.callbacks[key] = [];
1280 }
1281
1282 obj[key] = registerLoggingCallback(key);
1283 }
1284 }
1285
1286 function runLoggingCallbacks(key, args) {
1287 var i, l, callbacks;
1288
1289 callbacks = config.callbacks[key];
1290 for (i = 0, l = callbacks.length; i < l; i++) {
1291 callbacks[i](args);
1292 }
1293 }
1294
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(/.+\//, "");
1298
1299 function extractStacktrace(e, offset) {
1300 offset = offset === undefined ? 4 : offset;
1301
1302 var stack, include, i;
1303
1304 if (e && e.stack) {
1305 stack = e.stack.split("\n");
1306 if (/^error$/i.test(stack[0])) {
1307 stack.shift();
1308 }
1309 if (fileName) {
1310 include = [];
1311 for (i = offset; i < stack.length; i++) {
1312 if (stack[i].indexOf(fileName) !== -1) {
1313 break;
1314 }
1315 include.push(stack[i]);
1316 }
1317 if (include.length) {
1318 return include.join("\n");
1319 }
1320 }
1321 return stack[offset];
1322 }
1323 }
1324
1325 function sourceFromStacktrace(offset) {
1326 var error = new Error();
1327
1328 // Support: Safari <=7 only, IE <=10 - 11 only
1329 // Not all browsers generate the `stack` property for `new Error()`, see also #636
1330 if (!error.stack) {
1331 try {
1332 throw error;
1333 } catch (err) {
1334 error = err;
1335 }
1336 }
1337
1338 return extractStacktrace(error, offset);
1339 }
1340
1341 var priorityCount = 0;
1342 var unitSampler = void 0;
1343
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.
1347 var taskQueue = [];
1348
1349 /**
1350 * Advances the taskQueue to the next task. If the taskQueue is empty,
1351 * process the testQueue
1352 */
1353 function advance() {
1354 advanceTaskQueue();
1355
1356 if (!taskQueue.length) {
1357 advanceTestQueue();
1358 }
1359 }
1360
1361 /**
1362 * Advances the taskQueue to the next task if it is ready and not empty.
1363 */
1364 function advanceTaskQueue() {
1365 var start = now();
1366 config.depth = (config.depth || 0) + 1;
1367
1368 while (taskQueue.length && !config.blocking) {
1369 var elapsedTime = now() - start;
1370
1371 if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
1372 var task = taskQueue.shift();
1373 task();
1374 } else {
1375 setTimeout(advance);
1376 break;
1377 }
1378 }
1379
1380 config.depth--;
1381 }
1382
1383 /**
1384 * Advance the testQueue to the next test to process. Call done() if testQueue completes.
1385 */
1386 function advanceTestQueue() {
1387 if (!config.blocking && !config.queue.length && config.depth === 0) {
1388 done();
1389 return;
1390 }
1391
1392 var testTasks = config.queue.shift();
1393 addToTaskQueue(testTasks());
1394
1395 if (priorityCount > 0) {
1396 priorityCount--;
1397 }
1398
1399 advance();
1400 }
1401
1402 /**
1403 * Enqueue the tasks for a test into the task queue.
1404 * @param {Array} tasksArray
1405 */
1406 function addToTaskQueue(tasksArray) {
1407 taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
1408 }
1409
1410 /**
1411 * Return the number of tasks remaining in the task queue to be processed.
1412 * @return {Number}
1413 */
1414 function taskQueueLength() {
1415 return taskQueue.length;
1416 }
1417
1418 /**
1419 * Adds a test to the TestQueue for execution.
1420 * @param {Function} testTasksFunc
1421 * @param {Boolean} prioritize
1422 * @param {String} seed
1423 */
1424 function addToTestQueue(testTasksFunc, prioritize, seed) {
1425 if (prioritize) {
1426 config.queue.splice(priorityCount++, 0, testTasksFunc);
1427 } else if (seed) {
1428 if (!unitSampler) {
1429 unitSampler = unitSamplerGenerator(seed);
1430 }
1431
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);
1435 } else {
1436 config.queue.push(testTasksFunc);
1437 }
1438 }
1439
1440 /**
1441 * Creates a seeded "sample" generator which is used for randomizing tests.
1442 */
1443 function unitSamplerGenerator(seed) {
1444
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;
1452
1453 // ECMAScript has no unsigned number type
1454 if (sample < 0) {
1455 sample += 0x100000000;
1456 }
1457
1458 return sample / 0x100000000;
1459 };
1460 }
1461
1462 /**
1463 * This function is called when the ProcessingQueue is done processing all
1464 * items. It handles emitting the final run events.
1465 */
1466 function done() {
1467 var storage = config.storage;
1468
1469 ProcessingQueue.finished = true;
1470
1471 var runtime = now() - config.started;
1472 var passed = config.stats.all - config.stats.bad;
1473
1474 if (config.stats.all === 0) {
1475
1476 if (config.filter && config.filter.length) {
1477 throw new Error("No tests matched the filter \"" + config.filter + "\".");
1478 }
1479
1480 if (config.module && config.module.length) {
1481 throw new Error("No tests matched the module \"" + config.module + "\".");
1482 }
1483
1484 if (config.moduleId && config.moduleId.length) {
1485 throw new Error("No tests matched the moduleId \"" + config.moduleId + "\".");
1486 }
1487
1488 if (config.testId && config.testId.length) {
1489 throw new Error("No tests matched the testId \"" + config.testId + "\".");
1490 }
1491
1492 throw new Error("No tests were run.");
1493 }
1494
1495 emit("runEnd", globalSuite.end(true));
1496 runLoggingCallbacks("done", {
1497 passed: passed,
1498 failed: config.stats.bad,
1499 total: config.stats.all,
1500 runtime: runtime
1501 });
1502
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);
1507
1508 if (key.indexOf("qunit-test-") === 0) {
1509 storage.removeItem(key);
1510 }
1511 }
1512 }
1513 }
1514
1515 var ProcessingQueue = {
1516 finished: false,
1517 add: addToTestQueue,
1518 advance: advance,
1519 taskCount: taskQueueLength
1520 };
1521
1522 var TestReport = function () {
1523 function TestReport(name, suite, options) {
1524 classCallCheck(this, TestReport);
1525
1526 this.name = name;
1527 this.suiteName = suite.name;
1528 this.fullName = suite.fullName.concat(name);
1529 this.runtime = 0;
1530 this.assertions = [];
1531
1532 this.skipped = !!options.skip;
1533 this.todo = !!options.todo;
1534
1535 this.valid = options.valid;
1536
1537 this._startTime = 0;
1538 this._endTime = 0;
1539
1540 suite.pushTest(this);
1541 }
1542
1543 createClass(TestReport, [{
1544 key: "start",
1545 value: function start(recordTime) {
1546 if (recordTime) {
1547 this._startTime = Date.now();
1548 }
1549
1550 return {
1551 name: this.name,
1552 suiteName: this.suiteName,
1553 fullName: this.fullName.slice()
1554 };
1555 }
1556 }, {
1557 key: "end",
1558 value: function end(recordTime) {
1559 if (recordTime) {
1560 this._endTime = Date.now();
1561 }
1562
1563 return extend(this.start(), {
1564 runtime: this.getRuntime(),
1565 status: this.getStatus(),
1566 errors: this.getFailedAssertions(),
1567 assertions: this.getAssertions()
1568 });
1569 }
1570 }, {
1571 key: "pushAssertion",
1572 value: function pushAssertion(assertion) {
1573 this.assertions.push(assertion);
1574 }
1575 }, {
1576 key: "getRuntime",
1577 value: function getRuntime() {
1578 return this._endTime - this._startTime;
1579 }
1580 }, {
1581 key: "getStatus",
1582 value: function getStatus() {
1583 if (this.skipped) {
1584 return "skipped";
1585 }
1586
1587 var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
1588
1589 if (!testPassed) {
1590 return "failed";
1591 } else if (this.todo) {
1592 return "todo";
1593 } else {
1594 return "passed";
1595 }
1596 }
1597 }, {
1598 key: "getFailedAssertions",
1599 value: function getFailedAssertions() {
1600 return this.assertions.filter(function (assertion) {
1601 return !assertion.passed;
1602 });
1603 }
1604 }, {
1605 key: "getAssertions",
1606 value: function getAssertions() {
1607 return this.assertions.slice();
1608 }
1609
1610 // Remove actual and expected values from assertions. This is to prevent
1611 // leaking memory throughout a test suite.
1612
1613 }, {
1614 key: "slimAssertions",
1615 value: function slimAssertions() {
1616 this.assertions = this.assertions.map(function (assertion) {
1617 delete assertion.actual;
1618 delete assertion.expected;
1619 return assertion;
1620 });
1621 }
1622 }]);
1623 return TestReport;
1624 }();
1625
1626 var focused$1 = false;
1627
1628 function Test(settings) {
1629 var i, l;
1630
1631 ++Test.count;
1632
1633 this.expected = null;
1634 this.assertions = [];
1635 this.semaphore = 0;
1636 this.module = config.currentModule;
1637 this.stack = sourceFromStacktrace(3);
1638 this.steps = [];
1639 this.timeout = undefined;
1640
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.
1645 //
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;
1651
1652 // Skipped tests should be left intact
1653 } else if (this.module.todo && !settings.skip) {
1654 settings.todo = true;
1655 }
1656
1657 extend(this, settings);
1658
1659 this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
1660 todo: settings.todo,
1661 skip: settings.skip,
1662 valid: this.valid()
1663 });
1664
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 += " ";
1669 }
1670 }
1671
1672 this.testId = generateHash(this.module.name, this.testName);
1673
1674 this.module.tests.push({
1675 name: this.testName,
1676 testId: this.testId,
1677 skip: !!settings.skip
1678 });
1679
1680 if (settings.skip) {
1681
1682 // Skipped tests will fully ignore any sent callback
1683 this.callback = function () {};
1684 this.async = false;
1685 this.expected = 0;
1686 } else {
1687 if (typeof this.callback !== "function") {
1688 var method = this.todo ? "todo" : "test";
1689
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 + "\")");
1692 }
1693
1694 this.assert = new Assert(this);
1695 }
1696 }
1697
1698 Test.count = 0;
1699
1700 function getNotStartedModules(startModule) {
1701 var module = startModule,
1702 modules = [];
1703
1704 while (module && module.testsRun === 0) {
1705 modules.push(module);
1706 module = module.parentModule;
1707 }
1708
1709 return modules;
1710 }
1711
1712 Test.prototype = {
1713 before: function before() {
1714 var i,
1715 startModule,
1716 module = this.module,
1717 notStartedModules = getNotStartedModules(module);
1718
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
1726 });
1727 }
1728
1729 config.current = this;
1730
1731 this.testEnvironment = extend({}, module.testEnvironment);
1732
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
1740 });
1741
1742 if (!config.pollution) {
1743 saveGlobal();
1744 }
1745 },
1746
1747 run: function run() {
1748 var promise;
1749
1750 config.current = this;
1751
1752 this.callbackStarted = now();
1753
1754 if (config.notrycatch) {
1755 runTest(this);
1756 return;
1757 }
1758
1759 try {
1760 runTest(this);
1761 } catch (e) {
1762 this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
1763
1764 // Else next test will carry the responsibility
1765 saveGlobal();
1766
1767 // Restart the tests if they're blocking
1768 if (config.blocking) {
1769 internalRecover(this);
1770 }
1771 }
1772
1773 function runTest(test) {
1774 promise = test.callback.call(test.testEnvironment, test.assert);
1775 test.resolvePromise(promise);
1776
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));
1781 }
1782 }
1783 },
1784
1785 after: function after() {
1786 checkPollution();
1787 },
1788
1789 queueHook: function queueHook(hook, hookName, hookOwner) {
1790 var _this = this;
1791
1792 var callHook = function callHook() {
1793 var promise = hook.call(_this.testEnvironment, _this.assert);
1794 _this.resolvePromise(promise, hookName);
1795 };
1796
1797 var runHook = function runHook() {
1798 if (hookName === "before") {
1799 if (hookOwner.unskippedTestsRun !== 0) {
1800 return;
1801 }
1802
1803 _this.preserveEnvironment = true;
1804 }
1805
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)) {
1809 return;
1810 }
1811
1812 config.current = _this;
1813 if (config.notrycatch) {
1814 callHook();
1815 return;
1816 }
1817 try {
1818 callHook();
1819 } catch (error) {
1820 _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
1821 }
1822 };
1823
1824 return runHook;
1825 },
1826
1827
1828 // Currently only used for module level hooks, can be used to add global level ones
1829 hooks: function hooks(handler) {
1830 var hooks = [];
1831
1832 function processHooks(test, module) {
1833 if (module.parentModule) {
1834 processHooks(test, module.parentModule);
1835 }
1836
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));
1840 }
1841 }
1842 }
1843
1844 // Hooks are ignored on skipped tests
1845 if (!this.skip) {
1846 processHooks(this, this.module);
1847 }
1848
1849 return hooks;
1850 },
1851
1852
1853 finish: function finish() {
1854 config.current = this;
1855
1856 // Release the test callback to ensure that anything referenced has been
1857 // released to be garbage collected.
1858 this.callback = undefined;
1859
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);
1863 }
1864
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);
1871 }
1872
1873 var i,
1874 module = this.module,
1875 moduleName = module.name,
1876 testName = this.testName,
1877 skipped = !!this.skip,
1878 todo = !!this.todo,
1879 bad = 0,
1880 storage = config.storage;
1881
1882 this.runtime = now() - this.started;
1883
1884 config.stats.all += this.assertions.length;
1885 module.stats.all += this.assertions.length;
1886
1887 for (i = 0; i < this.assertions.length; i++) {
1888 if (!this.assertions[i].result) {
1889 bad++;
1890 config.stats.bad++;
1891 module.stats.bad++;
1892 }
1893 }
1894
1895 notifyTestsRan(module, skipped);
1896
1897 // Store result when possible
1898 if (storage) {
1899 if (bad) {
1900 storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
1901 } else {
1902 storage.removeItem("qunit-test-" + moduleName + "-" + testName);
1903 }
1904 }
1905
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();
1910
1911 runLoggingCallbacks("testDone", {
1912 name: testName,
1913 module: moduleName,
1914 skipped: skipped,
1915 todo: todo,
1916 failed: bad,
1917 passed: this.assertions.length - bad,
1918 total: this.assertions.length,
1919 runtime: skipped ? 0 : this.runtime,
1920
1921 // HTML Reporter use
1922 assertions: this.assertions,
1923 testId: this.testId,
1924
1925 // Source of Test
1926 source: this.stack
1927 });
1928
1929 if (module.testsRun === numberOfTests(module)) {
1930 logSuiteEnd(module);
1931
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;
1938 }
1939 }
1940
1941 config.current = undefined;
1942
1943 function logSuiteEnd(module) {
1944
1945 // Reset `module.hooks` to ensure that anything referenced in these hooks
1946 // has been released to be garbage collected.
1947 module.hooks = {};
1948
1949 emit("suiteEnd", module.suiteReport.end(true));
1950 runLoggingCallbacks("moduleDone", {
1951 name: module.name,
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
1957 });
1958 }
1959 },
1960
1961 preserveTestEnvironment: function preserveTestEnvironment() {
1962 if (this.preserveEnvironment) {
1963 this.module.testEnvironment = this.testEnvironment;
1964 this.testEnvironment = extend({}, this.module.testEnvironment);
1965 }
1966 },
1967
1968 queue: function queue() {
1969 var test = this;
1970
1971 if (!this.valid()) {
1972 return;
1973 }
1974
1975 function runTest() {
1976 return [function () {
1977 test.before();
1978 }].concat(toConsumableArray(test.hooks("before")), [function () {
1979 test.preserveTestEnvironment();
1980 }], toConsumableArray(test.hooks("beforeEach")), [function () {
1981 test.run();
1982 }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () {
1983 test.after();
1984 }, function () {
1985 test.finish();
1986 }]);
1987 }
1988
1989 var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
1990
1991 // Prioritize previously failed tests, detected from storage
1992 var prioritize = config.reorder && !!previousFailCount;
1993
1994 this.previousFailure = !!previousFailCount;
1995
1996 ProcessingQueue.add(runTest, prioritize, config.seed);
1997
1998 // If the queue has already finished, we manually process the new test
1999 if (ProcessingQueue.finished) {
2000 ProcessingQueue.advance();
2001 }
2002 },
2003
2004
2005 pushResult: function pushResult(resultInfo) {
2006 if (this !== config.current) {
2007 throw new Error("Assertion occurred after test had finished.");
2008 }
2009
2010 // Destructure of resultInfo = { result, actual, expected, message, negative }
2011 var source,
2012 details = {
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,
2021 todo: !!this.todo
2022 };
2023
2024 if (hasOwn.call(resultInfo, "expected")) {
2025 details.expected = resultInfo.expected;
2026 }
2027
2028 if (!resultInfo.result) {
2029 source = resultInfo.source || sourceFromStacktrace();
2030
2031 if (source) {
2032 details.source = source;
2033 }
2034 }
2035
2036 this.logAssertion(details);
2037
2038 this.assertions.push({
2039 result: !!resultInfo.result,
2040 message: resultInfo.message
2041 });
2042 },
2043
2044 pushFailure: function pushFailure(message, source, actual) {
2045 if (!(this instanceof Test)) {
2046 throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
2047 }
2048
2049 this.pushResult({
2050 result: false,
2051 message: message || "error",
2052 actual: actual || null,
2053 source: source
2054 });
2055 },
2056
2057 /**
2058 * Log assertion details using both the old QUnit.log interface and
2059 * QUnit.on( "assertion" ) interface.
2060 *
2061 * @private
2062 */
2063 logAssertion: function logAssertion(details) {
2064 runLoggingCallbacks("log", details);
2065
2066 var assertion = {
2067 passed: details.result,
2068 actual: details.actual,
2069 expected: details.expected,
2070 message: details.message,
2071 stack: details.source,
2072 todo: details.todo
2073 };
2074 this.testReport.pushAssertion(assertion);
2075 emit("assertion", assertion);
2076 },
2077
2078
2079 resolvePromise: function resolvePromise(promise, phase) {
2080 var then,
2081 resume,
2082 message,
2083 test = this;
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 () {
2090 resume();
2091 });
2092 } else {
2093 then.call(promise, function () {
2094 resume();
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));
2098
2099 // Else next test will carry the responsibility
2100 saveGlobal();
2101
2102 // Unblock
2103 internalRecover(test);
2104 });
2105 }
2106 }
2107 }
2108 },
2109
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;
2115
2116 function moduleChainNameMatch(testModule) {
2117 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
2118 if (testModuleName === module) {
2119 return true;
2120 } else if (testModule.parentModule) {
2121 return moduleChainNameMatch(testModule.parentModule);
2122 } else {
2123 return false;
2124 }
2125 }
2126
2127 function moduleChainIdMatch(testModule) {
2128 return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
2129 }
2130
2131 // Internally-generated tests are always valid
2132 if (this.callback && this.callback.validTest) {
2133 return true;
2134 }
2135
2136 if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
2137
2138 return false;
2139 }
2140
2141 if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
2142
2143 return false;
2144 }
2145
2146 if (module && !moduleChainNameMatch(this.module)) {
2147 return false;
2148 }
2149
2150 if (!filter) {
2151 return true;
2152 }
2153
2154 return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
2155 },
2156
2157 regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
2158 var regex = new RegExp(pattern, flags);
2159 var match = regex.test(fullName);
2160
2161 return match !== exclude;
2162 },
2163
2164 stringFilter: function stringFilter(filter, fullName) {
2165 filter = filter.toLowerCase();
2166 fullName = fullName.toLowerCase();
2167
2168 var include = filter.charAt(0) !== "!";
2169 if (!include) {
2170 filter = filter.slice(1);
2171 }
2172
2173 // If the filter matches, we need to honour include
2174 if (fullName.indexOf(filter) !== -1) {
2175 return include;
2176 }
2177
2178 // Otherwise, do the opposite
2179 return !include;
2180 }
2181 };
2182
2183 function pushFailure() {
2184 if (!config.current) {
2185 throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
2186 }
2187
2188 // Gets current test obj
2189 var currentTest = config.current;
2190
2191 return currentTest.pushFailure.apply(currentTest, arguments);
2192 }
2193
2194 function saveGlobal() {
2195 config.pollution = [];
2196
2197 if (config.noglobals) {
2198 for (var key in global$1) {
2199 if (hasOwn.call(global$1, key)) {
2200
2201 // In Opera sometimes DOM element ids show up here, ignore them
2202 if (/^qunit-test-output/.test(key)) {
2203 continue;
2204 }
2205 config.pollution.push(key);
2206 }
2207 }
2208 }
2209 }
2210
2211 function checkPollution() {
2212 var newGlobals,
2213 deletedGlobals,
2214 old = config.pollution;
2215
2216 saveGlobal();
2217
2218 newGlobals = diff(config.pollution, old);
2219 if (newGlobals.length > 0) {
2220 pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
2221 }
2222
2223 deletedGlobals = diff(old, config.pollution);
2224 if (deletedGlobals.length > 0) {
2225 pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
2226 }
2227 }
2228
2229 // Will be exposed as QUnit.test
2230 function test(testName, callback) {
2231 if (focused$1) {
2232 return;
2233 }
2234
2235 var newTest = new Test({
2236 testName: testName,
2237 callback: callback
2238 });
2239
2240 newTest.queue();
2241 }
2242
2243 function todo(testName, callback) {
2244 if (focused$1) {
2245 return;
2246 }
2247
2248 var newTest = new Test({
2249 testName: testName,
2250 callback: callback,
2251 todo: true
2252 });
2253
2254 newTest.queue();
2255 }
2256
2257 // Will be exposed as QUnit.skip
2258 function skip(testName) {
2259 if (focused$1) {
2260 return;
2261 }
2262
2263 var test = new Test({
2264 testName: testName,
2265 skip: true
2266 });
2267
2268 test.queue();
2269 }
2270
2271 // Will be exposed as QUnit.only
2272 function only(testName, callback) {
2273 if (focused$1) {
2274 return;
2275 }
2276
2277 config.queue.length = 0;
2278 focused$1 = true;
2279
2280 var newTest = new Test({
2281 testName: testName,
2282 callback: callback
2283 });
2284
2285 newTest.queue();
2286 }
2287
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;
2292
2293 // Set a recovery timeout, if so configured.
2294 if (defined.setTimeout) {
2295 var timeoutDuration = void 0;
2296
2297 if (typeof test.timeout === "number") {
2298 timeoutDuration = test.timeout;
2299 } else if (typeof config.testTimeout === "number") {
2300 timeoutDuration = config.testTimeout;
2301 }
2302
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);
2309 }
2310 }
2311
2312 var released = false;
2313 return function resume() {
2314 if (released) {
2315 return;
2316 }
2317
2318 released = true;
2319 test.semaphore -= 1;
2320 internalStart(test);
2321 };
2322 }
2323
2324 // Forcefully release all processing holds.
2325 function internalRecover(test) {
2326 test.semaphore = 0;
2327 internalStart(test);
2328 }
2329
2330 // Release a processing hold, scheduling a resumption attempt if no holds remain.
2331 function internalStart(test) {
2332
2333 // If semaphore is non-numeric, throw error
2334 if (isNaN(test.semaphore)) {
2335 test.semaphore = 0;
2336
2337 pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
2338 return;
2339 }
2340
2341 // Don't start until equal number of stop-calls
2342 if (test.semaphore > 0) {
2343 return;
2344 }
2345
2346 // Throw an Error if start is called more often than stop
2347 if (test.semaphore < 0) {
2348 test.semaphore = 0;
2349
2350 pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
2351 return;
2352 }
2353
2354 // Add a slight delay to allow more assertions etc.
2355 if (defined.setTimeout) {
2356 if (config.timeout) {
2357 clearTimeout(config.timeout);
2358 }
2359 config.timeout = setTimeout(function () {
2360 if (test.semaphore > 0) {
2361 return;
2362 }
2363
2364 if (config.timeout) {
2365 clearTimeout(config.timeout);
2366 }
2367
2368 begin();
2369 });
2370 } else {
2371 begin();
2372 }
2373 }
2374
2375 function collectTests(module) {
2376 var tests = [].concat(module.tests);
2377 var modules = [].concat(toConsumableArray(module.childModules));
2378
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));
2384 }
2385
2386 return tests;
2387 }
2388
2389 function numberOfTests(module) {
2390 return collectTests(module).length;
2391 }
2392
2393 function numberOfUnskippedTests(module) {
2394 return collectTests(module).filter(function (test) {
2395 return !test.skip;
2396 }).length;
2397 }
2398
2399 function notifyTestsRan(module, skipped) {
2400 module.testsRun++;
2401 if (!skipped) {
2402 module.unskippedTestsRun++;
2403 }
2404 while (module = module.parentModule) {
2405 module.testsRun++;
2406 if (!skipped) {
2407 module.unskippedTestsRun++;
2408 }
2409 }
2410 }
2411
2412 /**
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.
2417 */
2418 function consoleProxy(method) {
2419 return function () {
2420 if (console) {
2421 console[method].apply(console, arguments);
2422 }
2423 };
2424 }
2425
2426 var Logger = {
2427 warn: consoleProxy("warn")
2428 };
2429
2430 var Assert = function () {
2431 function Assert(testContext) {
2432 classCallCheck(this, Assert);
2433
2434 this.test = testContext;
2435 }
2436
2437 // Assert helpers
2438
2439 createClass(Assert, [{
2440 key: "timeout",
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");
2444 }
2445
2446 this.test.timeout = duration;
2447 }
2448
2449 // Documents a "step", which is a string value, in a test as a passing assertion
2450
2451 }, {
2452 key: "step",
2453 value: function step(message) {
2454 var assertionMessage = message;
2455 var result = !!message;
2456
2457 this.test.steps.push(message);
2458
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";
2463 result = false;
2464 }
2465
2466 this.pushResult({
2467 result: result,
2468 message: assertionMessage
2469 });
2470 }
2471
2472 // Verifies the steps in a test match a given array of string values
2473
2474 }, {
2475 key: "verifySteps",
2476 value: function verifySteps(steps, message) {
2477
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;
2482 }
2483
2484 // Specify the number of expected assertions to guarantee that failed test
2485 // (no assertions are run at all) don't slip through.
2486
2487 }, {
2488 key: "expect",
2489 value: function expect(asserts) {
2490 if (arguments.length === 1) {
2491 this.test.expected = asserts;
2492 } else {
2493 return this.test.expected;
2494 }
2495 }
2496
2497 // Put a hold on processing and return a function that will release it a maximum of once.
2498
2499 }, {
2500 key: "async",
2501 value: function async(count) {
2502 var test$$1 = this.test;
2503
2504 var popped = false,
2505 acceptCallCount = count;
2506
2507 if (typeof acceptCallCount === "undefined") {
2508 acceptCallCount = 1;
2509 }
2510
2511 var resume = internalStop(test$$1);
2512
2513 return function done() {
2514 if (config.current !== test$$1) {
2515 throw Error("assert.async callback called after test finished.");
2516 }
2517
2518 if (popped) {
2519 test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
2520 return;
2521 }
2522
2523 acceptCallCount -= 1;
2524 if (acceptCallCount > 0) {
2525 return;
2526 }
2527
2528 popped = true;
2529 resume();
2530 };
2531 }
2532
2533 // Exports test.push() to the user API
2534 // Alias of pushResult.
2535
2536 }, {
2537 key: "push",
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).");
2540
2541 var currentAssert = this instanceof Assert ? this : config.current.assert;
2542 return currentAssert.pushResult({
2543 result: result,
2544 actual: actual,
2545 expected: expected,
2546 message: message,
2547 negative: negative
2548 });
2549 }
2550 }, {
2551 key: "pushResult",
2552 value: function pushResult(resultInfo) {
2553
2554 // Destructure of resultInfo = { result, actual, expected, message, negative }
2555 var assert = this;
2556 var currentTest = assert instanceof Assert && assert.test || config.current;
2557
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.
2563 if (!currentTest) {
2564 throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
2565 }
2566
2567 if (!(assert instanceof Assert)) {
2568 assert = currentTest.assert;
2569 }
2570
2571 return assert.test.pushResult(resultInfo);
2572 }
2573 }, {
2574 key: "ok",
2575 value: function ok(result, message) {
2576 if (!message) {
2577 message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
2578 }
2579
2580 this.pushResult({
2581 result: !!result,
2582 actual: result,
2583 expected: true,
2584 message: message
2585 });
2586 }
2587 }, {
2588 key: "notOk",
2589 value: function notOk(result, message) {
2590 if (!message) {
2591 message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
2592 }
2593
2594 this.pushResult({
2595 result: !result,
2596 actual: result,
2597 expected: false,
2598 message: message
2599 });
2600 }
2601 }, {
2602 key: "equal",
2603 value: function equal(actual, expected, message) {
2604
2605 // eslint-disable-next-line eqeqeq
2606 var result = expected == actual;
2607
2608 this.pushResult({
2609 result: result,
2610 actual: actual,
2611 expected: expected,
2612 message: message
2613 });
2614 }
2615 }, {
2616 key: "notEqual",
2617 value: function notEqual(actual, expected, message) {
2618
2619 // eslint-disable-next-line eqeqeq
2620 var result = expected != actual;
2621
2622 this.pushResult({
2623 result: result,
2624 actual: actual,
2625 expected: expected,
2626 message: message,
2627 negative: true
2628 });
2629 }
2630 }, {
2631 key: "propEqual",
2632 value: function propEqual(actual, expected, message) {
2633 actual = objectValues(actual);
2634 expected = objectValues(expected);
2635
2636 this.pushResult({
2637 result: equiv(actual, expected),
2638 actual: actual,
2639 expected: expected,
2640 message: message
2641 });
2642 }
2643 }, {
2644 key: "notPropEqual",
2645 value: function notPropEqual(actual, expected, message) {
2646 actual = objectValues(actual);
2647 expected = objectValues(expected);
2648
2649 this.pushResult({
2650 result: !equiv(actual, expected),
2651 actual: actual,
2652 expected: expected,
2653 message: message,
2654 negative: true
2655 });
2656 }
2657 }, {
2658 key: "deepEqual",
2659 value: function deepEqual(actual, expected, message) {
2660 this.pushResult({
2661 result: equiv(actual, expected),
2662 actual: actual,
2663 expected: expected,
2664 message: message
2665 });
2666 }
2667 }, {
2668 key: "notDeepEqual",
2669 value: function notDeepEqual(actual, expected, message) {
2670 this.pushResult({
2671 result: !equiv(actual, expected),
2672 actual: actual,
2673 expected: expected,
2674 message: message,
2675 negative: true
2676 });
2677 }
2678 }, {
2679 key: "strictEqual",
2680 value: function strictEqual(actual, expected, message) {
2681 this.pushResult({
2682 result: expected === actual,
2683 actual: actual,
2684 expected: expected,
2685 message: message
2686 });
2687 }
2688 }, {
2689 key: "notStrictEqual",
2690 value: function notStrictEqual(actual, expected, message) {
2691 this.pushResult({
2692 result: expected !== actual,
2693 actual: actual,
2694 expected: expected,
2695 message: message,
2696 negative: true
2697 });
2698 }
2699 }, {
2700 key: "throws",
2701 value: function throws(block, expected, message) {
2702 var actual = void 0,
2703 result = false;
2704
2705 var currentTest = this instanceof Assert && this.test || config.current;
2706
2707 // 'expected' is optional unless doing string comparison
2708 if (objectType(expected) === "string") {
2709 if (message == null) {
2710 message = expected;
2711 expected = null;
2712 } else {
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.");
2714 }
2715 }
2716
2717 currentTest.ignoreGlobalErrors = true;
2718 try {
2719 block.call(currentTest.testEnvironment);
2720 } catch (e) {
2721 actual = e;
2722 }
2723 currentTest.ignoreGlobalErrors = false;
2724
2725 if (actual) {
2726 var expectedType = objectType(expected);
2727
2728 // We don't want to validate thrown error
2729 if (!expected) {
2730 result = true;
2731 expected = null;
2732
2733 // Expected is a regexp
2734 } else if (expectedType === "regexp") {
2735 result = expected.test(errorString(actual));
2736
2737 // Expected is a constructor, maybe an Error constructor
2738 } else if (expectedType === "function" && actual instanceof expected) {
2739 result = true;
2740
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;
2744
2745 // Expected is a validation function which returns true if validation passed
2746 } else if (expectedType === "function" && expected.call({}, actual) === true) {
2747 expected = null;
2748 result = true;
2749 }
2750 }
2751
2752 currentTest.assert.pushResult({
2753 result: result,
2754 actual: actual,
2755 expected: expected,
2756 message: message
2757 });
2758 }
2759 }, {
2760 key: "rejects",
2761 value: function rejects(promise, expected, message) {
2762 var result = false;
2763
2764 var currentTest = this instanceof Assert && this.test || config.current;
2765
2766 // 'expected' is optional unless doing string comparison
2767 if (objectType(expected) === "string") {
2768 if (message === undefined) {
2769 message = expected;
2770 expected = undefined;
2771 } else {
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.";
2773
2774 currentTest.assert.pushResult({
2775 result: false,
2776 message: message
2777 });
2778
2779 return;
2780 }
2781 }
2782
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.";
2786
2787 currentTest.assert.pushResult({
2788 result: false,
2789 message: _message,
2790 actual: promise
2791 });
2792
2793 return;
2794 }
2795
2796 var done = this.async();
2797
2798 return then.call(promise, function handleFulfillment() {
2799 var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject.";
2800
2801 currentTest.assert.pushResult({
2802 result: false,
2803 message: message,
2804 actual: promise
2805 });
2806
2807 done();
2808 }, function handleRejection(actual) {
2809 var expectedType = objectType(expected);
2810
2811 // We don't want to validate
2812 if (expected === undefined) {
2813 result = true;
2814 expected = actual;
2815
2816 // Expected is a regexp
2817 } else if (expectedType === "regexp") {
2818 result = expected.test(errorString(actual));
2819
2820 // Expected is a constructor, maybe an Error constructor
2821 } else if (expectedType === "function" && actual instanceof expected) {
2822 result = true;
2823
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;
2827
2828 // Expected is a validation function which returns true if validation passed
2829 } else {
2830 if (expectedType === "function") {
2831 result = expected.call({}, actual) === true;
2832 expected = null;
2833
2834 // Expected is some other invalid type
2835 } else {
2836 result = false;
2837 message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + ".";
2838 }
2839 }
2840
2841 currentTest.assert.pushResult({
2842 result: result,
2843 actual: actual,
2844 expected: expected,
2845 message: message
2846 });
2847
2848 done();
2849 });
2850 }
2851 }]);
2852 return Assert;
2853 }();
2854
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
2858
2859
2860 Assert.prototype.raises = Assert.prototype["throws"];
2861
2862 /**
2863 * Converts an error into a simple string for comparisons.
2864 *
2865 * @param {Error} error
2866 * @return {String}
2867 */
2868 function errorString(error) {
2869 var resultErrorString = error.toString();
2870
2871 if (resultErrorString.substring(0, 7) === "[object") {
2872 var name = error.name ? error.name.toString() : "Error";
2873 var message = error.message ? error.message.toString() : "";
2874
2875 if (name && message) {
2876 return name + ": " + message;
2877 } else if (name) {
2878 return name;
2879 } else if (message) {
2880 return message;
2881 } else {
2882 return "Error";
2883 }
2884 } else {
2885 return resultErrorString;
2886 }
2887 }
2888
2889 /* global module, exports, define */
2890 function exportQUnit(QUnit) {
2891
2892 if (defined.document) {
2893
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.");
2897 }
2898
2899 window.QUnit = QUnit;
2900 }
2901
2902 // For nodejs
2903 if (typeof module !== "undefined" && module && module.exports) {
2904 module.exports = QUnit;
2905
2906 // For consistency with CommonJS environments' exports
2907 module.exports.QUnit = QUnit;
2908 }
2909
2910 // For CommonJS with exports, but without module.exports, like Rhino
2911 if (typeof exports !== "undefined" && exports) {
2912 exports.QUnit = QUnit;
2913 }
2914
2915 if (typeof define === "function" && define.amd) {
2916 define(function () {
2917 return QUnit;
2918 });
2919 QUnit.config.autostart = false;
2920 }
2921
2922 // For Web/Service Workers
2923 if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
2924 self$1.QUnit = QUnit;
2925 }
2926 }
2927
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];
2935 }
2936
2937 if (config.current) {
2938 if (config.current.ignoreGlobalErrors) {
2939 return true;
2940 }
2941 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2942 } else {
2943 test("global failure", extend(function () {
2944 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2945 }, { validTest: true }));
2946 }
2947
2948 return false;
2949 }
2950
2951 // Handle an unhandled rejection
2952 function onUnhandledRejection(reason) {
2953 var resultInfo = {
2954 result: false,
2955 message: reason.message || "error",
2956 actual: reason,
2957 source: reason.stack || sourceFromStacktrace(3)
2958 };
2959
2960 var currentTest = config.current;
2961 if (currentTest) {
2962 currentTest.assert.pushResult(resultInfo);
2963 } else {
2964 test("global failure", extend(function (assert) {
2965 assert.pushResult(resultInfo);
2966 }, { validTest: true }));
2967 }
2968 }
2969
2970 var QUnit = {};
2971 var globalSuite = new SuiteReport();
2972
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;
2977
2978 var globalStartCalled = false;
2979 var runStarted = false;
2980
2981 // Figure out if we're running the tests from a server or not
2982 QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
2983
2984 // Expose the current QUnit version
2985 QUnit.version = "2.6.2";
2986
2987 extend(QUnit, {
2988 on: on,
2989
2990 module: module$1,
2991
2992 test: test,
2993
2994 todo: todo,
2995
2996 skip: skip,
2997
2998 only: only,
2999
3000 start: function start(count) {
3001 var globalStartAlreadyCalled = globalStartCalled;
3002
3003 if (!config.current) {
3004 globalStartCalled = true;
3005
3006 if (runStarted) {
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) {
3013
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;
3017
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) {
3021 QUnit.load();
3022 }
3023
3024 return;
3025 }
3026 } else {
3027 throw new Error("QUnit.start cannot be called inside a test context.");
3028 }
3029
3030 scheduleBegin();
3031 },
3032
3033 config: config,
3034
3035 is: is,
3036
3037 objectType: objectType,
3038
3039 extend: extend,
3040
3041 load: function load() {
3042 config.pageLoaded = true;
3043
3044 // Initialize the configuration options
3045 extend(config, {
3046 stats: { all: 0, bad: 0 },
3047 started: 0,
3048 updateRate: 1000,
3049 autostart: true,
3050 filter: ""
3051 }, true);
3052
3053 if (!runStarted) {
3054 config.blocking = false;
3055
3056 if (config.autostart) {
3057 scheduleBegin();
3058 }
3059 }
3060 },
3061
3062 stack: function stack(offset) {
3063 offset = (offset || 0) + 2;
3064 return sourceFromStacktrace(offset);
3065 },
3066
3067 onError: onError,
3068
3069 onUnhandledRejection: onUnhandledRejection
3070 });
3071
3072 QUnit.pushFailure = pushFailure;
3073 QUnit.assert = Assert.prototype;
3074 QUnit.equiv = equiv;
3075 QUnit.dump = dump;
3076
3077 registerLoggingCallbacks(QUnit);
3078
3079 function scheduleBegin() {
3080
3081 runStarted = true;
3082
3083 // Add a slight delay to allow definition of more modules and tests.
3084 if (defined.setTimeout) {
3085 setTimeout(function () {
3086 begin();
3087 });
3088 } else {
3089 begin();
3090 }
3091 }
3092
3093 function begin() {
3094 var i,
3095 l,
3096 modulesLog = [];
3097
3098 // If the test run hasn't officially begun yet
3099 if (!config.started) {
3100
3101 // Record the time of the test run's beginning
3102 config.started = now();
3103
3104 // Delete the loose unnamed module if unused.
3105 if (config.modules[0].name === "" && config.modules[0].tests.length === 0) {
3106 config.modules.shift();
3107 }
3108
3109 // Avoid unnecessary information by not logging modules' test environments
3110 for (i = 0, l = config.modules.length; i < l; i++) {
3111 modulesLog.push({
3112 name: config.modules[i].name,
3113 tests: config.modules[i].tests
3114 });
3115 }
3116
3117 // The test run is officially beginning now
3118 emit("runStart", globalSuite.start(true));
3119 runLoggingCallbacks("begin", {
3120 totalTests: Test.count,
3121 modules: modulesLog
3122 });
3123 }
3124
3125 config.blocking = false;
3126 ProcessingQueue.advance();
3127 }
3128
3129 exportQUnit(QUnit);
3130
3131 (function () {
3132
3133 if (typeof window === "undefined" || typeof document === "undefined") {
3134 return;
3135 }
3136
3137 var config = QUnit.config,
3138 hasOwn = Object.prototype.hasOwnProperty;
3139
3140 // Stores fixture HTML for resetting later
3141 function storeFixture() {
3142
3143 // Avoid overwriting user-defined values
3144 if (hasOwn.call(config, "fixture")) {
3145 return;
3146 }
3147
3148 var fixture = document.getElementById("qunit-fixture");
3149 if (fixture) {
3150 config.fixture = fixture.cloneNode(true);
3151 }
3152 }
3153
3154 QUnit.begin(storeFixture);
3155
3156 // Resets the fixture DOM element if available.
3157 function resetFixture() {
3158 if (config.fixture == null) {
3159 return;
3160 }
3161
3162 var fixture = document.getElementById("qunit-fixture");
3163 var resetFixtureType = _typeof(config.fixture);
3164 if (resetFixtureType === "string") {
3165
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);
3171 } else {
3172 var clonedFixture = config.fixture.cloneNode(true);
3173 fixture.parentNode.replaceChild(clonedFixture, fixture);
3174 }
3175 }
3176
3177 QUnit.testStart(resetFixture);
3178 })();
3179
3180 (function () {
3181
3182 // Only interact with URLs via window.location
3183 var location = typeof window !== "undefined" && window.location;
3184 if (!location) {
3185 return;
3186 }
3187
3188 var urlParams = getUrlParams();
3189
3190 QUnit.urlParams = urlParams;
3191
3192 // Match module/test by inclusion in an array
3193 QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
3194 QUnit.config.testId = [].concat(urlParams.testId || []);
3195
3196 // Exact case-insensitive match of the module name
3197 QUnit.config.module = urlParams.module;
3198
3199 // Regular expression or case-insenstive substring match against "moduleName: testName"
3200 QUnit.config.filter = urlParams.filter;
3201
3202 // Test order randomization
3203 if (urlParams.seed === true) {
3204
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;
3209 }
3210
3211 // Add URL-parameter-mapped config values with UI form rendering data
3212 QUnit.config.urlConfig.push({
3213 id: "hidepassed",
3214 label: "Hide passed tests",
3215 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
3216 }, {
3217 id: "noglobals",
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."
3220 }, {
3221 id: "notrycatch",
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."
3224 });
3225
3226 QUnit.begin(function () {
3227 var i,
3228 option,
3229 urlConfig = QUnit.config.urlConfig;
3230
3231 for (i = 0; i < urlConfig.length; i++) {
3232
3233 // Options can be either strings or objects with nonempty "id" properties
3234 option = QUnit.config.urlConfig[i];
3235 if (typeof option !== "string") {
3236 option = option.id;
3237 }
3238
3239 if (QUnit.config[option] === undefined) {
3240 QUnit.config[option] = urlParams[option];
3241 }
3242 }
3243 });
3244
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;
3250
3251 for (i = 0; i < length; i++) {
3252 if (params[i]) {
3253 param = params[i].split("=");
3254 name = decodeQueryParam(param[0]);
3255
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);
3260 } else {
3261 urlParams[name] = value;
3262 }
3263 }
3264 }
3265
3266 return urlParams;
3267 }
3268
3269 function decodeQueryParam(param) {
3270 return decodeURIComponent(param.replace(/\+/g, "%20"));
3271 }
3272 })();
3273
3274 var stats = {
3275 passedTests: 0,
3276 failedTests: 0,
3277 skippedTests: 0,
3278 todoTests: 0
3279 };
3280
3281 // Escape text for attribute or text content.
3282 function escapeText(s) {
3283 if (!s) {
3284 return "";
3285 }
3286 s = s + "";
3287
3288 // Both single quotes and double quotes (for attributes)
3289 return s.replace(/['"<>&]/g, function (s) {
3290 switch (s) {
3291 case "'":
3292 return "&#039;";
3293 case "\"":
3294 return "&quot;";
3295 case "<":
3296 return "&lt;";
3297 case ">":
3298 return "&gt;";
3299 case "&":
3300 return "&amp;";
3301 }
3302 });
3303 }
3304
3305 (function () {
3306
3307 // Don't load the HTML Reporter on non-browser environments
3308 if (typeof window === "undefined" || !window.document) {
3309 return;
3310 }
3311
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 }),
3318 modulesList = [];
3319
3320 function addEvent(elem, type, fn) {
3321 elem.addEventListener(type, fn, false);
3322 }
3323
3324 function removeEvent(elem, type, fn) {
3325 elem.removeEventListener(type, fn, false);
3326 }
3327
3328 function addEvents(elems, type, fn) {
3329 var i = elems.length;
3330 while (i--) {
3331 addEvent(elems[i], type, fn);
3332 }
3333 }
3334
3335 function hasClass(elem, name) {
3336 return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
3337 }
3338
3339 function addClass(elem, name) {
3340 if (!hasClass(elem, name)) {
3341 elem.className += (elem.className ? " " : "") + name;
3342 }
3343 }
3344
3345 function toggleClass(elem, name, force) {
3346 if (force || typeof force === "undefined" && !hasClass(elem, name)) {
3347 addClass(elem, name);
3348 } else {
3349 removeClass(elem, name);
3350 }
3351 }
3352
3353 function removeClass(elem, name) {
3354 var set = " " + elem.className + " ";
3355
3356 // Class name may appear multiple times
3357 while (set.indexOf(" " + name + " ") >= 0) {
3358 set = set.replace(" " + name + " ", " ");
3359 }
3360
3361 // Trim for prettiness
3362 elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
3363 }
3364
3365 function id(name) {
3366 return document$$1.getElementById && document$$1.getElementById(name);
3367 }
3368
3369 function abortTests() {
3370 var abortButton = id("qunit-abort-tests-button");
3371 if (abortButton) {
3372 abortButton.disabled = true;
3373 abortButton.innerHTML = "Aborting...";
3374 }
3375 QUnit.config.queue.length = 0;
3376 return false;
3377 }
3378
3379 function interceptNavigation(ev) {
3380 applyUrlParams();
3381
3382 if (ev && ev.preventDefault) {
3383 ev.preventDefault();
3384 }
3385
3386 return false;
3387 }
3388
3389 function getUrlConfigHtml() {
3390 var i,
3391 j,
3392 val,
3393 escaped,
3394 escapedTooltip,
3395 selection = false,
3396 urlConfig = config.urlConfig,
3397 urlConfigHtml = "";
3398
3399 for (i = 0; i < urlConfig.length; i++) {
3400
3401 // Options can be either strings or objects with nonempty "id" properties
3402 val = config.urlConfig[i];
3403 if (typeof val === "string") {
3404 val = {
3405 id: val,
3406 label: val
3407 };
3408 }
3409
3410 escaped = escapeText(val.id);
3411 escapedTooltip = escapeText(val.tooltip);
3412
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>";
3415 } else {
3416 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val.label + ": </label><select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3417
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>";
3422 }
3423 } else {
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>";
3427 }
3428 }
3429 }
3430 if (config[val.id] && !selection) {
3431 escaped = escapeText(config[val.id]);
3432 urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3433 }
3434 urlConfigHtml += "</select>";
3435 }
3436 }
3437
3438 return urlConfigHtml;
3439 }
3440
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() {
3444 var updatedUrl,
3445 value,
3446 tests,
3447 field = this,
3448 params = {};
3449
3450 // Detect if field is a select menu or a checkbox
3451 if ("selectedIndex" in field) {
3452 value = field.options[field.selectedIndex].value || undefined;
3453 } else {
3454 value = field.checked ? field.defaultValue || true : undefined;
3455 }
3456
3457 params[field.name] = value;
3458 updatedUrl = setUrl(params);
3459
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");
3465 if (tests) {
3466 toggleClass(tests, "hidepass", value || false);
3467 }
3468 window.history.replaceState(null, "", updatedUrl);
3469 } else {
3470 window.location = updatedUrl;
3471 }
3472 }
3473
3474 function setUrl(params) {
3475 var key,
3476 arrValue,
3477 i,
3478 querystring = "?",
3479 location = window.location;
3480
3481 params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
3482
3483 for (key in params) {
3484
3485 // Skip inherited or undefined properties
3486 if (hasOwn.call(params, key) && params[key] !== undefined) {
3487
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]);
3495 }
3496 querystring += "&";
3497 }
3498 }
3499 }
3500 return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
3501 }
3502
3503 function applyUrlParams() {
3504 var i,
3505 selectedModules = [],
3506 modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
3507 filter = id("qunit-filter-input").value;
3508
3509 for (i = 0; i < modulesList.length; i++) {
3510 if (modulesList[i].checked) {
3511 selectedModules.push(modulesList[i].value);
3512 }
3513 }
3514
3515 window.location = setUrl({
3516 filter: filter === "" ? undefined : filter,
3517 moduleId: selectedModules.length === 0 ? undefined : selectedModules,
3518
3519 // Remove module and testId filter
3520 module: undefined,
3521 testId: undefined
3522 });
3523 }
3524
3525 function toolbarUrlConfigContainer() {
3526 var urlConfigContainer = document$$1.createElement("span");
3527
3528 urlConfigContainer.innerHTML = getUrlConfigHtml();
3529 addClass(urlConfigContainer, "qunit-url-config");
3530
3531 addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
3532 addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
3533
3534 return urlConfigContainer;
3535 }
3536
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);
3542 return button;
3543 }
3544
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");
3550
3551 addClass(filter, "qunit-filter");
3552
3553 label.innerHTML = "Filter: ";
3554
3555 input.type = "text";
3556 input.value = config.filter || "";
3557 input.name = "filter";
3558 input.id = "qunit-filter-input";
3559
3560 button.innerHTML = "Go";
3561
3562 label.appendChild(input);
3563
3564 filter.appendChild(label);
3565 filter.appendChild(document$$1.createTextNode(" "));
3566 filter.appendChild(button);
3567 addEvent(filter, "submit", interceptNavigation);
3568
3569 return filter;
3570 }
3571
3572 function moduleListHtml() {
3573 var i,
3574 checked,
3575 html = "";
3576
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>";
3581 }
3582 }
3583
3584 return html;
3585 }
3586
3587 function toolbarModuleFilter() {
3588 var allCheckbox,
3589 commit,
3590 reset,
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"),
3597 dirty = false;
3598
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);
3605
3606 label.id = "qunit-modulefilter-search-container";
3607 label.innerHTML = "Module: ";
3608 label.appendChild(moduleSearch);
3609
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);
3616
3617 dropDownList.id = "qunit-modulefilter-dropdown-list";
3618 dropDownList.innerHTML = moduleListHtml();
3619
3620 dropDown.id = "qunit-modulefilter-dropdown";
3621 dropDown.style.display = "none";
3622 dropDown.appendChild(actions);
3623 dropDown.appendChild(dropDownList);
3624 addEvent(dropDown, "change", selectionChange);
3625 selectionChange();
3626
3627 moduleFilter.id = "qunit-modulefilter";
3628 moduleFilter.appendChild(label);
3629 moduleFilter.appendChild(dropDown);
3630 addEvent(moduleFilter, "submit", interceptNavigation);
3631 addEvent(moduleFilter, "reset", function () {
3632
3633 // Let the reset happen, then update styles
3634 window.setTimeout(selectionChange);
3635 });
3636
3637 // Enables show/hide for the dropdown
3638 function searchFocus() {
3639 if (dropDown.style.display !== "none") {
3640 return;
3641 }
3642
3643 dropDown.style.display = "block";
3644 addEvent(document$$1, "click", hideHandler);
3645 addEvent(document$$1, "keydown", hideHandler);
3646
3647 // Hide on Escape keydown or outside-container click
3648 function hideHandler(e) {
3649 var inContainer = moduleFilter.contains(e.target);
3650
3651 if (e.keyCode === 27 || !inContainer) {
3652 if (e.keyCode === 27 && inContainer) {
3653 moduleSearch.focus();
3654 }
3655 dropDown.style.display = "none";
3656 removeEvent(document$$1, "click", hideHandler);
3657 removeEvent(document$$1, "keydown", hideHandler);
3658 moduleSearch.value = "";
3659 searchInput();
3660 }
3661 }
3662 }
3663
3664 // Processes module search box input
3665 function searchInput() {
3666 var i,
3667 item,
3668 searchText = moduleSearch.value.toLowerCase(),
3669 listItems = dropDownList.children;
3670
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 = "";
3675 } else {
3676 item.style.display = "none";
3677 }
3678 }
3679 }
3680
3681 // Processes selection changes
3682 function selectionChange(evt) {
3683 var i,
3684 item,
3685 checkbox = evt && evt.target || allCheckbox,
3686 modulesList = dropDownList.getElementsByTagName("input"),
3687 selectedNames = [];
3688
3689 toggleClass(checkbox.parentNode, "checked", checkbox.checked);
3690
3691 dirty = false;
3692 if (checkbox.checked && checkbox !== allCheckbox) {
3693 allCheckbox.checked = false;
3694 removeClass(allCheckbox.parentNode, "checked");
3695 }
3696 for (i = 0; i < modulesList.length; i++) {
3697 item = modulesList[i];
3698 if (!evt) {
3699 toggleClass(item.parentNode, "checked", item.checked);
3700 } else if (checkbox === allCheckbox && checkbox.checked) {
3701 item.checked = false;
3702 removeClass(item.parentNode, "checked");
3703 }
3704 dirty = dirty || item.checked !== item.defaultChecked;
3705 if (item.checked) {
3706 selectedNames.push(item.parentNode.textContent);
3707 }
3708 }
3709
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);
3713 }
3714
3715 return moduleFilter;
3716 }
3717
3718 function appendToolbar() {
3719 var toolbar = id("qunit-testrunner-toolbar");
3720
3721 if (toolbar) {
3722 toolbar.appendChild(toolbarUrlConfigContainer());
3723 toolbar.appendChild(toolbarModuleFilter());
3724 toolbar.appendChild(toolbarLooseFilter());
3725 toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
3726 }
3727 }
3728
3729 function appendHeader() {
3730 var header = id("qunit-header");
3731
3732 if (header) {
3733 header.innerHTML = "<a href='" + escapeText(unfilteredUrl) + "'>" + header.innerHTML + "</a> ";
3734 }
3735 }
3736
3737 function appendBanner() {
3738 var banner = id("qunit-banner");
3739
3740 if (banner) {
3741 banner.className = "";
3742 }
3743 }
3744
3745 function appendTestResults() {
3746 var tests = id("qunit-tests"),
3747 result = id("qunit-testresult"),
3748 controls;
3749
3750 if (result) {
3751 result.parentNode.removeChild(result);
3752 }
3753
3754 if (tests) {
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 />&#160;</div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
3761 controls = id("qunit-testresult-controls");
3762 }
3763
3764 if (controls) {
3765 controls.appendChild(abortTestsButton());
3766 }
3767 }
3768
3769 function appendFilteredTest() {
3770 var testId = QUnit.config.testId;
3771 if (!testId || testId.length <= 0) {
3772 return "";
3773 }
3774 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl) + "'>Run all tests</a></div>";
3775 }
3776
3777 function appendUserAgent() {
3778 var userAgent = id("qunit-userAgent");
3779
3780 if (userAgent) {
3781 userAgent.innerHTML = "";
3782 userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
3783 }
3784 }
3785
3786 function appendInterface() {
3787 var qunit = id("qunit");
3788
3789 if (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>";
3791 }
3792
3793 appendHeader();
3794 appendBanner();
3795 appendTestResults();
3796 appendUserAgent();
3797 appendToolbar();
3798 }
3799
3800 function appendTestsList(modules) {
3801 var i, l, x, z, test, moduleObj;
3802
3803 for (i = 0, l = modules.length; i < l; i++) {
3804 moduleObj = modules[i];
3805
3806 for (x = 0, z = moduleObj.tests.length; x < z; x++) {
3807 test = moduleObj.tests[x];
3808
3809 appendTest(test.name, test.testId, moduleObj.name);
3810 }
3811 }
3812 }
3813
3814 function appendTest(name, testId, moduleName) {
3815 var title,
3816 rerunTrigger,
3817 testBlock,
3818 assertList,
3819 tests = id("qunit-tests");
3820
3821 if (!tests) {
3822 return;
3823 }
3824
3825 title = document$$1.createElement("strong");
3826 title.innerHTML = getNameHtml(name, moduleName);
3827
3828 rerunTrigger = document$$1.createElement("a");
3829 rerunTrigger.innerHTML = "Rerun";
3830 rerunTrigger.href = setUrl({ testId: testId });
3831
3832 testBlock = document$$1.createElement("li");
3833 testBlock.appendChild(title);
3834 testBlock.appendChild(rerunTrigger);
3835 testBlock.id = "qunit-test-output-" + testId;
3836
3837 assertList = document$$1.createElement("ol");
3838 assertList.className = "qunit-assert-list";
3839
3840 testBlock.appendChild(assertList);
3841
3842 tests.appendChild(testBlock);
3843 }
3844
3845 // HTML Reporter initialization and load
3846 QUnit.begin(function (details) {
3847 var i, moduleObj, tests;
3848
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);
3854 }
3855 }
3856 modulesList.sort(function (a, b) {
3857 return a.localeCompare(b);
3858 });
3859
3860 // Initialize QUnit elements
3861 appendInterface();
3862 appendTestsList(details.modules);
3863 tests = id("qunit-tests");
3864 if (tests && config.hidepassed) {
3865 addClass(tests, "hidepass");
3866 }
3867 });
3868
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(""),
3875 test,
3876 assertLi,
3877 assertList;
3878
3879 // Update remaing tests to aborted
3880 if (abortButton && abortButton.disabled) {
3881 html = "Tests aborted after " + details.runtime + " milliseconds.";
3882
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);
3892 }
3893 }
3894 }
3895
3896 if (banner && (!abortButton || abortButton.disabled === false)) {
3897 banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
3898 }
3899
3900 if (abortButton) {
3901 abortButton.parentNode.removeChild(abortButton);
3902 }
3903
3904 if (tests) {
3905 id("qunit-testresult-display").innerHTML = html;
3906 }
3907
3908 if (config.altertitle && document$$1.title) {
3909
3910 // Show ✖ for good, ✔ for bad suite result in title
3911 // use escape sequences in case file gets loaded with non-utf-8
3912 // charset
3913 document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
3914 }
3915
3916 // Scroll back to top to show results
3917 if (config.scrolltop && window.scrollTo) {
3918 window.scrollTo(0, 0);
3919 }
3920 });
3921
3922 function getNameHtml(name, module) {
3923 var nameHtml = "";
3924
3925 if (module) {
3926 nameHtml = "<span class='module-name'>" + escapeText(module) + "</span>: ";
3927 }
3928
3929 nameHtml += "<span class='test-name'>" + escapeText(name) + "</span>";
3930
3931 return nameHtml;
3932 }
3933
3934 QUnit.testStart(function (details) {
3935 var running, testBlock, bad;
3936
3937 testBlock = id("qunit-test-output-" + details.testId);
3938 if (testBlock) {
3939 testBlock.className = "running";
3940 } else {
3941
3942 // Report later registered tests
3943 appendTest(details.name, details.testId, details.module);
3944 }
3945
3946 running = id("qunit-testresult-display");
3947 if (running) {
3948 bad = QUnit.config.reorder && details.previousFailure;
3949
3950 running.innerHTML = [bad ? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details.name, details.module)].join("");
3951 }
3952 });
3953
3954 function stripHtml(string) {
3955
3956 // Strip tags, html entity and whitespaces
3957 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/&quot;/g, "").replace(/\s+/g, "");
3958 }
3959
3960 QUnit.log(function (details) {
3961 var assertList,
3962 assertLi,
3963 message,
3964 expected,
3965 actual,
3966 diff,
3967 showDiff = false,
3968 testItem = id("qunit-test-output-" + details.testId);
3969
3970 if (!testItem) {
3971 return;
3972 }
3973
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>";
3977
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);
3984 } else {
3985 expected = QUnit.dump.parse(details.expected);
3986 }
3987
3988 actual = QUnit.dump.parse(details.actual);
3989 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected) + "</pre></td></tr>";
3990
3991 if (actual !== expected) {
3992
3993 message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
3994
3995 if (typeof details.actual === "number" && typeof details.expected === "number") {
3996 if (!isNaN(details.actual) && !isNaN(details.expected)) {
3997 showDiff = true;
3998 diff = details.actual - details.expected;
3999 diff = (diff > 0 ? "+" : "") + diff;
4000 }
4001 } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
4002 diff = QUnit.diff(expected, actual);
4003
4004 // don't show diff if there is zero overlap
4005 showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
4006 }
4007
4008 if (showDiff) {
4009 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
4010 }
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>";
4013 } else {
4014 message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
4015 }
4016
4017 if (details.source) {
4018 message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>";
4019 }
4020
4021 message += "</table>";
4022
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>";
4026 }
4027
4028 assertList = testItem.getElementsByTagName("ol")[0];
4029
4030 assertLi = document$$1.createElement("li");
4031 assertLi.className = details.result ? "pass" : "fail";
4032 assertLi.innerHTML = message;
4033 assertList.appendChild(assertLi);
4034 });
4035
4036 QUnit.testDone(function (details) {
4037 var testTitle,
4038 time,
4039 testItem,
4040 assertList,
4041 good,
4042 bad,
4043 testCounts,
4044 skipped,
4045 sourceName,
4046 tests = id("qunit-tests");
4047
4048 if (!tests) {
4049 return;
4050 }
4051
4052 testItem = id("qunit-test-output-" + details.testId);
4053
4054 assertList = testItem.getElementsByTagName("ol")[0];
4055
4056 good = details.passed;
4057 bad = details.failed;
4058
4059 // This test passed if it has no unexpected failed assertions
4060 var testPassed = details.failed > 0 ? details.todo : !details.todo;
4061
4062 if (testPassed) {
4063
4064 // Collapse the passing tests
4065 addClass(assertList, "qunit-collapsed");
4066 } else if (config.collapse) {
4067 if (!collapseNext) {
4068
4069 // Skip collapsing the first failing test
4070 collapseNext = true;
4071 } else {
4072
4073 // Collapse remaining tests
4074 addClass(assertList, "qunit-collapsed");
4075 }
4076 }
4077
4078 // The testItem.firstChild is the test name
4079 testTitle = testItem.firstChild;
4080
4081 testCounts = bad ? "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : "";
4082
4083 testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
4084
4085 if (details.skipped) {
4086 stats.skippedTests++;
4087
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);
4093 } else {
4094 addEvent(testTitle, "click", function () {
4095 toggleClass(assertList, "qunit-collapsed");
4096 });
4097
4098 testItem.className = testPassed ? "pass" : "fail";
4099
4100 if (details.todo) {
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);
4106 }
4107
4108 time = document$$1.createElement("span");
4109 time.className = "runtime";
4110 time.innerHTML = details.runtime + " ms";
4111 testItem.insertBefore(time, assertList);
4112
4113 if (!testPassed) {
4114 stats.failedTests++;
4115 } else if (details.todo) {
4116 stats.todoTests++;
4117 } else {
4118 stats.passedTests++;
4119 }
4120 }
4121
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");
4127 if (testPassed) {
4128 addClass(sourceName, "qunit-collapsed");
4129 }
4130 addEvent(testTitle, "click", function () {
4131 toggleClass(sourceName, "qunit-collapsed");
4132 });
4133 testItem.appendChild(sourceName);
4134 }
4135 });
4136
4137 // Avoid readyState issue with phantomjs
4138 // Ref: #818
4139 var notPhantom = function (p) {
4140 return !(p && p.version && p.version.major > 0);
4141 }(window.phantom);
4142
4143 if (notPhantom && document$$1.readyState === "complete") {
4144 QUnit.load();
4145 } else {
4146 addEvent(window, "load", QUnit.load);
4147 }
4148
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;
4153
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) {
4158 var ret = false;
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];
4162 }
4163
4164 ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
4165 }
4166
4167 // Treat return value as window.onerror itself does,
4168 // Only do our handling if not suppressed.
4169 if (ret !== true) {
4170 var error = {
4171 message: message,
4172 fileName: fileName,
4173 lineNumber: lineNumber
4174 };
4175
4176 ret = QUnit.onError(error);
4177 }
4178
4179 return ret;
4180 };
4181
4182 // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
4183 window.addEventListener("unhandledrejection", function (event) {
4184 QUnit.onUnhandledRejection(event.reason);
4185 });
4186 })();
4187
4188 /*
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.
4192 *
4193 * The original source of google-diff-match-patch is attributable and licensed as follows:
4194 *
4195 * Copyright 2006 Google Inc.
4196 * https://code.google.com/p/google-diff-match-patch/
4197 *
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
4201 *
4202 * https://www.apache.org/licenses/LICENSE-2.0
4203 *
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.
4209 *
4210 * More Info:
4211 * https://code.google.com/p/google-diff-match-patch/
4212 *
4213 * Usage: QUnit.diff(expected, actual)
4214 *
4215 */
4216 QUnit.diff = function () {
4217 function DiffMatchPatch() {}
4218
4219 // DIFF FUNCTIONS
4220
4221 /**
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.'
4225 */
4226 var DIFF_DELETE = -1,
4227 DIFF_INSERT = 1,
4228 DIFF_EQUAL = 0;
4229
4230 /**
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.
4239 */
4240 DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
4241 var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs;
4242
4243 // The diff must be complete in up to 1 second.
4244 deadline = new Date().getTime() + 1000;
4245
4246 // Check for null inputs.
4247 if (text1 === null || text2 === null) {
4248 throw new Error("Null input. (DiffMain)");
4249 }
4250
4251 // Check for equality (speedup).
4252 if (text1 === text2) {
4253 if (text1) {
4254 return [[DIFF_EQUAL, text1]];
4255 }
4256 return [];
4257 }
4258
4259 if (typeof optChecklines === "undefined") {
4260 optChecklines = true;
4261 }
4262
4263 checklines = optChecklines;
4264
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);
4270
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);
4276
4277 // Compute the diff on the middle block.
4278 diffs = this.diffCompute(text1, text2, checklines, deadline);
4279
4280 // Restore the prefix and suffix.
4281 if (commonprefix) {
4282 diffs.unshift([DIFF_EQUAL, commonprefix]);
4283 }
4284 if (commonsuffix) {
4285 diffs.push([DIFF_EQUAL, commonsuffix]);
4286 }
4287 this.diffCleanupMerge(diffs);
4288 return diffs;
4289 };
4290
4291 /**
4292 * Reduce the number of edits by eliminating operationally trivial equalities.
4293 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4294 */
4295 DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
4296 var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
4297 changes = false;
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;
4302
4303 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4304 pointer = 0; // Index of current position.
4305
4306 // Is there an insertion operation before the last equality.
4307 preIns = false;
4308
4309 // Is there a deletion operation before the last equality.
4310 preDel = false;
4311
4312 // Is there an insertion operation after the last equality.
4313 postIns = false;
4314
4315 // Is there a deletion operation after the last equality.
4316 postDel = false;
4317 while (pointer < diffs.length) {
4318
4319 // Equality found.
4320 if (diffs[pointer][0] === DIFF_EQUAL) {
4321 if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
4322
4323 // Candidate found.
4324 equalities[equalitiesLength++] = pointer;
4325 preIns = postIns;
4326 preDel = postDel;
4327 lastequality = diffs[pointer][1];
4328 } else {
4329
4330 // Not a candidate, and can never become one.
4331 equalitiesLength = 0;
4332 lastequality = null;
4333 }
4334 postIns = postDel = false;
4335
4336 // An insertion or deletion.
4337 } else {
4338
4339 if (diffs[pointer][0] === DIFF_DELETE) {
4340 postDel = true;
4341 } else {
4342 postIns = true;
4343 }
4344
4345 /*
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>
4352 */
4353 if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
4354
4355 // Duplicate record.
4356 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4357
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) {
4363
4364 // No changes made which could affect previous entry, keep going.
4365 postIns = postDel = true;
4366 equalitiesLength = 0;
4367 } else {
4368 equalitiesLength--; // Throw away the previous equality.
4369 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4370 postIns = postDel = false;
4371 }
4372 changes = true;
4373 }
4374 }
4375 pointer++;
4376 }
4377
4378 if (changes) {
4379 this.diffCleanupMerge(diffs);
4380 }
4381 };
4382
4383 /**
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.
4388 */
4389 DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
4390 var op,
4391 data,
4392 x,
4393 html = [];
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.
4397 switch (op) {
4398 case DIFF_INSERT:
4399 html[x] = "<ins>" + escapeText(data) + "</ins>";
4400 break;
4401 case DIFF_DELETE:
4402 html[x] = "<del>" + escapeText(data) + "</del>";
4403 break;
4404 case DIFF_EQUAL:
4405 html[x] = "<span>" + escapeText(data) + "</span>";
4406 break;
4407 }
4408 }
4409 return html.join("");
4410 };
4411
4412 /**
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
4417 * string.
4418 */
4419 DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
4420 var pointermid, pointermax, pointermin, pointerstart;
4421
4422 // Quick check for common null cases.
4423 if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
4424 return 0;
4425 }
4426
4427 // Binary search.
4428 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4429 pointermin = 0;
4430 pointermax = Math.min(text1.length, text2.length);
4431 pointermid = pointermax;
4432 pointerstart = 0;
4433 while (pointermin < pointermid) {
4434 if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
4435 pointermin = pointermid;
4436 pointerstart = pointermin;
4437 } else {
4438 pointermax = pointermid;
4439 }
4440 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4441 }
4442 return pointermid;
4443 };
4444
4445 /**
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.
4450 */
4451 DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
4452 var pointermid, pointermax, pointermin, pointerend;
4453
4454 // Quick check for common null cases.
4455 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
4456 return 0;
4457 }
4458
4459 // Binary search.
4460 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4461 pointermin = 0;
4462 pointermax = Math.min(text1.length, text2.length);
4463 pointermid = pointermax;
4464 pointerend = 0;
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;
4469 } else {
4470 pointermax = pointermid;
4471 }
4472 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4473 }
4474 return pointermid;
4475 };
4476
4477 /**
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.
4487 * @private
4488 */
4489 DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
4490 var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
4491
4492 if (!text1) {
4493
4494 // Just add some text (speedup).
4495 return [[DIFF_INSERT, text2]];
4496 }
4497
4498 if (!text2) {
4499
4500 // Just delete some text (speedup).
4501 return [[DIFF_DELETE, text1]];
4502 }
4503
4504 longtext = text1.length > text2.length ? text1 : text2;
4505 shorttext = text1.length > text2.length ? text2 : text1;
4506 i = longtext.indexOf(shorttext);
4507 if (i !== -1) {
4508
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)]];
4511
4512 // Swap insertions for deletions if diff is reversed.
4513 if (text1.length > text2.length) {
4514 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
4515 }
4516 return diffs;
4517 }
4518
4519 if (shorttext.length === 1) {
4520
4521 // Single character string.
4522 // After the previous speedup, the character can't be an equality.
4523 return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4524 }
4525
4526 // Check to see if the problem can be split in two.
4527 hm = this.diffHalfMatch(text1, text2);
4528 if (hm) {
4529
4530 // A half-match was found, sort out the return data.
4531 text1A = hm[0];
4532 text1B = hm[1];
4533 text2A = hm[2];
4534 text2B = hm[3];
4535 midCommon = hm[4];
4536
4537 // Send both pairs off for separate processing.
4538 diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
4539 diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
4540
4541 // Merge the results.
4542 return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
4543 }
4544
4545 if (checklines && text1.length > 100 && text2.length > 100) {
4546 return this.diffLineMode(text1, text2, deadline);
4547 }
4548
4549 return this.diffBisect(text1, text2, deadline);
4550 };
4551
4552 /**
4553 * Do the two texts share a substring which is at least half the length of the
4554 * longer text?
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.
4561 * @private
4562 */
4563 DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
4564 var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
4565
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.
4570 }
4571 dmp = this; // 'this' becomes 'window' in a closure.
4572
4573 /**
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.
4583 * @private
4584 */
4585 function diffHalfMatchI(longtext, shorttext, i) {
4586 var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
4587
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));
4590 j = -1;
4591 bestCommon = "";
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);
4601 }
4602 }
4603 if (bestCommon.length * 2 >= longtext.length) {
4604 return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
4605 } else {
4606 return null;
4607 }
4608 }
4609
4610 // First check if the second quarter is the seed for a half-match.
4611 hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
4612
4613 // Check again based on the third quarter.
4614 hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
4615 if (!hm1 && !hm2) {
4616 return null;
4617 } else if (!hm2) {
4618 hm = hm1;
4619 } else if (!hm1) {
4620 hm = hm2;
4621 } else {
4622
4623 // Both matched. Select the longest.
4624 hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
4625 }
4626
4627 // A half-match was found, sort out the return data.
4628 if (text1.length > text2.length) {
4629 text1A = hm[0];
4630 text1B = hm[1];
4631 text2A = hm[2];
4632 text2B = hm[3];
4633 } else {
4634 text2A = hm[0];
4635 text2B = hm[1];
4636 text1A = hm[2];
4637 text1B = hm[3];
4638 }
4639 midCommon = hm[4];
4640 return [text1A, text1B, text2A, text2B, midCommon];
4641 };
4642
4643 /**
4644 * Do a quick line-level diff on both strings, then rediff the parts for
4645 * greater accuracy.
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.
4651 * @private
4652 */
4653 DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
4654 var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
4655
4656 // Scan the text on a line-by-line basis first.
4657 a = this.diffLinesToChars(text1, text2);
4658 text1 = a.chars1;
4659 text2 = a.chars2;
4660 linearray = a.lineArray;
4661
4662 diffs = this.DiffMain(text1, text2, false, deadline);
4663
4664 // Convert the diff back to original text.
4665 this.diffCharsToLines(diffs, linearray);
4666
4667 // Eliminate freak matches (e.g. blank lines)
4668 this.diffCleanupSemantic(diffs);
4669
4670 // Rediff any replacement blocks, this time character-by-character.
4671 // Add a dummy entry at the end.
4672 diffs.push([DIFF_EQUAL, ""]);
4673 pointer = 0;
4674 countDelete = 0;
4675 countInsert = 0;
4676 textDelete = "";
4677 textInsert = "";
4678 while (pointer < diffs.length) {
4679 switch (diffs[pointer][0]) {
4680 case DIFF_INSERT:
4681 countInsert++;
4682 textInsert += diffs[pointer][1];
4683 break;
4684 case DIFF_DELETE:
4685 countDelete++;
4686 textDelete += diffs[pointer][1];
4687 break;
4688 case DIFF_EQUAL:
4689
4690 // Upon reaching an equality, check for prior redundancies.
4691 if (countDelete >= 1 && countInsert >= 1) {
4692
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]);
4699 }
4700 pointer = pointer + a.length;
4701 }
4702 countInsert = 0;
4703 countDelete = 0;
4704 textDelete = "";
4705 textInsert = "";
4706 break;
4707 }
4708 pointer++;
4709 }
4710 diffs.pop(); // Remove the dummy entry at the end.
4711
4712 return diffs;
4713 };
4714
4715 /**
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.
4723 * @private
4724 */
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;
4727
4728 // Cache the text lengths to prevent multiple calls.
4729 text1Length = text1.length;
4730 text2Length = text2.length;
4731 maxD = Math.ceil((text1Length + text2Length) / 2);
4732 vOffset = maxD;
4733 vLength = 2 * maxD;
4734 v1 = new Array(vLength);
4735 v2 = new Array(vLength);
4736
4737 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
4738 // integers and undefined.
4739 for (x = 0; x < vLength; x++) {
4740 v1[x] = -1;
4741 v2[x] = -1;
4742 }
4743 v1[vOffset + 1] = 0;
4744 v2[vOffset + 1] = 0;
4745 delta = text1Length - text2Length;
4746
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;
4750
4751 // Offsets for start and end of k loop.
4752 // Prevents mapping of space beyond the grid.
4753 k1start = 0;
4754 k1end = 0;
4755 k2start = 0;
4756 k2end = 0;
4757 for (d = 0; d < maxD; d++) {
4758
4759 // Bail out if deadline is reached.
4760 if (new Date().getTime() > deadline) {
4761 break;
4762 }
4763
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];
4769 } else {
4770 x1 = v1[k1Offset - 1] + 1;
4771 }
4772 y1 = x1 - k1;
4773 while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
4774 x1++;
4775 y1++;
4776 }
4777 v1[k1Offset] = x1;
4778 if (x1 > text1Length) {
4779
4780 // Ran off the right of the graph.
4781 k1end += 2;
4782 } else if (y1 > text2Length) {
4783
4784 // Ran off the bottom of the graph.
4785 k1start += 2;
4786 } else if (front) {
4787 k2Offset = vOffset + delta - k1;
4788 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
4789
4790 // Mirror x2 onto top-left coordinate system.
4791 x2 = text1Length - v2[k2Offset];
4792 if (x1 >= x2) {
4793
4794 // Overlap detected.
4795 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4796 }
4797 }
4798 }
4799 }
4800
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];
4806 } else {
4807 x2 = v2[k2Offset - 1] + 1;
4808 }
4809 y2 = x2 - k2;
4810 while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
4811 x2++;
4812 y2++;
4813 }
4814 v2[k2Offset] = x2;
4815 if (x2 > text1Length) {
4816
4817 // Ran off the left of the graph.
4818 k2end += 2;
4819 } else if (y2 > text2Length) {
4820
4821 // Ran off the top of the graph.
4822 k2start += 2;
4823 } else if (!front) {
4824 k1Offset = vOffset + delta - k2;
4825 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
4826 x1 = v1[k1Offset];
4827 y1 = vOffset + x1 - k1Offset;
4828
4829 // Mirror x2 onto top-left coordinate system.
4830 x2 = text1Length - x2;
4831 if (x1 >= x2) {
4832
4833 // Overlap detected.
4834 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4835 }
4836 }
4837 }
4838 }
4839 }
4840
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]];
4844 };
4845
4846 /**
4847 * Given the location of the 'middle snake', split the diff in two parts
4848 * and recurse.
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.
4855 * @private
4856 */
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);
4863
4864 // Compute both diffs serially.
4865 diffs = this.DiffMain(text1a, text2a, false, deadline);
4866 diffsb = this.DiffMain(text1b, text2b, false, deadline);
4867
4868 return diffs.concat(diffsb);
4869 };
4870
4871 /**
4872 * Reduce the number of edits by eliminating semantically trivial equalities.
4873 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4874 */
4875 DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
4876 var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
4877 changes = false;
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;
4882
4883 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4884 pointer = 0; // Index of current position.
4885
4886 // Number of characters that changed prior to the equality.
4887 lengthInsertions1 = 0;
4888 lengthDeletions1 = 0;
4889
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) {
4895 // Equality found.
4896 equalities[equalitiesLength++] = pointer;
4897 lengthInsertions1 = lengthInsertions2;
4898 lengthDeletions1 = lengthDeletions2;
4899 lengthInsertions2 = 0;
4900 lengthDeletions2 = 0;
4901 lastequality = diffs[pointer][1];
4902 } else {
4903 // An insertion or deletion.
4904 if (diffs[pointer][0] === DIFF_INSERT) {
4905 lengthInsertions2 += diffs[pointer][1].length;
4906 } else {
4907 lengthDeletions2 += diffs[pointer][1].length;
4908 }
4909
4910 // Eliminate an equality that is smaller or equal to the edits on both
4911 // sides of it.
4912 if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
4913
4914 // Duplicate record.
4915 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4916
4917 // Change second copy to insert.
4918 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4919
4920 // Throw away the equality we just deleted.
4921 equalitiesLength--;
4922
4923 // Throw away the previous equality (it needs to be reevaluated).
4924 equalitiesLength--;
4925 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4926
4927 // Reset the counters.
4928 lengthInsertions1 = 0;
4929 lengthDeletions1 = 0;
4930 lengthInsertions2 = 0;
4931 lengthDeletions2 = 0;
4932 lastequality = null;
4933 changes = true;
4934 }
4935 }
4936 pointer++;
4937 }
4938
4939 // Normalize the diff.
4940 if (changes) {
4941 this.diffCleanupMerge(diffs);
4942 }
4943
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.
4950 pointer = 1;
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) {
4959
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);
4964 pointer++;
4965 }
4966 } else {
4967 if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
4968
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)]);
4972
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);
4977 pointer++;
4978 }
4979 }
4980 pointer++;
4981 }
4982 pointer++;
4983 }
4984 };
4985
4986 /**
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.
4992 * @private
4993 */
4994 DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
4995 var text1Length, text2Length, textLength, best, length, pattern, found;
4996
4997 // Cache the text lengths to prevent multiple calls.
4998 text1Length = text1.length;
4999 text2Length = text2.length;
5000
5001 // Eliminate the null case.
5002 if (text1Length === 0 || text2Length === 0) {
5003 return 0;
5004 }
5005
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);
5011 }
5012 textLength = Math.min(text1Length, text2Length);
5013
5014 // Quick check for the worst case.
5015 if (text1 === text2) {
5016 return textLength;
5017 }
5018
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/
5022 best = 0;
5023 length = 1;
5024 while (true) {
5025 pattern = text1.substring(textLength - length);
5026 found = text2.indexOf(pattern);
5027 if (found === -1) {
5028 return best;
5029 }
5030 length += found;
5031 if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
5032 best = length;
5033 length++;
5034 }
5035 }
5036 };
5037
5038 /**
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.
5047 * @private
5048 */
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
5053
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.
5056 lineArray[0] = "";
5057
5058 /**
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.
5064 * @private
5065 */
5066 function diffLinesToCharsMunge(text) {
5067 var chars, lineStart, lineEnd, lineArrayLength, line;
5068 chars = "";
5069
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.
5073 lineStart = 0;
5074 lineEnd = -1;
5075
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;
5082 }
5083 line = text.substring(lineStart, lineEnd + 1);
5084 lineStart = lineEnd + 1;
5085
5086 var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined;
5087
5088 if (lineHashExists) {
5089 chars += String.fromCharCode(lineHash[line]);
5090 } else {
5091 chars += String.fromCharCode(lineArrayLength);
5092 lineHash[line] = lineArrayLength;
5093 lineArray[lineArrayLength++] = line;
5094 }
5095 }
5096 return chars;
5097 }
5098
5099 chars1 = diffLinesToCharsMunge(text1);
5100 chars2 = diffLinesToCharsMunge(text2);
5101 return {
5102 chars1: chars1,
5103 chars2: chars2,
5104 lineArray: lineArray
5105 };
5106 };
5107
5108 /**
5109 * Rehydrate the text in a diff from a string of line hashes to real lines of
5110 * text.
5111 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5112 * @param {!Array.<string>} lineArray Array of unique strings.
5113 * @private
5114 */
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];
5119 text = [];
5120 for (y = 0; y < chars.length; y++) {
5121 text[y] = lineArray[chars.charCodeAt(y)];
5122 }
5123 diffs[x][1] = text.join("");
5124 }
5125 };
5126
5127 /**
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.
5131 */
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.
5135 pointer = 0;
5136 countDelete = 0;
5137 countInsert = 0;
5138 textDelete = "";
5139 textInsert = "";
5140
5141 while (pointer < diffs.length) {
5142 switch (diffs[pointer][0]) {
5143 case DIFF_INSERT:
5144 countInsert++;
5145 textInsert += diffs[pointer][1];
5146 pointer++;
5147 break;
5148 case DIFF_DELETE:
5149 countDelete++;
5150 textDelete += diffs[pointer][1];
5151 pointer++;
5152 break;
5153 case DIFF_EQUAL:
5154
5155 // Upon reaching an equality, check for prior redundancies.
5156 if (countDelete + countInsert > 1) {
5157 if (countDelete !== 0 && countInsert !== 0) {
5158
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);
5164 } else {
5165 diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
5166 pointer++;
5167 }
5168 textInsert = textInsert.substring(commonlength);
5169 textDelete = textDelete.substring(commonlength);
5170 }
5171
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);
5178 }
5179 }
5180
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]);
5186 } else {
5187 diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
5188 }
5189 pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
5190 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
5191
5192 // Merge this equality with the previous one.
5193 diffs[pointer - 1][1] += diffs[pointer][1];
5194 diffs.splice(pointer, 1);
5195 } else {
5196 pointer++;
5197 }
5198 countInsert = 0;
5199 countDelete = 0;
5200 textDelete = "";
5201 textInsert = "";
5202 break;
5203 }
5204 }
5205 if (diffs[diffs.length - 1][1] === "") {
5206 diffs.pop(); // Remove the dummy entry at the end.
5207 }
5208
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
5212 changes = false;
5213 pointer = 1;
5214
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) {
5218
5219 diffPointer = diffs[pointer][1];
5220 position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
5221
5222 // This is a single edit surrounded by equalities.
5223 if (position === diffs[pointer - 1][1]) {
5224
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);
5229 changes = true;
5230 } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
5231
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);
5236 changes = true;
5237 }
5238 }
5239 pointer++;
5240 }
5241
5242 // If shifts were made, the diff needs reordering and another shift sweep.
5243 if (changes) {
5244 this.diffCleanupMerge(diffs);
5245 }
5246 };
5247
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);
5254
5255 return text;
5256 };
5257 }();
5258
5259 }((function() { return this; }())));