d7b22ddf845a9f4d926716fc46391b6da4f89dea
[lhc/web/wiklou.git] / resources / lib / qunitjs / qunit.js
1 /*!
2 * QUnit 2.6.0
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-03-27T02:18Z
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 LISTENERS = Object.create(null);
954 var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
955
956 /**
957 * Emits an event with the specified data to all currently registered listeners.
958 * Callbacks will fire in the order in which they are registered (FIFO). This
959 * function is not exposed publicly; it is used by QUnit internals to emit
960 * logging events.
961 *
962 * @private
963 * @method emit
964 * @param {String} eventName
965 * @param {Object} data
966 * @return {Void}
967 */
968 function emit(eventName, data) {
969 if (objectType(eventName) !== "string") {
970 throw new TypeError("eventName must be a string when emitting an event");
971 }
972
973 // Clone the callbacks in case one of them registers a new callback
974 var originalCallbacks = LISTENERS[eventName];
975 var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
976
977 for (var i = 0; i < callbacks.length; i++) {
978 callbacks[i](data);
979 }
980 }
981
982 /**
983 * Registers a callback as a listener to the specified event.
984 *
985 * @public
986 * @method on
987 * @param {String} eventName
988 * @param {Function} callback
989 * @return {Void}
990 */
991 function on(eventName, callback) {
992 if (objectType(eventName) !== "string") {
993 throw new TypeError("eventName must be a string when registering a listener");
994 } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
995 var events = SUPPORTED_EVENTS.join(", ");
996 throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
997 } else if (objectType(callback) !== "function") {
998 throw new TypeError("callback must be a function when registering a listener");
999 }
1000
1001 if (!LISTENERS[eventName]) {
1002 LISTENERS[eventName] = [];
1003 }
1004
1005 // Don't register the same callback more than once
1006 if (!inArray(callback, LISTENERS[eventName])) {
1007 LISTENERS[eventName].push(callback);
1008 }
1009 }
1010
1011 // Register logging callbacks
1012 function registerLoggingCallbacks(obj) {
1013 var i,
1014 l,
1015 key,
1016 callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
1017
1018 function registerLoggingCallback(key) {
1019 var loggingCallback = function loggingCallback(callback) {
1020 if (objectType(callback) !== "function") {
1021 throw new Error("QUnit logging methods require a callback function as their first parameters.");
1022 }
1023
1024 config.callbacks[key].push(callback);
1025 };
1026
1027 return loggingCallback;
1028 }
1029
1030 for (i = 0, l = callbackNames.length; i < l; i++) {
1031 key = callbackNames[i];
1032
1033 // Initialize key collection of logging callback
1034 if (objectType(config.callbacks[key]) === "undefined") {
1035 config.callbacks[key] = [];
1036 }
1037
1038 obj[key] = registerLoggingCallback(key);
1039 }
1040 }
1041
1042 function runLoggingCallbacks(key, args) {
1043 var i, l, callbacks;
1044
1045 callbacks = config.callbacks[key];
1046 for (i = 0, l = callbacks.length; i < l; i++) {
1047 callbacks[i](args);
1048 }
1049 }
1050
1051 // Doesn't support IE9, it will return undefined on these browsers
1052 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1053 var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
1054
1055 function extractStacktrace(e, offset) {
1056 offset = offset === undefined ? 4 : offset;
1057
1058 var stack, include, i;
1059
1060 if (e && e.stack) {
1061 stack = e.stack.split("\n");
1062 if (/^error$/i.test(stack[0])) {
1063 stack.shift();
1064 }
1065 if (fileName) {
1066 include = [];
1067 for (i = offset; i < stack.length; i++) {
1068 if (stack[i].indexOf(fileName) !== -1) {
1069 break;
1070 }
1071 include.push(stack[i]);
1072 }
1073 if (include.length) {
1074 return include.join("\n");
1075 }
1076 }
1077 return stack[offset];
1078 }
1079 }
1080
1081 function sourceFromStacktrace(offset) {
1082 var error = new Error();
1083
1084 // Support: Safari <=7 only, IE <=10 - 11 only
1085 // Not all browsers generate the `stack` property for `new Error()`, see also #636
1086 if (!error.stack) {
1087 try {
1088 throw error;
1089 } catch (err) {
1090 error = err;
1091 }
1092 }
1093
1094 return extractStacktrace(error, offset);
1095 }
1096
1097 var priorityCount = 0;
1098 var unitSampler = void 0;
1099
1100 // This is a queue of functions that are tasks within a single test.
1101 // After tests are dequeued from config.queue they are expanded into
1102 // a set of tasks in this queue.
1103 var taskQueue = [];
1104
1105 /**
1106 * Advances the taskQueue to the next task. If the taskQueue is empty,
1107 * process the testQueue
1108 */
1109 function advance() {
1110 advanceTaskQueue();
1111
1112 if (!taskQueue.length) {
1113 advanceTestQueue();
1114 }
1115 }
1116
1117 /**
1118 * Advances the taskQueue to the next task if it is ready and not empty.
1119 */
1120 function advanceTaskQueue() {
1121 var start = now();
1122 config.depth = (config.depth || 0) + 1;
1123
1124 while (taskQueue.length && !config.blocking) {
1125 var elapsedTime = now() - start;
1126
1127 if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
1128 var task = taskQueue.shift();
1129 task();
1130 } else {
1131 setTimeout(advance);
1132 break;
1133 }
1134 }
1135
1136 config.depth--;
1137 }
1138
1139 /**
1140 * Advance the testQueue to the next test to process. Call done() if testQueue completes.
1141 */
1142 function advanceTestQueue() {
1143 if (!config.blocking && !config.queue.length && config.depth === 0) {
1144 done();
1145 return;
1146 }
1147
1148 var testTasks = config.queue.shift();
1149 addToTaskQueue(testTasks());
1150
1151 if (priorityCount > 0) {
1152 priorityCount--;
1153 }
1154
1155 advance();
1156 }
1157
1158 /**
1159 * Enqueue the tasks for a test into the task queue.
1160 * @param {Array} tasksArray
1161 */
1162 function addToTaskQueue(tasksArray) {
1163 taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
1164 }
1165
1166 /**
1167 * Return the number of tasks remaining in the task queue to be processed.
1168 * @return {Number}
1169 */
1170 function taskQueueLength() {
1171 return taskQueue.length;
1172 }
1173
1174 /**
1175 * Adds a test to the TestQueue for execution.
1176 * @param {Function} testTasksFunc
1177 * @param {Boolean} prioritize
1178 * @param {String} seed
1179 */
1180 function addToTestQueue(testTasksFunc, prioritize, seed) {
1181 if (prioritize) {
1182 config.queue.splice(priorityCount++, 0, testTasksFunc);
1183 } else if (seed) {
1184 if (!unitSampler) {
1185 unitSampler = unitSamplerGenerator(seed);
1186 }
1187
1188 // Insert into a random position after all prioritized items
1189 var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
1190 config.queue.splice(priorityCount + index, 0, testTasksFunc);
1191 } else {
1192 config.queue.push(testTasksFunc);
1193 }
1194 }
1195
1196 /**
1197 * Creates a seeded "sample" generator which is used for randomizing tests.
1198 */
1199 function unitSamplerGenerator(seed) {
1200
1201 // 32-bit xorshift, requires only a nonzero seed
1202 // http://excamera.com/sphinx/article-xorshift.html
1203 var sample = parseInt(generateHash(seed), 16) || -1;
1204 return function () {
1205 sample ^= sample << 13;
1206 sample ^= sample >>> 17;
1207 sample ^= sample << 5;
1208
1209 // ECMAScript has no unsigned number type
1210 if (sample < 0) {
1211 sample += 0x100000000;
1212 }
1213
1214 return sample / 0x100000000;
1215 };
1216 }
1217
1218 /**
1219 * This function is called when the ProcessingQueue is done processing all
1220 * items. It handles emitting the final run events.
1221 */
1222 function done() {
1223 var storage = config.storage;
1224
1225 ProcessingQueue.finished = true;
1226
1227 var runtime = now() - config.started;
1228 var passed = config.stats.all - config.stats.bad;
1229
1230 if (config.stats.all === 0) {
1231
1232 if (config.filter && config.filter.length) {
1233 throw new Error("No tests matched the filter \"" + config.filter + "\".");
1234 }
1235
1236 if (config.module && config.module.length) {
1237 throw new Error("No tests matched the module \"" + config.module + "\".");
1238 }
1239
1240 if (config.moduleId && config.moduleId.length) {
1241 throw new Error("No tests matched the moduleId \"" + config.moduleId + "\".");
1242 }
1243
1244 if (config.testId && config.testId.length) {
1245 throw new Error("No tests matched the testId \"" + config.testId + "\".");
1246 }
1247
1248 throw new Error("No tests were run.");
1249 }
1250
1251 emit("runEnd", globalSuite.end(true));
1252 runLoggingCallbacks("done", {
1253 passed: passed,
1254 failed: config.stats.bad,
1255 total: config.stats.all,
1256 runtime: runtime
1257 });
1258
1259 // Clear own storage items if all tests passed
1260 if (storage && config.stats.bad === 0) {
1261 for (var i = storage.length - 1; i >= 0; i--) {
1262 var key = storage.key(i);
1263
1264 if (key.indexOf("qunit-test-") === 0) {
1265 storage.removeItem(key);
1266 }
1267 }
1268 }
1269 }
1270
1271 var ProcessingQueue = {
1272 finished: false,
1273 add: addToTestQueue,
1274 advance: advance,
1275 taskCount: taskQueueLength
1276 };
1277
1278 var TestReport = function () {
1279 function TestReport(name, suite, options) {
1280 classCallCheck(this, TestReport);
1281
1282 this.name = name;
1283 this.suiteName = suite.name;
1284 this.fullName = suite.fullName.concat(name);
1285 this.runtime = 0;
1286 this.assertions = [];
1287
1288 this.skipped = !!options.skip;
1289 this.todo = !!options.todo;
1290
1291 this.valid = options.valid;
1292
1293 this._startTime = 0;
1294 this._endTime = 0;
1295
1296 suite.pushTest(this);
1297 }
1298
1299 createClass(TestReport, [{
1300 key: "start",
1301 value: function start(recordTime) {
1302 if (recordTime) {
1303 this._startTime = Date.now();
1304 }
1305
1306 return {
1307 name: this.name,
1308 suiteName: this.suiteName,
1309 fullName: this.fullName.slice()
1310 };
1311 }
1312 }, {
1313 key: "end",
1314 value: function end(recordTime) {
1315 if (recordTime) {
1316 this._endTime = Date.now();
1317 }
1318
1319 return extend(this.start(), {
1320 runtime: this.getRuntime(),
1321 status: this.getStatus(),
1322 errors: this.getFailedAssertions(),
1323 assertions: this.getAssertions()
1324 });
1325 }
1326 }, {
1327 key: "pushAssertion",
1328 value: function pushAssertion(assertion) {
1329 this.assertions.push(assertion);
1330 }
1331 }, {
1332 key: "getRuntime",
1333 value: function getRuntime() {
1334 return this._endTime - this._startTime;
1335 }
1336 }, {
1337 key: "getStatus",
1338 value: function getStatus() {
1339 if (this.skipped) {
1340 return "skipped";
1341 }
1342
1343 var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
1344
1345 if (!testPassed) {
1346 return "failed";
1347 } else if (this.todo) {
1348 return "todo";
1349 } else {
1350 return "passed";
1351 }
1352 }
1353 }, {
1354 key: "getFailedAssertions",
1355 value: function getFailedAssertions() {
1356 return this.assertions.filter(function (assertion) {
1357 return !assertion.passed;
1358 });
1359 }
1360 }, {
1361 key: "getAssertions",
1362 value: function getAssertions() {
1363 return this.assertions.slice();
1364 }
1365
1366 // Remove actual and expected values from assertions. This is to prevent
1367 // leaking memory throughout a test suite.
1368
1369 }, {
1370 key: "slimAssertions",
1371 value: function slimAssertions() {
1372 this.assertions = this.assertions.map(function (assertion) {
1373 delete assertion.actual;
1374 delete assertion.expected;
1375 return assertion;
1376 });
1377 }
1378 }]);
1379 return TestReport;
1380 }();
1381
1382 var focused$1 = false;
1383
1384 function Test(settings) {
1385 var i, l;
1386
1387 ++Test.count;
1388
1389 this.expected = null;
1390 this.assertions = [];
1391 this.semaphore = 0;
1392 this.module = config.currentModule;
1393 this.stack = sourceFromStacktrace(3);
1394 this.steps = [];
1395 this.timeout = undefined;
1396
1397 // If a module is skipped, all its tests and the tests of the child suites
1398 // should be treated as skipped even if they are defined as `only` or `todo`.
1399 // As for `todo` module, all its tests will be treated as `todo` except for
1400 // tests defined as `skip` which will be left intact.
1401 //
1402 // So, if a test is defined as `todo` and is inside a skipped module, we should
1403 // then treat that test as if was defined as `skip`.
1404 if (this.module.skip) {
1405 settings.skip = true;
1406 settings.todo = false;
1407
1408 // Skipped tests should be left intact
1409 } else if (this.module.todo && !settings.skip) {
1410 settings.todo = true;
1411 }
1412
1413 extend(this, settings);
1414
1415 this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
1416 todo: settings.todo,
1417 skip: settings.skip,
1418 valid: this.valid()
1419 });
1420
1421 // Register unique strings
1422 for (i = 0, l = this.module.tests; i < l.length; i++) {
1423 if (this.module.tests[i].name === this.testName) {
1424 this.testName += " ";
1425 }
1426 }
1427
1428 this.testId = generateHash(this.module.name, this.testName);
1429
1430 this.module.tests.push({
1431 name: this.testName,
1432 testId: this.testId,
1433 skip: !!settings.skip
1434 });
1435
1436 if (settings.skip) {
1437
1438 // Skipped tests will fully ignore any sent callback
1439 this.callback = function () {};
1440 this.async = false;
1441 this.expected = 0;
1442 } else {
1443 if (typeof this.callback !== "function") {
1444 var method = this.todo ? "todo" : "test";
1445
1446 // eslint-disable-next-line max-len
1447 throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")");
1448 }
1449
1450 this.assert = new Assert(this);
1451 }
1452 }
1453
1454 Test.count = 0;
1455
1456 function getNotStartedModules(startModule) {
1457 var module = startModule,
1458 modules = [];
1459
1460 while (module && module.testsRun === 0) {
1461 modules.push(module);
1462 module = module.parentModule;
1463 }
1464
1465 return modules;
1466 }
1467
1468 Test.prototype = {
1469 before: function before() {
1470 var i,
1471 startModule,
1472 module = this.module,
1473 notStartedModules = getNotStartedModules(module);
1474
1475 for (i = notStartedModules.length - 1; i >= 0; i--) {
1476 startModule = notStartedModules[i];
1477 startModule.stats = { all: 0, bad: 0, started: now() };
1478 emit("suiteStart", startModule.suiteReport.start(true));
1479 runLoggingCallbacks("moduleStart", {
1480 name: startModule.name,
1481 tests: startModule.tests
1482 });
1483 }
1484
1485 config.current = this;
1486
1487 this.testEnvironment = extend({}, module.testEnvironment);
1488
1489 this.started = now();
1490 emit("testStart", this.testReport.start(true));
1491 runLoggingCallbacks("testStart", {
1492 name: this.testName,
1493 module: module.name,
1494 testId: this.testId,
1495 previousFailure: this.previousFailure
1496 });
1497
1498 if (!config.pollution) {
1499 saveGlobal();
1500 }
1501 },
1502
1503 run: function run() {
1504 var promise;
1505
1506 config.current = this;
1507
1508 this.callbackStarted = now();
1509
1510 if (config.notrycatch) {
1511 runTest(this);
1512 return;
1513 }
1514
1515 try {
1516 runTest(this);
1517 } catch (e) {
1518 this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
1519
1520 // Else next test will carry the responsibility
1521 saveGlobal();
1522
1523 // Restart the tests if they're blocking
1524 if (config.blocking) {
1525 internalRecover(this);
1526 }
1527 }
1528
1529 function runTest(test) {
1530 promise = test.callback.call(test.testEnvironment, test.assert);
1531 test.resolvePromise(promise);
1532
1533 // If the test has a "lock" on it, but the timeout is 0, then we push a
1534 // failure as the test should be synchronous.
1535 if (test.timeout === 0 && test.semaphore !== 0) {
1536 pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2));
1537 }
1538 }
1539 },
1540
1541 after: function after() {
1542 checkPollution();
1543 },
1544
1545 queueHook: function queueHook(hook, hookName, hookOwner) {
1546 var _this = this;
1547
1548 var callHook = function callHook() {
1549 var promise = hook.call(_this.testEnvironment, _this.assert);
1550 _this.resolvePromise(promise, hookName);
1551 };
1552
1553 var runHook = function runHook() {
1554 if (hookName === "before") {
1555 if (hookOwner.unskippedTestsRun !== 0) {
1556 return;
1557 }
1558
1559 _this.preserveEnvironment = true;
1560 }
1561
1562 // The 'after' hook should only execute when there are not tests left and
1563 // when the 'after' and 'finish' tasks are the only tasks left to process
1564 if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) {
1565 return;
1566 }
1567
1568 config.current = _this;
1569 if (config.notrycatch) {
1570 callHook();
1571 return;
1572 }
1573 try {
1574 callHook();
1575 } catch (error) {
1576 _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
1577 }
1578 };
1579
1580 return runHook;
1581 },
1582
1583
1584 // Currently only used for module level hooks, can be used to add global level ones
1585 hooks: function hooks(handler) {
1586 var hooks = [];
1587
1588 function processHooks(test, module) {
1589 if (module.parentModule) {
1590 processHooks(test, module.parentModule);
1591 }
1592
1593 if (module.hooks[handler].length) {
1594 for (var i = 0; i < module.hooks[handler].length; i++) {
1595 hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
1596 }
1597 }
1598 }
1599
1600 // Hooks are ignored on skipped tests
1601 if (!this.skip) {
1602 processHooks(this, this.module);
1603 }
1604
1605 return hooks;
1606 },
1607
1608
1609 finish: function finish() {
1610 config.current = this;
1611
1612 if (this.steps.length) {
1613 var stepsList = this.steps.join(", ");
1614 this.pushFailure("Expected assert.verifySteps() to be called before end of test " + ("after using assert.step(). Unverified steps: " + stepsList), this.stack);
1615 }
1616
1617 if (config.requireExpects && this.expected === null) {
1618 this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
1619 } else if (this.expected !== null && this.expected !== this.assertions.length) {
1620 this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack);
1621 } else if (this.expected === null && !this.assertions.length) {
1622 this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack);
1623 }
1624
1625 var i,
1626 module = this.module,
1627 moduleName = module.name,
1628 testName = this.testName,
1629 skipped = !!this.skip,
1630 todo = !!this.todo,
1631 bad = 0,
1632 storage = config.storage;
1633
1634 this.runtime = now() - this.started;
1635
1636 config.stats.all += this.assertions.length;
1637 module.stats.all += this.assertions.length;
1638
1639 for (i = 0; i < this.assertions.length; i++) {
1640 if (!this.assertions[i].result) {
1641 bad++;
1642 config.stats.bad++;
1643 module.stats.bad++;
1644 }
1645 }
1646
1647 notifyTestsRan(module, skipped);
1648
1649 // Store result when possible
1650 if (storage) {
1651 if (bad) {
1652 storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
1653 } else {
1654 storage.removeItem("qunit-test-" + moduleName + "-" + testName);
1655 }
1656 }
1657
1658 // After emitting the js-reporters event we cleanup the assertion data to
1659 // avoid leaking it. It is not used by the legacy testDone callbacks.
1660 emit("testEnd", this.testReport.end(true));
1661 this.testReport.slimAssertions();
1662
1663 runLoggingCallbacks("testDone", {
1664 name: testName,
1665 module: moduleName,
1666 skipped: skipped,
1667 todo: todo,
1668 failed: bad,
1669 passed: this.assertions.length - bad,
1670 total: this.assertions.length,
1671 runtime: skipped ? 0 : this.runtime,
1672
1673 // HTML Reporter use
1674 assertions: this.assertions,
1675 testId: this.testId,
1676
1677 // Source of Test
1678 source: this.stack
1679 });
1680
1681 if (module.testsRun === numberOfTests(module)) {
1682 logSuiteEnd(module);
1683
1684 // Check if the parent modules, iteratively, are done. If that the case,
1685 // we emit the `suiteEnd` event and trigger `moduleDone` callback.
1686 var parent = module.parentModule;
1687 while (parent && parent.testsRun === numberOfTests(parent)) {
1688 logSuiteEnd(parent);
1689 parent = parent.parentModule;
1690 }
1691 }
1692
1693 config.current = undefined;
1694
1695 function logSuiteEnd(module) {
1696 emit("suiteEnd", module.suiteReport.end(true));
1697 runLoggingCallbacks("moduleDone", {
1698 name: module.name,
1699 tests: module.tests,
1700 failed: module.stats.bad,
1701 passed: module.stats.all - module.stats.bad,
1702 total: module.stats.all,
1703 runtime: now() - module.stats.started
1704 });
1705 }
1706 },
1707
1708 preserveTestEnvironment: function preserveTestEnvironment() {
1709 if (this.preserveEnvironment) {
1710 this.module.testEnvironment = this.testEnvironment;
1711 this.testEnvironment = extend({}, this.module.testEnvironment);
1712 }
1713 },
1714
1715 queue: function queue() {
1716 var test = this;
1717
1718 if (!this.valid()) {
1719 return;
1720 }
1721
1722 function runTest() {
1723 return [function () {
1724 test.before();
1725 }].concat(toConsumableArray(test.hooks("before")), [function () {
1726 test.preserveTestEnvironment();
1727 }], toConsumableArray(test.hooks("beforeEach")), [function () {
1728 test.run();
1729 }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () {
1730 test.after();
1731 }, function () {
1732 test.finish();
1733 }]);
1734 }
1735
1736 var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
1737
1738 // Prioritize previously failed tests, detected from storage
1739 var prioritize = config.reorder && !!previousFailCount;
1740
1741 this.previousFailure = !!previousFailCount;
1742
1743 ProcessingQueue.add(runTest, prioritize, config.seed);
1744
1745 // If the queue has already finished, we manually process the new test
1746 if (ProcessingQueue.finished) {
1747 ProcessingQueue.advance();
1748 }
1749 },
1750
1751
1752 pushResult: function pushResult(resultInfo) {
1753 if (this !== config.current) {
1754 throw new Error("Assertion occurred after test had finished.");
1755 }
1756
1757 // Destructure of resultInfo = { result, actual, expected, message, negative }
1758 var source,
1759 details = {
1760 module: this.module.name,
1761 name: this.testName,
1762 result: resultInfo.result,
1763 message: resultInfo.message,
1764 actual: resultInfo.actual,
1765 testId: this.testId,
1766 negative: resultInfo.negative || false,
1767 runtime: now() - this.started,
1768 todo: !!this.todo
1769 };
1770
1771 if (hasOwn.call(resultInfo, "expected")) {
1772 details.expected = resultInfo.expected;
1773 }
1774
1775 if (!resultInfo.result) {
1776 source = resultInfo.source || sourceFromStacktrace();
1777
1778 if (source) {
1779 details.source = source;
1780 }
1781 }
1782
1783 this.logAssertion(details);
1784
1785 this.assertions.push({
1786 result: !!resultInfo.result,
1787 message: resultInfo.message
1788 });
1789 },
1790
1791 pushFailure: function pushFailure(message, source, actual) {
1792 if (!(this instanceof Test)) {
1793 throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
1794 }
1795
1796 this.pushResult({
1797 result: false,
1798 message: message || "error",
1799 actual: actual || null,
1800 source: source
1801 });
1802 },
1803
1804 /**
1805 * Log assertion details using both the old QUnit.log interface and
1806 * QUnit.on( "assertion" ) interface.
1807 *
1808 * @private
1809 */
1810 logAssertion: function logAssertion(details) {
1811 runLoggingCallbacks("log", details);
1812
1813 var assertion = {
1814 passed: details.result,
1815 actual: details.actual,
1816 expected: details.expected,
1817 message: details.message,
1818 stack: details.source,
1819 todo: details.todo
1820 };
1821 this.testReport.pushAssertion(assertion);
1822 emit("assertion", assertion);
1823 },
1824
1825
1826 resolvePromise: function resolvePromise(promise, phase) {
1827 var then,
1828 resume,
1829 message,
1830 test = this;
1831 if (promise != null) {
1832 then = promise.then;
1833 if (objectType(then) === "function") {
1834 resume = internalStop(test);
1835 if (config.notrycatch) {
1836 then.call(promise, function () {
1837 resume();
1838 });
1839 } else {
1840 then.call(promise, function () {
1841 resume();
1842 }, function (error) {
1843 message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
1844 test.pushFailure(message, extractStacktrace(error, 0));
1845
1846 // Else next test will carry the responsibility
1847 saveGlobal();
1848
1849 // Unblock
1850 internalRecover(test);
1851 });
1852 }
1853 }
1854 }
1855 },
1856
1857 valid: function valid() {
1858 var filter = config.filter,
1859 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
1860 module = config.module && config.module.toLowerCase(),
1861 fullName = this.module.name + ": " + this.testName;
1862
1863 function moduleChainNameMatch(testModule) {
1864 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1865 if (testModuleName === module) {
1866 return true;
1867 } else if (testModule.parentModule) {
1868 return moduleChainNameMatch(testModule.parentModule);
1869 } else {
1870 return false;
1871 }
1872 }
1873
1874 function moduleChainIdMatch(testModule) {
1875 return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
1876 }
1877
1878 // Internally-generated tests are always valid
1879 if (this.callback && this.callback.validTest) {
1880 return true;
1881 }
1882
1883 if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
1884
1885 return false;
1886 }
1887
1888 if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
1889
1890 return false;
1891 }
1892
1893 if (module && !moduleChainNameMatch(this.module)) {
1894 return false;
1895 }
1896
1897 if (!filter) {
1898 return true;
1899 }
1900
1901 return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
1902 },
1903
1904 regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
1905 var regex = new RegExp(pattern, flags);
1906 var match = regex.test(fullName);
1907
1908 return match !== exclude;
1909 },
1910
1911 stringFilter: function stringFilter(filter, fullName) {
1912 filter = filter.toLowerCase();
1913 fullName = fullName.toLowerCase();
1914
1915 var include = filter.charAt(0) !== "!";
1916 if (!include) {
1917 filter = filter.slice(1);
1918 }
1919
1920 // If the filter matches, we need to honour include
1921 if (fullName.indexOf(filter) !== -1) {
1922 return include;
1923 }
1924
1925 // Otherwise, do the opposite
1926 return !include;
1927 }
1928 };
1929
1930 function pushFailure() {
1931 if (!config.current) {
1932 throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
1933 }
1934
1935 // Gets current test obj
1936 var currentTest = config.current;
1937
1938 return currentTest.pushFailure.apply(currentTest, arguments);
1939 }
1940
1941 function saveGlobal() {
1942 config.pollution = [];
1943
1944 if (config.noglobals) {
1945 for (var key in global$1) {
1946 if (hasOwn.call(global$1, key)) {
1947
1948 // In Opera sometimes DOM element ids show up here, ignore them
1949 if (/^qunit-test-output/.test(key)) {
1950 continue;
1951 }
1952 config.pollution.push(key);
1953 }
1954 }
1955 }
1956 }
1957
1958 function checkPollution() {
1959 var newGlobals,
1960 deletedGlobals,
1961 old = config.pollution;
1962
1963 saveGlobal();
1964
1965 newGlobals = diff(config.pollution, old);
1966 if (newGlobals.length > 0) {
1967 pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
1968 }
1969
1970 deletedGlobals = diff(old, config.pollution);
1971 if (deletedGlobals.length > 0) {
1972 pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
1973 }
1974 }
1975
1976 // Will be exposed as QUnit.test
1977 function test(testName, callback) {
1978 if (focused$1) {
1979 return;
1980 }
1981
1982 var newTest = new Test({
1983 testName: testName,
1984 callback: callback
1985 });
1986
1987 newTest.queue();
1988 }
1989
1990 function todo(testName, callback) {
1991 if (focused$1) {
1992 return;
1993 }
1994
1995 var newTest = new Test({
1996 testName: testName,
1997 callback: callback,
1998 todo: true
1999 });
2000
2001 newTest.queue();
2002 }
2003
2004 // Will be exposed as QUnit.skip
2005 function skip(testName) {
2006 if (focused$1) {
2007 return;
2008 }
2009
2010 var test = new Test({
2011 testName: testName,
2012 skip: true
2013 });
2014
2015 test.queue();
2016 }
2017
2018 // Will be exposed as QUnit.only
2019 function only(testName, callback) {
2020 if (focused$1) {
2021 return;
2022 }
2023
2024 config.queue.length = 0;
2025 focused$1 = true;
2026
2027 var newTest = new Test({
2028 testName: testName,
2029 callback: callback
2030 });
2031
2032 newTest.queue();
2033 }
2034
2035 // Put a hold on processing and return a function that will release it.
2036 function internalStop(test) {
2037 test.semaphore += 1;
2038 config.blocking = true;
2039
2040 // Set a recovery timeout, if so configured.
2041 if (defined.setTimeout) {
2042 var timeoutDuration = void 0;
2043
2044 if (typeof test.timeout === "number") {
2045 timeoutDuration = test.timeout;
2046 } else if (typeof config.testTimeout === "number") {
2047 timeoutDuration = config.testTimeout;
2048 }
2049
2050 if (typeof timeoutDuration === "number" && timeoutDuration > 0) {
2051 clearTimeout(config.timeout);
2052 config.timeout = setTimeout(function () {
2053 pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2));
2054 internalRecover(test);
2055 }, timeoutDuration);
2056 }
2057 }
2058
2059 var released = false;
2060 return function resume() {
2061 if (released) {
2062 return;
2063 }
2064
2065 released = true;
2066 test.semaphore -= 1;
2067 internalStart(test);
2068 };
2069 }
2070
2071 // Forcefully release all processing holds.
2072 function internalRecover(test) {
2073 test.semaphore = 0;
2074 internalStart(test);
2075 }
2076
2077 // Release a processing hold, scheduling a resumption attempt if no holds remain.
2078 function internalStart(test) {
2079
2080 // If semaphore is non-numeric, throw error
2081 if (isNaN(test.semaphore)) {
2082 test.semaphore = 0;
2083
2084 pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
2085 return;
2086 }
2087
2088 // Don't start until equal number of stop-calls
2089 if (test.semaphore > 0) {
2090 return;
2091 }
2092
2093 // Throw an Error if start is called more often than stop
2094 if (test.semaphore < 0) {
2095 test.semaphore = 0;
2096
2097 pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
2098 return;
2099 }
2100
2101 // Add a slight delay to allow more assertions etc.
2102 if (defined.setTimeout) {
2103 if (config.timeout) {
2104 clearTimeout(config.timeout);
2105 }
2106 config.timeout = setTimeout(function () {
2107 if (test.semaphore > 0) {
2108 return;
2109 }
2110
2111 if (config.timeout) {
2112 clearTimeout(config.timeout);
2113 }
2114
2115 begin();
2116 });
2117 } else {
2118 begin();
2119 }
2120 }
2121
2122 function collectTests(module) {
2123 var tests = [].concat(module.tests);
2124 var modules = [].concat(toConsumableArray(module.childModules));
2125
2126 // Do a breadth-first traversal of the child modules
2127 while (modules.length) {
2128 var nextModule = modules.shift();
2129 tests.push.apply(tests, nextModule.tests);
2130 modules.push.apply(modules, toConsumableArray(nextModule.childModules));
2131 }
2132
2133 return tests;
2134 }
2135
2136 function numberOfTests(module) {
2137 return collectTests(module).length;
2138 }
2139
2140 function numberOfUnskippedTests(module) {
2141 return collectTests(module).filter(function (test) {
2142 return !test.skip;
2143 }).length;
2144 }
2145
2146 function notifyTestsRan(module, skipped) {
2147 module.testsRun++;
2148 if (!skipped) {
2149 module.unskippedTestsRun++;
2150 }
2151 while (module = module.parentModule) {
2152 module.testsRun++;
2153 if (!skipped) {
2154 module.unskippedTestsRun++;
2155 }
2156 }
2157 }
2158
2159 /**
2160 * Returns a function that proxies to the given method name on the globals
2161 * console object. The proxy will also detect if the console doesn't exist and
2162 * will appropriately no-op. This allows support for IE9, which doesn't have a
2163 * console if the developer tools are not open.
2164 */
2165 function consoleProxy(method) {
2166 return function () {
2167 if (console) {
2168 console[method].apply(console, arguments);
2169 }
2170 };
2171 }
2172
2173 var Logger = {
2174 warn: consoleProxy("warn")
2175 };
2176
2177 var Assert = function () {
2178 function Assert(testContext) {
2179 classCallCheck(this, Assert);
2180
2181 this.test = testContext;
2182 }
2183
2184 // Assert helpers
2185
2186 createClass(Assert, [{
2187 key: "timeout",
2188 value: function timeout(duration) {
2189 if (typeof duration !== "number") {
2190 throw new Error("You must pass a number as the duration to assert.timeout");
2191 }
2192
2193 this.test.timeout = duration;
2194 }
2195
2196 // Documents a "step", which is a string value, in a test as a passing assertion
2197
2198 }, {
2199 key: "step",
2200 value: function step(message) {
2201 var assertionMessage = message;
2202 var result = !!message;
2203
2204 this.test.steps.push(message);
2205
2206 if (objectType(message) === "undefined" || message === "") {
2207 assertionMessage = "You must provide a message to assert.step";
2208 } else if (objectType(message) !== "string") {
2209 assertionMessage = "You must provide a string value to assert.step";
2210 result = false;
2211 }
2212
2213 return this.pushResult({
2214 result: result,
2215 message: assertionMessage
2216 });
2217 }
2218
2219 // Verifies the steps in a test match a given array of string values
2220
2221 }, {
2222 key: "verifySteps",
2223 value: function verifySteps(steps, message) {
2224
2225 // Since the steps array is just string values, we can clone with slice
2226 var actualStepsClone = this.test.steps.slice();
2227 this.deepEqual(actualStepsClone, steps, message);
2228 this.test.steps.length = 0;
2229 }
2230
2231 // Specify the number of expected assertions to guarantee that failed test
2232 // (no assertions are run at all) don't slip through.
2233
2234 }, {
2235 key: "expect",
2236 value: function expect(asserts) {
2237 if (arguments.length === 1) {
2238 this.test.expected = asserts;
2239 } else {
2240 return this.test.expected;
2241 }
2242 }
2243
2244 // Put a hold on processing and return a function that will release it a maximum of once.
2245
2246 }, {
2247 key: "async",
2248 value: function async(count) {
2249 var test$$1 = this.test;
2250
2251 var popped = false,
2252 acceptCallCount = count;
2253
2254 if (typeof acceptCallCount === "undefined") {
2255 acceptCallCount = 1;
2256 }
2257
2258 var resume = internalStop(test$$1);
2259
2260 return function done() {
2261 if (config.current !== test$$1) {
2262 throw Error("assert.async callback called after test finished.");
2263 }
2264
2265 if (popped) {
2266 test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
2267 return;
2268 }
2269
2270 acceptCallCount -= 1;
2271 if (acceptCallCount > 0) {
2272 return;
2273 }
2274
2275 popped = true;
2276 resume();
2277 };
2278 }
2279
2280 // Exports test.push() to the user API
2281 // Alias of pushResult.
2282
2283 }, {
2284 key: "push",
2285 value: function push(result, actual, expected, message, negative) {
2286 Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).");
2287
2288 var currentAssert = this instanceof Assert ? this : config.current.assert;
2289 return currentAssert.pushResult({
2290 result: result,
2291 actual: actual,
2292 expected: expected,
2293 message: message,
2294 negative: negative
2295 });
2296 }
2297 }, {
2298 key: "pushResult",
2299 value: function pushResult(resultInfo) {
2300
2301 // Destructure of resultInfo = { result, actual, expected, message, negative }
2302 var assert = this;
2303 var currentTest = assert instanceof Assert && assert.test || config.current;
2304
2305 // Backwards compatibility fix.
2306 // Allows the direct use of global exported assertions and QUnit.assert.*
2307 // Although, it's use is not recommended as it can leak assertions
2308 // to other tests from async tests, because we only get a reference to the current test,
2309 // not exactly the test where assertion were intended to be called.
2310 if (!currentTest) {
2311 throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
2312 }
2313
2314 if (!(assert instanceof Assert)) {
2315 assert = currentTest.assert;
2316 }
2317
2318 return assert.test.pushResult(resultInfo);
2319 }
2320 }, {
2321 key: "ok",
2322 value: function ok(result, message) {
2323 if (!message) {
2324 message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
2325 }
2326
2327 this.pushResult({
2328 result: !!result,
2329 actual: result,
2330 expected: true,
2331 message: message
2332 });
2333 }
2334 }, {
2335 key: "notOk",
2336 value: function notOk(result, message) {
2337 if (!message) {
2338 message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
2339 }
2340
2341 this.pushResult({
2342 result: !result,
2343 actual: result,
2344 expected: false,
2345 message: message
2346 });
2347 }
2348 }, {
2349 key: "equal",
2350 value: function equal(actual, expected, message) {
2351
2352 // eslint-disable-next-line eqeqeq
2353 var result = expected == actual;
2354
2355 this.pushResult({
2356 result: result,
2357 actual: actual,
2358 expected: expected,
2359 message: message
2360 });
2361 }
2362 }, {
2363 key: "notEqual",
2364 value: function notEqual(actual, expected, message) {
2365
2366 // eslint-disable-next-line eqeqeq
2367 var result = expected != actual;
2368
2369 this.pushResult({
2370 result: result,
2371 actual: actual,
2372 expected: expected,
2373 message: message,
2374 negative: true
2375 });
2376 }
2377 }, {
2378 key: "propEqual",
2379 value: function propEqual(actual, expected, message) {
2380 actual = objectValues(actual);
2381 expected = objectValues(expected);
2382
2383 this.pushResult({
2384 result: equiv(actual, expected),
2385 actual: actual,
2386 expected: expected,
2387 message: message
2388 });
2389 }
2390 }, {
2391 key: "notPropEqual",
2392 value: function notPropEqual(actual, expected, message) {
2393 actual = objectValues(actual);
2394 expected = objectValues(expected);
2395
2396 this.pushResult({
2397 result: !equiv(actual, expected),
2398 actual: actual,
2399 expected: expected,
2400 message: message,
2401 negative: true
2402 });
2403 }
2404 }, {
2405 key: "deepEqual",
2406 value: function deepEqual(actual, expected, message) {
2407 this.pushResult({
2408 result: equiv(actual, expected),
2409 actual: actual,
2410 expected: expected,
2411 message: message
2412 });
2413 }
2414 }, {
2415 key: "notDeepEqual",
2416 value: function notDeepEqual(actual, expected, message) {
2417 this.pushResult({
2418 result: !equiv(actual, expected),
2419 actual: actual,
2420 expected: expected,
2421 message: message,
2422 negative: true
2423 });
2424 }
2425 }, {
2426 key: "strictEqual",
2427 value: function strictEqual(actual, expected, message) {
2428 this.pushResult({
2429 result: expected === actual,
2430 actual: actual,
2431 expected: expected,
2432 message: message
2433 });
2434 }
2435 }, {
2436 key: "notStrictEqual",
2437 value: function notStrictEqual(actual, expected, message) {
2438 this.pushResult({
2439 result: expected !== actual,
2440 actual: actual,
2441 expected: expected,
2442 message: message,
2443 negative: true
2444 });
2445 }
2446 }, {
2447 key: "throws",
2448 value: function throws(block, expected, message) {
2449 var actual = void 0,
2450 result = false;
2451
2452 var currentTest = this instanceof Assert && this.test || config.current;
2453
2454 // 'expected' is optional unless doing string comparison
2455 if (objectType(expected) === "string") {
2456 if (message == null) {
2457 message = expected;
2458 expected = null;
2459 } else {
2460 throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary.");
2461 }
2462 }
2463
2464 currentTest.ignoreGlobalErrors = true;
2465 try {
2466 block.call(currentTest.testEnvironment);
2467 } catch (e) {
2468 actual = e;
2469 }
2470 currentTest.ignoreGlobalErrors = false;
2471
2472 if (actual) {
2473 var expectedType = objectType(expected);
2474
2475 // We don't want to validate thrown error
2476 if (!expected) {
2477 result = true;
2478 expected = null;
2479
2480 // Expected is a regexp
2481 } else if (expectedType === "regexp") {
2482 result = expected.test(errorString(actual));
2483
2484 // Expected is a constructor, maybe an Error constructor
2485 } else if (expectedType === "function" && actual instanceof expected) {
2486 result = true;
2487
2488 // Expected is an Error object
2489 } else if (expectedType === "object") {
2490 result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
2491
2492 // Expected is a validation function which returns true if validation passed
2493 } else if (expectedType === "function" && expected.call({}, actual) === true) {
2494 expected = null;
2495 result = true;
2496 }
2497 }
2498
2499 currentTest.assert.pushResult({
2500 result: result,
2501 actual: actual,
2502 expected: expected,
2503 message: message
2504 });
2505 }
2506 }, {
2507 key: "rejects",
2508 value: function rejects(promise, expected, message) {
2509 var result = false;
2510
2511 var currentTest = this instanceof Assert && this.test || config.current;
2512
2513 // 'expected' is optional unless doing string comparison
2514 if (objectType(expected) === "string") {
2515 if (message === undefined) {
2516 message = expected;
2517 expected = undefined;
2518 } else {
2519 message = "assert.rejects does not accept a string value for the expected " + "argument.\nUse a non-string object value (e.g. validator function) instead " + "if necessary.";
2520
2521 currentTest.assert.pushResult({
2522 result: false,
2523 message: message
2524 });
2525
2526 return;
2527 }
2528 }
2529
2530 var then = promise && promise.then;
2531 if (objectType(then) !== "function") {
2532 var _message = "The value provided to `assert.rejects` in " + "\"" + currentTest.testName + "\" was not a promise.";
2533
2534 currentTest.assert.pushResult({
2535 result: false,
2536 message: _message,
2537 actual: promise
2538 });
2539
2540 return;
2541 }
2542
2543 var done = this.async();
2544
2545 return then.call(promise, function handleFulfillment() {
2546 var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject.";
2547
2548 currentTest.assert.pushResult({
2549 result: false,
2550 message: message,
2551 actual: promise
2552 });
2553
2554 done();
2555 }, function handleRejection(actual) {
2556 var expectedType = objectType(expected);
2557
2558 // We don't want to validate
2559 if (expected === undefined) {
2560 result = true;
2561 expected = actual;
2562
2563 // Expected is a regexp
2564 } else if (expectedType === "regexp") {
2565 result = expected.test(errorString(actual));
2566
2567 // Expected is a constructor, maybe an Error constructor
2568 } else if (expectedType === "function" && actual instanceof expected) {
2569 result = true;
2570
2571 // Expected is an Error object
2572 } else if (expectedType === "object") {
2573 result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
2574
2575 // Expected is a validation function which returns true if validation passed
2576 } else {
2577 if (expectedType === "function") {
2578 result = expected.call({}, actual) === true;
2579 expected = null;
2580
2581 // Expected is some other invalid type
2582 } else {
2583 result = false;
2584 message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + ".";
2585 }
2586 }
2587
2588 currentTest.assert.pushResult({
2589 result: result,
2590 actual: actual,
2591 expected: expected,
2592 message: message
2593 });
2594
2595 done();
2596 });
2597 }
2598 }]);
2599 return Assert;
2600 }();
2601
2602 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
2603 // Known to us are: Closure Compiler, Narwhal
2604 // eslint-disable-next-line dot-notation
2605
2606
2607 Assert.prototype.raises = Assert.prototype["throws"];
2608
2609 /**
2610 * Converts an error into a simple string for comparisons.
2611 *
2612 * @param {Error} error
2613 * @return {String}
2614 */
2615 function errorString(error) {
2616 var resultErrorString = error.toString();
2617
2618 if (resultErrorString.substring(0, 7) === "[object") {
2619 var name = error.name ? error.name.toString() : "Error";
2620 var message = error.message ? error.message.toString() : "";
2621
2622 if (name && message) {
2623 return name + ": " + message;
2624 } else if (name) {
2625 return name;
2626 } else if (message) {
2627 return message;
2628 } else {
2629 return "Error";
2630 }
2631 } else {
2632 return resultErrorString;
2633 }
2634 }
2635
2636 /* global module, exports, define */
2637 function exportQUnit(QUnit) {
2638
2639 if (defined.document) {
2640
2641 // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
2642 if (window.QUnit && window.QUnit.version) {
2643 throw new Error("QUnit has already been defined.");
2644 }
2645
2646 window.QUnit = QUnit;
2647 }
2648
2649 // For nodejs
2650 if (typeof module !== "undefined" && module && module.exports) {
2651 module.exports = QUnit;
2652
2653 // For consistency with CommonJS environments' exports
2654 module.exports.QUnit = QUnit;
2655 }
2656
2657 // For CommonJS with exports, but without module.exports, like Rhino
2658 if (typeof exports !== "undefined" && exports) {
2659 exports.QUnit = QUnit;
2660 }
2661
2662 if (typeof define === "function" && define.amd) {
2663 define(function () {
2664 return QUnit;
2665 });
2666 QUnit.config.autostart = false;
2667 }
2668
2669 // For Web/Service Workers
2670 if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
2671 self$1.QUnit = QUnit;
2672 }
2673 }
2674
2675 var SuiteReport = function () {
2676 function SuiteReport(name, parentSuite) {
2677 classCallCheck(this, SuiteReport);
2678
2679 this.name = name;
2680 this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
2681
2682 this.tests = [];
2683 this.childSuites = [];
2684
2685 if (parentSuite) {
2686 parentSuite.pushChildSuite(this);
2687 }
2688 }
2689
2690 createClass(SuiteReport, [{
2691 key: "start",
2692 value: function start(recordTime) {
2693 if (recordTime) {
2694 this._startTime = Date.now();
2695 }
2696
2697 return {
2698 name: this.name,
2699 fullName: this.fullName.slice(),
2700 tests: this.tests.map(function (test) {
2701 return test.start();
2702 }),
2703 childSuites: this.childSuites.map(function (suite) {
2704 return suite.start();
2705 }),
2706 testCounts: {
2707 total: this.getTestCounts().total
2708 }
2709 };
2710 }
2711 }, {
2712 key: "end",
2713 value: function end(recordTime) {
2714 if (recordTime) {
2715 this._endTime = Date.now();
2716 }
2717
2718 return {
2719 name: this.name,
2720 fullName: this.fullName.slice(),
2721 tests: this.tests.map(function (test) {
2722 return test.end();
2723 }),
2724 childSuites: this.childSuites.map(function (suite) {
2725 return suite.end();
2726 }),
2727 testCounts: this.getTestCounts(),
2728 runtime: this.getRuntime(),
2729 status: this.getStatus()
2730 };
2731 }
2732 }, {
2733 key: "pushChildSuite",
2734 value: function pushChildSuite(suite) {
2735 this.childSuites.push(suite);
2736 }
2737 }, {
2738 key: "pushTest",
2739 value: function pushTest(test) {
2740 this.tests.push(test);
2741 }
2742 }, {
2743 key: "getRuntime",
2744 value: function getRuntime() {
2745 return this._endTime - this._startTime;
2746 }
2747 }, {
2748 key: "getTestCounts",
2749 value: function getTestCounts() {
2750 var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
2751
2752 counts = this.tests.reduce(function (counts, test) {
2753 if (test.valid) {
2754 counts[test.getStatus()]++;
2755 counts.total++;
2756 }
2757
2758 return counts;
2759 }, counts);
2760
2761 return this.childSuites.reduce(function (counts, suite) {
2762 return suite.getTestCounts(counts);
2763 }, counts);
2764 }
2765 }, {
2766 key: "getStatus",
2767 value: function getStatus() {
2768 var _getTestCounts = this.getTestCounts(),
2769 total = _getTestCounts.total,
2770 failed = _getTestCounts.failed,
2771 skipped = _getTestCounts.skipped,
2772 todo = _getTestCounts.todo;
2773
2774 if (failed) {
2775 return "failed";
2776 } else {
2777 if (skipped === total) {
2778 return "skipped";
2779 } else if (todo === total) {
2780 return "todo";
2781 } else {
2782 return "passed";
2783 }
2784 }
2785 }
2786 }]);
2787 return SuiteReport;
2788 }();
2789
2790 // Handle an unhandled exception. By convention, returns true if further
2791 // error handling should be suppressed and false otherwise.
2792 // In this case, we will only suppress further error handling if the
2793 // "ignoreGlobalErrors" configuration option is enabled.
2794 function onError(error) {
2795 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
2796 args[_key - 1] = arguments[_key];
2797 }
2798
2799 if (config.current) {
2800 if (config.current.ignoreGlobalErrors) {
2801 return true;
2802 }
2803 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2804 } else {
2805 test("global failure", extend(function () {
2806 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2807 }, { validTest: true }));
2808 }
2809
2810 return false;
2811 }
2812
2813 // Handle an unhandled rejection
2814 function onUnhandledRejection(reason) {
2815 var resultInfo = {
2816 result: false,
2817 message: reason.message || "error",
2818 actual: reason,
2819 source: reason.stack || sourceFromStacktrace(3)
2820 };
2821
2822 var currentTest = config.current;
2823 if (currentTest) {
2824 currentTest.assert.pushResult(resultInfo);
2825 } else {
2826 test("global failure", extend(function (assert) {
2827 assert.pushResult(resultInfo);
2828 }, { validTest: true }));
2829 }
2830 }
2831
2832 var focused = false;
2833 var QUnit = {};
2834 var globalSuite = new SuiteReport();
2835
2836 // The initial "currentModule" represents the global (or top-level) module that
2837 // is not explicitly defined by the user, therefore we add the "globalSuite" to
2838 // it since each module has a suiteReport associated with it.
2839 config.currentModule.suiteReport = globalSuite;
2840
2841 var moduleStack = [];
2842 var globalStartCalled = false;
2843 var runStarted = false;
2844
2845 // Figure out if we're running the tests from a server or not
2846 QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
2847
2848 // Expose the current QUnit version
2849 QUnit.version = "2.6.0";
2850
2851 function createModule(name, testEnvironment, modifiers) {
2852 var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
2853 var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
2854 var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
2855
2856 var skip$$1 = parentModule !== null && parentModule.skip || modifiers.skip;
2857 var todo$$1 = parentModule !== null && parentModule.todo || modifiers.todo;
2858
2859 var module = {
2860 name: moduleName,
2861 parentModule: parentModule,
2862 tests: [],
2863 moduleId: generateHash(moduleName),
2864 testsRun: 0,
2865 unskippedTestsRun: 0,
2866 childModules: [],
2867 suiteReport: new SuiteReport(name, parentSuite),
2868
2869 // Pass along `skip` and `todo` properties from parent module, in case
2870 // there is one, to childs. And use own otherwise.
2871 // This property will be used to mark own tests and tests of child suites
2872 // as either `skipped` or `todo`.
2873 skip: skip$$1,
2874 todo: skip$$1 ? false : todo$$1
2875 };
2876
2877 var env = {};
2878 if (parentModule) {
2879 parentModule.childModules.push(module);
2880 extend(env, parentModule.testEnvironment);
2881 }
2882 extend(env, testEnvironment);
2883 module.testEnvironment = env;
2884
2885 config.modules.push(module);
2886 return module;
2887 }
2888
2889 function processModule(name, options, executeNow) {
2890 var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
2891
2892 var module = createModule(name, options, modifiers);
2893
2894 // Move any hooks to a 'hooks' object
2895 var testEnvironment = module.testEnvironment;
2896 var hooks = module.hooks = {};
2897
2898 setHookFromEnvironment(hooks, testEnvironment, "before");
2899 setHookFromEnvironment(hooks, testEnvironment, "beforeEach");
2900 setHookFromEnvironment(hooks, testEnvironment, "afterEach");
2901 setHookFromEnvironment(hooks, testEnvironment, "after");
2902
2903 function setHookFromEnvironment(hooks, environment, name) {
2904 var potentialHook = environment[name];
2905 hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
2906 delete environment[name];
2907 }
2908
2909 var moduleFns = {
2910 before: setHookFunction(module, "before"),
2911 beforeEach: setHookFunction(module, "beforeEach"),
2912 afterEach: setHookFunction(module, "afterEach"),
2913 after: setHookFunction(module, "after")
2914 };
2915
2916 var currentModule = config.currentModule;
2917 if (objectType(executeNow) === "function") {
2918 moduleStack.push(module);
2919 config.currentModule = module;
2920 executeNow.call(module.testEnvironment, moduleFns);
2921 moduleStack.pop();
2922 module = module.parentModule || currentModule;
2923 }
2924
2925 config.currentModule = module;
2926 }
2927
2928 // TODO: extract this to a new file alongside its related functions
2929 function module$1(name, options, executeNow) {
2930 if (focused) {
2931 return;
2932 }
2933
2934 if (arguments.length === 2) {
2935 if (objectType(options) === "function") {
2936 executeNow = options;
2937 options = undefined;
2938 }
2939 }
2940
2941 processModule(name, options, executeNow);
2942 }
2943
2944 module$1.only = function () {
2945 if (focused) {
2946 return;
2947 }
2948
2949 config.modules.length = 0;
2950 config.queue.length = 0;
2951
2952 module$1.apply(undefined, arguments);
2953
2954 focused = true;
2955 };
2956
2957 module$1.skip = function (name, options, executeNow) {
2958 if (focused) {
2959 return;
2960 }
2961
2962 if (arguments.length === 2) {
2963 if (objectType(options) === "function") {
2964 executeNow = options;
2965 options = undefined;
2966 }
2967 }
2968
2969 processModule(name, options, executeNow, { skip: true });
2970 };
2971
2972 module$1.todo = function (name, options, executeNow) {
2973 if (focused) {
2974 return;
2975 }
2976
2977 if (arguments.length === 2) {
2978 if (objectType(options) === "function") {
2979 executeNow = options;
2980 options = undefined;
2981 }
2982 }
2983
2984 processModule(name, options, executeNow, { todo: true });
2985 };
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 function setHookFunction(module, hookName) {
3130 return function setHook(callback) {
3131 module.hooks[hookName].push(callback);
3132 };
3133 }
3134
3135 exportQUnit(QUnit);
3136
3137 (function () {
3138
3139 if (typeof window === "undefined" || typeof document === "undefined") {
3140 return;
3141 }
3142
3143 var config = QUnit.config,
3144 hasOwn = Object.prototype.hasOwnProperty;
3145
3146 // Stores fixture HTML for resetting later
3147 function storeFixture() {
3148
3149 // Avoid overwriting user-defined values
3150 if (hasOwn.call(config, "fixture")) {
3151 return;
3152 }
3153
3154 var fixture = document.getElementById("qunit-fixture");
3155 if (fixture) {
3156 config.fixture = fixture.cloneNode(true);
3157 }
3158 }
3159
3160 QUnit.begin(storeFixture);
3161
3162 // Resets the fixture DOM element if available.
3163 function resetFixture() {
3164 if (config.fixture == null) {
3165 return;
3166 }
3167
3168 var fixture = document.getElementById("qunit-fixture");
3169 var resetFixtureType = _typeof(config.fixture);
3170 if (resetFixtureType === "string") {
3171
3172 // support user defined values for `config.fixture`
3173 var newFixture = document.createElement("div");
3174 newFixture.setAttribute("id", "qunit-fixture");
3175 newFixture.innerHTML = config.fixture;
3176 fixture.parentNode.replaceChild(newFixture, fixture);
3177 } else {
3178 var clonedFixture = config.fixture.cloneNode(true);
3179 fixture.parentNode.replaceChild(clonedFixture, fixture);
3180 }
3181 }
3182
3183 QUnit.testStart(resetFixture);
3184 })();
3185
3186 (function () {
3187
3188 // Only interact with URLs via window.location
3189 var location = typeof window !== "undefined" && window.location;
3190 if (!location) {
3191 return;
3192 }
3193
3194 var urlParams = getUrlParams();
3195
3196 QUnit.urlParams = urlParams;
3197
3198 // Match module/test by inclusion in an array
3199 QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
3200 QUnit.config.testId = [].concat(urlParams.testId || []);
3201
3202 // Exact case-insensitive match of the module name
3203 QUnit.config.module = urlParams.module;
3204
3205 // Regular expression or case-insenstive substring match against "moduleName: testName"
3206 QUnit.config.filter = urlParams.filter;
3207
3208 // Test order randomization
3209 if (urlParams.seed === true) {
3210
3211 // Generate a random seed if the option is specified without a value
3212 QUnit.config.seed = Math.random().toString(36).slice(2);
3213 } else if (urlParams.seed) {
3214 QUnit.config.seed = urlParams.seed;
3215 }
3216
3217 // Add URL-parameter-mapped config values with UI form rendering data
3218 QUnit.config.urlConfig.push({
3219 id: "hidepassed",
3220 label: "Hide passed tests",
3221 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
3222 }, {
3223 id: "noglobals",
3224 label: "Check for Globals",
3225 tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
3226 }, {
3227 id: "notrycatch",
3228 label: "No try-catch",
3229 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
3230 });
3231
3232 QUnit.begin(function () {
3233 var i,
3234 option,
3235 urlConfig = QUnit.config.urlConfig;
3236
3237 for (i = 0; i < urlConfig.length; i++) {
3238
3239 // Options can be either strings or objects with nonempty "id" properties
3240 option = QUnit.config.urlConfig[i];
3241 if (typeof option !== "string") {
3242 option = option.id;
3243 }
3244
3245 if (QUnit.config[option] === undefined) {
3246 QUnit.config[option] = urlParams[option];
3247 }
3248 }
3249 });
3250
3251 function getUrlParams() {
3252 var i, param, name, value;
3253 var urlParams = Object.create(null);
3254 var params = location.search.slice(1).split("&");
3255 var length = params.length;
3256
3257 for (i = 0; i < length; i++) {
3258 if (params[i]) {
3259 param = params[i].split("=");
3260 name = decodeQueryParam(param[0]);
3261
3262 // Allow just a key to turn on a flag, e.g., test.html?noglobals
3263 value = param.length === 1 || decodeQueryParam(param.slice(1).join("="));
3264 if (name in urlParams) {
3265 urlParams[name] = [].concat(urlParams[name], value);
3266 } else {
3267 urlParams[name] = value;
3268 }
3269 }
3270 }
3271
3272 return urlParams;
3273 }
3274
3275 function decodeQueryParam(param) {
3276 return decodeURIComponent(param.replace(/\+/g, "%20"));
3277 }
3278 })();
3279
3280 var stats = {
3281 passedTests: 0,
3282 failedTests: 0,
3283 skippedTests: 0,
3284 todoTests: 0
3285 };
3286
3287 // Escape text for attribute or text content.
3288 function escapeText(s) {
3289 if (!s) {
3290 return "";
3291 }
3292 s = s + "";
3293
3294 // Both single quotes and double quotes (for attributes)
3295 return s.replace(/['"<>&]/g, function (s) {
3296 switch (s) {
3297 case "'":
3298 return "&#039;";
3299 case "\"":
3300 return "&quot;";
3301 case "<":
3302 return "&lt;";
3303 case ">":
3304 return "&gt;";
3305 case "&":
3306 return "&amp;";
3307 }
3308 });
3309 }
3310
3311 (function () {
3312
3313 // Don't load the HTML Reporter on non-browser environments
3314 if (typeof window === "undefined" || !window.document) {
3315 return;
3316 }
3317
3318 var config = QUnit.config,
3319 document$$1 = window.document,
3320 collapseNext = false,
3321 hasOwn = Object.prototype.hasOwnProperty,
3322 unfilteredUrl = setUrl({ filter: undefined, module: undefined,
3323 moduleId: undefined, testId: undefined }),
3324 modulesList = [];
3325
3326 function addEvent(elem, type, fn) {
3327 elem.addEventListener(type, fn, false);
3328 }
3329
3330 function removeEvent(elem, type, fn) {
3331 elem.removeEventListener(type, fn, false);
3332 }
3333
3334 function addEvents(elems, type, fn) {
3335 var i = elems.length;
3336 while (i--) {
3337 addEvent(elems[i], type, fn);
3338 }
3339 }
3340
3341 function hasClass(elem, name) {
3342 return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
3343 }
3344
3345 function addClass(elem, name) {
3346 if (!hasClass(elem, name)) {
3347 elem.className += (elem.className ? " " : "") + name;
3348 }
3349 }
3350
3351 function toggleClass(elem, name, force) {
3352 if (force || typeof force === "undefined" && !hasClass(elem, name)) {
3353 addClass(elem, name);
3354 } else {
3355 removeClass(elem, name);
3356 }
3357 }
3358
3359 function removeClass(elem, name) {
3360 var set = " " + elem.className + " ";
3361
3362 // Class name may appear multiple times
3363 while (set.indexOf(" " + name + " ") >= 0) {
3364 set = set.replace(" " + name + " ", " ");
3365 }
3366
3367 // Trim for prettiness
3368 elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
3369 }
3370
3371 function id(name) {
3372 return document$$1.getElementById && document$$1.getElementById(name);
3373 }
3374
3375 function abortTests() {
3376 var abortButton = id("qunit-abort-tests-button");
3377 if (abortButton) {
3378 abortButton.disabled = true;
3379 abortButton.innerHTML = "Aborting...";
3380 }
3381 QUnit.config.queue.length = 0;
3382 return false;
3383 }
3384
3385 function interceptNavigation(ev) {
3386 applyUrlParams();
3387
3388 if (ev && ev.preventDefault) {
3389 ev.preventDefault();
3390 }
3391
3392 return false;
3393 }
3394
3395 function getUrlConfigHtml() {
3396 var i,
3397 j,
3398 val,
3399 escaped,
3400 escapedTooltip,
3401 selection = false,
3402 urlConfig = config.urlConfig,
3403 urlConfigHtml = "";
3404
3405 for (i = 0; i < urlConfig.length; i++) {
3406
3407 // Options can be either strings or objects with nonempty "id" properties
3408 val = config.urlConfig[i];
3409 if (typeof val === "string") {
3410 val = {
3411 id: val,
3412 label: val
3413 };
3414 }
3415
3416 escaped = escapeText(val.id);
3417 escapedTooltip = escapeText(val.tooltip);
3418
3419 if (!val.value || typeof val.value === "string") {
3420 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'><input id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' type='checkbox'" + (val.value ? " value='" + escapeText(val.value) + "'" : "") + (config[val.id] ? " checked='checked'" : "") + " title='" + escapedTooltip + "' />" + escapeText(val.label) + "</label>";
3421 } else {
3422 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val.label + ": </label><select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3423
3424 if (QUnit.is("array", val.value)) {
3425 for (j = 0; j < val.value.length; j++) {
3426 escaped = escapeText(val.value[j]);
3427 urlConfigHtml += "<option value='" + escaped + "'" + (config[val.id] === val.value[j] ? (selection = true) && " selected='selected'" : "") + ">" + escaped + "</option>";
3428 }
3429 } else {
3430 for (j in val.value) {
3431 if (hasOwn.call(val.value, j)) {
3432 urlConfigHtml += "<option value='" + escapeText(j) + "'" + (config[val.id] === j ? (selection = true) && " selected='selected'" : "") + ">" + escapeText(val.value[j]) + "</option>";
3433 }
3434 }
3435 }
3436 if (config[val.id] && !selection) {
3437 escaped = escapeText(config[val.id]);
3438 urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3439 }
3440 urlConfigHtml += "</select>";
3441 }
3442 }
3443
3444 return urlConfigHtml;
3445 }
3446
3447 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3448 // Updates the URL with the new state of `config.urlConfig` values.
3449 function toolbarChanged() {
3450 var updatedUrl,
3451 value,
3452 tests,
3453 field = this,
3454 params = {};
3455
3456 // Detect if field is a select menu or a checkbox
3457 if ("selectedIndex" in field) {
3458 value = field.options[field.selectedIndex].value || undefined;
3459 } else {
3460 value = field.checked ? field.defaultValue || true : undefined;
3461 }
3462
3463 params[field.name] = value;
3464 updatedUrl = setUrl(params);
3465
3466 // Check if we can apply the change without a page refresh
3467 if ("hidepassed" === field.name && "replaceState" in window.history) {
3468 QUnit.urlParams[field.name] = value;
3469 config[field.name] = value || false;
3470 tests = id("qunit-tests");
3471 if (tests) {
3472 toggleClass(tests, "hidepass", value || false);
3473 }
3474 window.history.replaceState(null, "", updatedUrl);
3475 } else {
3476 window.location = updatedUrl;
3477 }
3478 }
3479
3480 function setUrl(params) {
3481 var key,
3482 arrValue,
3483 i,
3484 querystring = "?",
3485 location = window.location;
3486
3487 params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
3488
3489 for (key in params) {
3490
3491 // Skip inherited or undefined properties
3492 if (hasOwn.call(params, key) && params[key] !== undefined) {
3493
3494 // Output a parameter for each value of this key
3495 // (but usually just one)
3496 arrValue = [].concat(params[key]);
3497 for (i = 0; i < arrValue.length; i++) {
3498 querystring += encodeURIComponent(key);
3499 if (arrValue[i] !== true) {
3500 querystring += "=" + encodeURIComponent(arrValue[i]);
3501 }
3502 querystring += "&";
3503 }
3504 }
3505 }
3506 return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
3507 }
3508
3509 function applyUrlParams() {
3510 var i,
3511 selectedModules = [],
3512 modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
3513 filter = id("qunit-filter-input").value;
3514
3515 for (i = 0; i < modulesList.length; i++) {
3516 if (modulesList[i].checked) {
3517 selectedModules.push(modulesList[i].value);
3518 }
3519 }
3520
3521 window.location = setUrl({
3522 filter: filter === "" ? undefined : filter,
3523 moduleId: selectedModules.length === 0 ? undefined : selectedModules,
3524
3525 // Remove module and testId filter
3526 module: undefined,
3527 testId: undefined
3528 });
3529 }
3530
3531 function toolbarUrlConfigContainer() {
3532 var urlConfigContainer = document$$1.createElement("span");
3533
3534 urlConfigContainer.innerHTML = getUrlConfigHtml();
3535 addClass(urlConfigContainer, "qunit-url-config");
3536
3537 addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
3538 addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
3539
3540 return urlConfigContainer;
3541 }
3542
3543 function abortTestsButton() {
3544 var button = document$$1.createElement("button");
3545 button.id = "qunit-abort-tests-button";
3546 button.innerHTML = "Abort";
3547 addEvent(button, "click", abortTests);
3548 return button;
3549 }
3550
3551 function toolbarLooseFilter() {
3552 var filter = document$$1.createElement("form"),
3553 label = document$$1.createElement("label"),
3554 input = document$$1.createElement("input"),
3555 button = document$$1.createElement("button");
3556
3557 addClass(filter, "qunit-filter");
3558
3559 label.innerHTML = "Filter: ";
3560
3561 input.type = "text";
3562 input.value = config.filter || "";
3563 input.name = "filter";
3564 input.id = "qunit-filter-input";
3565
3566 button.innerHTML = "Go";
3567
3568 label.appendChild(input);
3569
3570 filter.appendChild(label);
3571 filter.appendChild(document$$1.createTextNode(" "));
3572 filter.appendChild(button);
3573 addEvent(filter, "submit", interceptNavigation);
3574
3575 return filter;
3576 }
3577
3578 function moduleListHtml() {
3579 var i,
3580 checked,
3581 html = "";
3582
3583 for (i = 0; i < config.modules.length; i++) {
3584 if (config.modules[i].name !== "") {
3585 checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1;
3586 html += "<li><label class='clickable" + (checked ? " checked" : "") + "'><input type='checkbox' " + "value='" + config.modules[i].moduleId + "'" + (checked ? " checked='checked'" : "") + " />" + escapeText(config.modules[i].name) + "</label></li>";
3587 }
3588 }
3589
3590 return html;
3591 }
3592
3593 function toolbarModuleFilter() {
3594 var allCheckbox,
3595 commit,
3596 reset,
3597 moduleFilter = document$$1.createElement("form"),
3598 label = document$$1.createElement("label"),
3599 moduleSearch = document$$1.createElement("input"),
3600 dropDown = document$$1.createElement("div"),
3601 actions = document$$1.createElement("span"),
3602 dropDownList = document$$1.createElement("ul"),
3603 dirty = false;
3604
3605 moduleSearch.id = "qunit-modulefilter-search";
3606 addEvent(moduleSearch, "input", searchInput);
3607 addEvent(moduleSearch, "input", searchFocus);
3608 addEvent(moduleSearch, "focus", searchFocus);
3609 addEvent(moduleSearch, "click", searchFocus);
3610
3611 label.id = "qunit-modulefilter-search-container";
3612 label.innerHTML = "Module: ";
3613 label.appendChild(moduleSearch);
3614
3615 actions.id = "qunit-modulefilter-actions";
3616 actions.innerHTML = "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + (config.moduleId.length ? "" : " checked") + "'><input type='checkbox'" + (config.moduleId.length ? "" : " checked='checked'") + ">All modules</label>";
3617 allCheckbox = actions.lastChild.firstChild;
3618 commit = actions.firstChild;
3619 reset = commit.nextSibling;
3620 addEvent(commit, "click", applyUrlParams);
3621
3622 dropDownList.id = "qunit-modulefilter-dropdown-list";
3623 dropDownList.innerHTML = moduleListHtml();
3624
3625 dropDown.id = "qunit-modulefilter-dropdown";
3626 dropDown.style.display = "none";
3627 dropDown.appendChild(actions);
3628 dropDown.appendChild(dropDownList);
3629 addEvent(dropDown, "change", selectionChange);
3630 selectionChange();
3631
3632 moduleFilter.id = "qunit-modulefilter";
3633 moduleFilter.appendChild(label);
3634 moduleFilter.appendChild(dropDown);
3635 addEvent(moduleFilter, "submit", interceptNavigation);
3636 addEvent(moduleFilter, "reset", function () {
3637
3638 // Let the reset happen, then update styles
3639 window.setTimeout(selectionChange);
3640 });
3641
3642 // Enables show/hide for the dropdown
3643 function searchFocus() {
3644 if (dropDown.style.display !== "none") {
3645 return;
3646 }
3647
3648 dropDown.style.display = "block";
3649 addEvent(document$$1, "click", hideHandler);
3650 addEvent(document$$1, "keydown", hideHandler);
3651
3652 // Hide on Escape keydown or outside-container click
3653 function hideHandler(e) {
3654 var inContainer = moduleFilter.contains(e.target);
3655
3656 if (e.keyCode === 27 || !inContainer) {
3657 if (e.keyCode === 27 && inContainer) {
3658 moduleSearch.focus();
3659 }
3660 dropDown.style.display = "none";
3661 removeEvent(document$$1, "click", hideHandler);
3662 removeEvent(document$$1, "keydown", hideHandler);
3663 moduleSearch.value = "";
3664 searchInput();
3665 }
3666 }
3667 }
3668
3669 // Processes module search box input
3670 function searchInput() {
3671 var i,
3672 item,
3673 searchText = moduleSearch.value.toLowerCase(),
3674 listItems = dropDownList.children;
3675
3676 for (i = 0; i < listItems.length; i++) {
3677 item = listItems[i];
3678 if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) {
3679 item.style.display = "";
3680 } else {
3681 item.style.display = "none";
3682 }
3683 }
3684 }
3685
3686 // Processes selection changes
3687 function selectionChange(evt) {
3688 var i,
3689 item,
3690 checkbox = evt && evt.target || allCheckbox,
3691 modulesList = dropDownList.getElementsByTagName("input"),
3692 selectedNames = [];
3693
3694 toggleClass(checkbox.parentNode, "checked", checkbox.checked);
3695
3696 dirty = false;
3697 if (checkbox.checked && checkbox !== allCheckbox) {
3698 allCheckbox.checked = false;
3699 removeClass(allCheckbox.parentNode, "checked");
3700 }
3701 for (i = 0; i < modulesList.length; i++) {
3702 item = modulesList[i];
3703 if (!evt) {
3704 toggleClass(item.parentNode, "checked", item.checked);
3705 } else if (checkbox === allCheckbox && checkbox.checked) {
3706 item.checked = false;
3707 removeClass(item.parentNode, "checked");
3708 }
3709 dirty = dirty || item.checked !== item.defaultChecked;
3710 if (item.checked) {
3711 selectedNames.push(item.parentNode.textContent);
3712 }
3713 }
3714
3715 commit.style.display = reset.style.display = dirty ? "" : "none";
3716 moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent;
3717 moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent);
3718 }
3719
3720 return moduleFilter;
3721 }
3722
3723 function appendToolbar() {
3724 var toolbar = id("qunit-testrunner-toolbar");
3725
3726 if (toolbar) {
3727 toolbar.appendChild(toolbarUrlConfigContainer());
3728 toolbar.appendChild(toolbarModuleFilter());
3729 toolbar.appendChild(toolbarLooseFilter());
3730 toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
3731 }
3732 }
3733
3734 function appendHeader() {
3735 var header = id("qunit-header");
3736
3737 if (header) {
3738 header.innerHTML = "<a href='" + escapeText(unfilteredUrl) + "'>" + header.innerHTML + "</a> ";
3739 }
3740 }
3741
3742 function appendBanner() {
3743 var banner = id("qunit-banner");
3744
3745 if (banner) {
3746 banner.className = "";
3747 }
3748 }
3749
3750 function appendTestResults() {
3751 var tests = id("qunit-tests"),
3752 result = id("qunit-testresult"),
3753 controls;
3754
3755 if (result) {
3756 result.parentNode.removeChild(result);
3757 }
3758
3759 if (tests) {
3760 tests.innerHTML = "";
3761 result = document$$1.createElement("p");
3762 result.id = "qunit-testresult";
3763 result.className = "result";
3764 tests.parentNode.insertBefore(result, tests);
3765 result.innerHTML = "<div id=\"qunit-testresult-display\">Running...<br />&#160;</div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
3766 controls = id("qunit-testresult-controls");
3767 }
3768
3769 if (controls) {
3770 controls.appendChild(abortTestsButton());
3771 }
3772 }
3773
3774 function appendFilteredTest() {
3775 var testId = QUnit.config.testId;
3776 if (!testId || testId.length <= 0) {
3777 return "";
3778 }
3779 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl) + "'>Run all tests</a></div>";
3780 }
3781
3782 function appendUserAgent() {
3783 var userAgent = id("qunit-userAgent");
3784
3785 if (userAgent) {
3786 userAgent.innerHTML = "";
3787 userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
3788 }
3789 }
3790
3791 function appendInterface() {
3792 var qunit = id("qunit");
3793
3794 if (qunit) {
3795 qunit.innerHTML = "<h1 id='qunit-header'>" + escapeText(document$$1.title) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
3796 }
3797
3798 appendHeader();
3799 appendBanner();
3800 appendTestResults();
3801 appendUserAgent();
3802 appendToolbar();
3803 }
3804
3805 function appendTestsList(modules) {
3806 var i, l, x, z, test, moduleObj;
3807
3808 for (i = 0, l = modules.length; i < l; i++) {
3809 moduleObj = modules[i];
3810
3811 for (x = 0, z = moduleObj.tests.length; x < z; x++) {
3812 test = moduleObj.tests[x];
3813
3814 appendTest(test.name, test.testId, moduleObj.name);
3815 }
3816 }
3817 }
3818
3819 function appendTest(name, testId, moduleName) {
3820 var title,
3821 rerunTrigger,
3822 testBlock,
3823 assertList,
3824 tests = id("qunit-tests");
3825
3826 if (!tests) {
3827 return;
3828 }
3829
3830 title = document$$1.createElement("strong");
3831 title.innerHTML = getNameHtml(name, moduleName);
3832
3833 rerunTrigger = document$$1.createElement("a");
3834 rerunTrigger.innerHTML = "Rerun";
3835 rerunTrigger.href = setUrl({ testId: testId });
3836
3837 testBlock = document$$1.createElement("li");
3838 testBlock.appendChild(title);
3839 testBlock.appendChild(rerunTrigger);
3840 testBlock.id = "qunit-test-output-" + testId;
3841
3842 assertList = document$$1.createElement("ol");
3843 assertList.className = "qunit-assert-list";
3844
3845 testBlock.appendChild(assertList);
3846
3847 tests.appendChild(testBlock);
3848 }
3849
3850 // HTML Reporter initialization and load
3851 QUnit.begin(function (details) {
3852 var i, moduleObj, tests;
3853
3854 // Sort modules by name for the picker
3855 for (i = 0; i < details.modules.length; i++) {
3856 moduleObj = details.modules[i];
3857 if (moduleObj.name) {
3858 modulesList.push(moduleObj.name);
3859 }
3860 }
3861 modulesList.sort(function (a, b) {
3862 return a.localeCompare(b);
3863 });
3864
3865 // Initialize QUnit elements
3866 appendInterface();
3867 appendTestsList(details.modules);
3868 tests = id("qunit-tests");
3869 if (tests && config.hidepassed) {
3870 addClass(tests, "hidepass");
3871 }
3872 });
3873
3874 QUnit.done(function (details) {
3875 var banner = id("qunit-banner"),
3876 tests = id("qunit-tests"),
3877 abortButton = id("qunit-abort-tests-button"),
3878 totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
3879 html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
3880 test,
3881 assertLi,
3882 assertList;
3883
3884 // Update remaing tests to aborted
3885 if (abortButton && abortButton.disabled) {
3886 html = "Tests aborted after " + details.runtime + " milliseconds.";
3887
3888 for (var i = 0; i < tests.children.length; i++) {
3889 test = tests.children[i];
3890 if (test.className === "" || test.className === "running") {
3891 test.className = "aborted";
3892 assertList = test.getElementsByTagName("ol")[0];
3893 assertLi = document$$1.createElement("li");
3894 assertLi.className = "fail";
3895 assertLi.innerHTML = "Test aborted.";
3896 assertList.appendChild(assertLi);
3897 }
3898 }
3899 }
3900
3901 if (banner && (!abortButton || abortButton.disabled === false)) {
3902 banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
3903 }
3904
3905 if (abortButton) {
3906 abortButton.parentNode.removeChild(abortButton);
3907 }
3908
3909 if (tests) {
3910 id("qunit-testresult-display").innerHTML = html;
3911 }
3912
3913 if (config.altertitle && document$$1.title) {
3914
3915 // Show ✖ for good, ✔ for bad suite result in title
3916 // use escape sequences in case file gets loaded with non-utf-8
3917 // charset
3918 document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
3919 }
3920
3921 // Scroll back to top to show results
3922 if (config.scrolltop && window.scrollTo) {
3923 window.scrollTo(0, 0);
3924 }
3925 });
3926
3927 function getNameHtml(name, module) {
3928 var nameHtml = "";
3929
3930 if (module) {
3931 nameHtml = "<span class='module-name'>" + escapeText(module) + "</span>: ";
3932 }
3933
3934 nameHtml += "<span class='test-name'>" + escapeText(name) + "</span>";
3935
3936 return nameHtml;
3937 }
3938
3939 QUnit.testStart(function (details) {
3940 var running, testBlock, bad;
3941
3942 testBlock = id("qunit-test-output-" + details.testId);
3943 if (testBlock) {
3944 testBlock.className = "running";
3945 } else {
3946
3947 // Report later registered tests
3948 appendTest(details.name, details.testId, details.module);
3949 }
3950
3951 running = id("qunit-testresult-display");
3952 if (running) {
3953 bad = QUnit.config.reorder && details.previousFailure;
3954
3955 running.innerHTML = [bad ? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details.name, details.module)].join("");
3956 }
3957 });
3958
3959 function stripHtml(string) {
3960
3961 // Strip tags, html entity and whitespaces
3962 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
3963 }
3964
3965 QUnit.log(function (details) {
3966 var assertList,
3967 assertLi,
3968 message,
3969 expected,
3970 actual,
3971 diff,
3972 showDiff = false,
3973 testItem = id("qunit-test-output-" + details.testId);
3974
3975 if (!testItem) {
3976 return;
3977 }
3978
3979 message = escapeText(details.message) || (details.result ? "okay" : "failed");
3980 message = "<span class='test-message'>" + message + "</span>";
3981 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3982
3983 // The pushFailure doesn't provide details.expected
3984 // when it calls, it's implicit to also not show expected and diff stuff
3985 // Also, we need to check details.expected existence, as it can exist and be undefined
3986 if (!details.result && hasOwn.call(details, "expected")) {
3987 if (details.negative) {
3988 expected = "NOT " + QUnit.dump.parse(details.expected);
3989 } else {
3990 expected = QUnit.dump.parse(details.expected);
3991 }
3992
3993 actual = QUnit.dump.parse(details.actual);
3994 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected) + "</pre></td></tr>";
3995
3996 if (actual !== expected) {
3997
3998 message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
3999
4000 if (typeof details.actual === "number" && typeof details.expected === "number") {
4001 if (!isNaN(details.actual) && !isNaN(details.expected)) {
4002 showDiff = true;
4003 diff = details.actual - details.expected;
4004 diff = (diff > 0 ? "+" : "") + diff;
4005 }
4006 } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
4007 diff = QUnit.diff(expected, actual);
4008
4009 // don't show diff if there is zero overlap
4010 showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
4011 }
4012
4013 if (showDiff) {
4014 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
4015 }
4016 } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) {
4017 message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the depth of object is more than current max depth (" + QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " + " run with a higher max depth or <a href='" + escapeText(setUrl({ maxDepth: -1 })) + "'>" + "Rerun</a> without max depth.</p></td></tr>";
4018 } else {
4019 message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
4020 }
4021
4022 if (details.source) {
4023 message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>";
4024 }
4025
4026 message += "</table>";
4027
4028 // This occurs when pushFailure is set and we have an extracted stack trace
4029 } else if (!details.result && details.source) {
4030 message += "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>" + "</table>";
4031 }
4032
4033 assertList = testItem.getElementsByTagName("ol")[0];
4034
4035 assertLi = document$$1.createElement("li");
4036 assertLi.className = details.result ? "pass" : "fail";
4037 assertLi.innerHTML = message;
4038 assertList.appendChild(assertLi);
4039 });
4040
4041 QUnit.testDone(function (details) {
4042 var testTitle,
4043 time,
4044 testItem,
4045 assertList,
4046 good,
4047 bad,
4048 testCounts,
4049 skipped,
4050 sourceName,
4051 tests = id("qunit-tests");
4052
4053 if (!tests) {
4054 return;
4055 }
4056
4057 testItem = id("qunit-test-output-" + details.testId);
4058
4059 assertList = testItem.getElementsByTagName("ol")[0];
4060
4061 good = details.passed;
4062 bad = details.failed;
4063
4064 // This test passed if it has no unexpected failed assertions
4065 var testPassed = details.failed > 0 ? details.todo : !details.todo;
4066
4067 if (testPassed) {
4068
4069 // Collapse the passing tests
4070 addClass(assertList, "qunit-collapsed");
4071 } else if (config.collapse) {
4072 if (!collapseNext) {
4073
4074 // Skip collapsing the first failing test
4075 collapseNext = true;
4076 } else {
4077
4078 // Collapse remaining tests
4079 addClass(assertList, "qunit-collapsed");
4080 }
4081 }
4082
4083 // The testItem.firstChild is the test name
4084 testTitle = testItem.firstChild;
4085
4086 testCounts = bad ? "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : "";
4087
4088 testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
4089
4090 if (details.skipped) {
4091 stats.skippedTests++;
4092
4093 testItem.className = "skipped";
4094 skipped = document$$1.createElement("em");
4095 skipped.className = "qunit-skipped-label";
4096 skipped.innerHTML = "skipped";
4097 testItem.insertBefore(skipped, testTitle);
4098 } else {
4099 addEvent(testTitle, "click", function () {
4100 toggleClass(assertList, "qunit-collapsed");
4101 });
4102
4103 testItem.className = testPassed ? "pass" : "fail";
4104
4105 if (details.todo) {
4106 var todoLabel = document$$1.createElement("em");
4107 todoLabel.className = "qunit-todo-label";
4108 todoLabel.innerHTML = "todo";
4109 testItem.className += " todo";
4110 testItem.insertBefore(todoLabel, testTitle);
4111 }
4112
4113 time = document$$1.createElement("span");
4114 time.className = "runtime";
4115 time.innerHTML = details.runtime + " ms";
4116 testItem.insertBefore(time, assertList);
4117
4118 if (!testPassed) {
4119 stats.failedTests++;
4120 } else if (details.todo) {
4121 stats.todoTests++;
4122 } else {
4123 stats.passedTests++;
4124 }
4125 }
4126
4127 // Show the source of the test when showing assertions
4128 if (details.source) {
4129 sourceName = document$$1.createElement("p");
4130 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
4131 addClass(sourceName, "qunit-source");
4132 if (testPassed) {
4133 addClass(sourceName, "qunit-collapsed");
4134 }
4135 addEvent(testTitle, "click", function () {
4136 toggleClass(sourceName, "qunit-collapsed");
4137 });
4138 testItem.appendChild(sourceName);
4139 }
4140 });
4141
4142 // Avoid readyState issue with phantomjs
4143 // Ref: #818
4144 var notPhantom = function (p) {
4145 return !(p && p.version && p.version.major > 0);
4146 }(window.phantom);
4147
4148 if (notPhantom && document$$1.readyState === "complete") {
4149 QUnit.load();
4150 } else {
4151 addEvent(window, "load", QUnit.load);
4152 }
4153
4154 // Wrap window.onerror. We will call the original window.onerror to see if
4155 // the existing handler fully handles the error; if not, we will call the
4156 // QUnit.onError function.
4157 var originalWindowOnError = window.onerror;
4158
4159 // Cover uncaught exceptions
4160 // Returning true will suppress the default browser handler,
4161 // returning false will let it run.
4162 window.onerror = function (message, fileName, lineNumber) {
4163 var ret = false;
4164 if (originalWindowOnError) {
4165 for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
4166 args[_key - 3] = arguments[_key];
4167 }
4168
4169 ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
4170 }
4171
4172 // Treat return value as window.onerror itself does,
4173 // Only do our handling if not suppressed.
4174 if (ret !== true) {
4175 var error = {
4176 message: message,
4177 fileName: fileName,
4178 lineNumber: lineNumber
4179 };
4180
4181 ret = QUnit.onError(error);
4182 }
4183
4184 return ret;
4185 };
4186
4187 // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
4188 window.addEventListener("unhandledrejection", function (event) {
4189 QUnit.onUnhandledRejection(event.reason);
4190 });
4191 })();
4192
4193 /*
4194 * This file is a modified version of google-diff-match-patch's JavaScript implementation
4195 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
4196 * modifications are licensed as more fully set forth in LICENSE.txt.
4197 *
4198 * The original source of google-diff-match-patch is attributable and licensed as follows:
4199 *
4200 * Copyright 2006 Google Inc.
4201 * https://code.google.com/p/google-diff-match-patch/
4202 *
4203 * Licensed under the Apache License, Version 2.0 (the "License");
4204 * you may not use this file except in compliance with the License.
4205 * You may obtain a copy of the License at
4206 *
4207 * https://www.apache.org/licenses/LICENSE-2.0
4208 *
4209 * Unless required by applicable law or agreed to in writing, software
4210 * distributed under the License is distributed on an "AS IS" BASIS,
4211 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4212 * See the License for the specific language governing permissions and
4213 * limitations under the License.
4214 *
4215 * More Info:
4216 * https://code.google.com/p/google-diff-match-patch/
4217 *
4218 * Usage: QUnit.diff(expected, actual)
4219 *
4220 */
4221 QUnit.diff = function () {
4222 function DiffMatchPatch() {}
4223
4224 // DIFF FUNCTIONS
4225
4226 /**
4227 * The data structure representing a diff is an array of tuples:
4228 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
4229 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
4230 */
4231 var DIFF_DELETE = -1,
4232 DIFF_INSERT = 1,
4233 DIFF_EQUAL = 0;
4234
4235 /**
4236 * Find the differences between two texts. Simplifies the problem by stripping
4237 * any common prefix or suffix off the texts before diffing.
4238 * @param {string} text1 Old string to be diffed.
4239 * @param {string} text2 New string to be diffed.
4240 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
4241 * then don't run a line-level diff first to identify the changed areas.
4242 * Defaults to true, which does a faster, slightly less optimal diff.
4243 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4244 */
4245 DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
4246 var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs;
4247
4248 // The diff must be complete in up to 1 second.
4249 deadline = new Date().getTime() + 1000;
4250
4251 // Check for null inputs.
4252 if (text1 === null || text2 === null) {
4253 throw new Error("Null input. (DiffMain)");
4254 }
4255
4256 // Check for equality (speedup).
4257 if (text1 === text2) {
4258 if (text1) {
4259 return [[DIFF_EQUAL, text1]];
4260 }
4261 return [];
4262 }
4263
4264 if (typeof optChecklines === "undefined") {
4265 optChecklines = true;
4266 }
4267
4268 checklines = optChecklines;
4269
4270 // Trim off common prefix (speedup).
4271 commonlength = this.diffCommonPrefix(text1, text2);
4272 commonprefix = text1.substring(0, commonlength);
4273 text1 = text1.substring(commonlength);
4274 text2 = text2.substring(commonlength);
4275
4276 // Trim off common suffix (speedup).
4277 commonlength = this.diffCommonSuffix(text1, text2);
4278 commonsuffix = text1.substring(text1.length - commonlength);
4279 text1 = text1.substring(0, text1.length - commonlength);
4280 text2 = text2.substring(0, text2.length - commonlength);
4281
4282 // Compute the diff on the middle block.
4283 diffs = this.diffCompute(text1, text2, checklines, deadline);
4284
4285 // Restore the prefix and suffix.
4286 if (commonprefix) {
4287 diffs.unshift([DIFF_EQUAL, commonprefix]);
4288 }
4289 if (commonsuffix) {
4290 diffs.push([DIFF_EQUAL, commonsuffix]);
4291 }
4292 this.diffCleanupMerge(diffs);
4293 return diffs;
4294 };
4295
4296 /**
4297 * Reduce the number of edits by eliminating operationally trivial equalities.
4298 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4299 */
4300 DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
4301 var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
4302 changes = false;
4303 equalities = []; // Stack of indices where equalities are found.
4304 equalitiesLength = 0; // Keeping our own length var is faster in JS.
4305 /** @type {?string} */
4306 lastequality = null;
4307
4308 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4309 pointer = 0; // Index of current position.
4310
4311 // Is there an insertion operation before the last equality.
4312 preIns = false;
4313
4314 // Is there a deletion operation before the last equality.
4315 preDel = false;
4316
4317 // Is there an insertion operation after the last equality.
4318 postIns = false;
4319
4320 // Is there a deletion operation after the last equality.
4321 postDel = false;
4322 while (pointer < diffs.length) {
4323
4324 // Equality found.
4325 if (diffs[pointer][0] === DIFF_EQUAL) {
4326 if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
4327
4328 // Candidate found.
4329 equalities[equalitiesLength++] = pointer;
4330 preIns = postIns;
4331 preDel = postDel;
4332 lastequality = diffs[pointer][1];
4333 } else {
4334
4335 // Not a candidate, and can never become one.
4336 equalitiesLength = 0;
4337 lastequality = null;
4338 }
4339 postIns = postDel = false;
4340
4341 // An insertion or deletion.
4342 } else {
4343
4344 if (diffs[pointer][0] === DIFF_DELETE) {
4345 postDel = true;
4346 } else {
4347 postIns = true;
4348 }
4349
4350 /*
4351 * Five types to be split:
4352 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
4353 * <ins>A</ins>X<ins>C</ins><del>D</del>
4354 * <ins>A</ins><del>B</del>X<ins>C</ins>
4355 * <ins>A</del>X<ins>C</ins><del>D</del>
4356 * <ins>A</ins><del>B</del>X<del>C</del>
4357 */
4358 if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
4359
4360 // Duplicate record.
4361 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4362
4363 // Change second copy to insert.
4364 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4365 equalitiesLength--; // Throw away the equality we just deleted;
4366 lastequality = null;
4367 if (preIns && preDel) {
4368
4369 // No changes made which could affect previous entry, keep going.
4370 postIns = postDel = true;
4371 equalitiesLength = 0;
4372 } else {
4373 equalitiesLength--; // Throw away the previous equality.
4374 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4375 postIns = postDel = false;
4376 }
4377 changes = true;
4378 }
4379 }
4380 pointer++;
4381 }
4382
4383 if (changes) {
4384 this.diffCleanupMerge(diffs);
4385 }
4386 };
4387
4388 /**
4389 * Convert a diff array into a pretty HTML report.
4390 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4391 * @param {integer} string to be beautified.
4392 * @return {string} HTML representation.
4393 */
4394 DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
4395 var op,
4396 data,
4397 x,
4398 html = [];
4399 for (x = 0; x < diffs.length; x++) {
4400 op = diffs[x][0]; // Operation (insert, delete, equal)
4401 data = diffs[x][1]; // Text of change.
4402 switch (op) {
4403 case DIFF_INSERT:
4404 html[x] = "<ins>" + escapeText(data) + "</ins>";
4405 break;
4406 case DIFF_DELETE:
4407 html[x] = "<del>" + escapeText(data) + "</del>";
4408 break;
4409 case DIFF_EQUAL:
4410 html[x] = "<span>" + escapeText(data) + "</span>";
4411 break;
4412 }
4413 }
4414 return html.join("");
4415 };
4416
4417 /**
4418 * Determine the common prefix of two strings.
4419 * @param {string} text1 First string.
4420 * @param {string} text2 Second string.
4421 * @return {number} The number of characters common to the start of each
4422 * string.
4423 */
4424 DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
4425 var pointermid, pointermax, pointermin, pointerstart;
4426
4427 // Quick check for common null cases.
4428 if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
4429 return 0;
4430 }
4431
4432 // Binary search.
4433 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4434 pointermin = 0;
4435 pointermax = Math.min(text1.length, text2.length);
4436 pointermid = pointermax;
4437 pointerstart = 0;
4438 while (pointermin < pointermid) {
4439 if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
4440 pointermin = pointermid;
4441 pointerstart = pointermin;
4442 } else {
4443 pointermax = pointermid;
4444 }
4445 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4446 }
4447 return pointermid;
4448 };
4449
4450 /**
4451 * Determine the common suffix of two strings.
4452 * @param {string} text1 First string.
4453 * @param {string} text2 Second string.
4454 * @return {number} The number of characters common to the end of each string.
4455 */
4456 DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
4457 var pointermid, pointermax, pointermin, pointerend;
4458
4459 // Quick check for common null cases.
4460 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
4461 return 0;
4462 }
4463
4464 // Binary search.
4465 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4466 pointermin = 0;
4467 pointermax = Math.min(text1.length, text2.length);
4468 pointermid = pointermax;
4469 pointerend = 0;
4470 while (pointermin < pointermid) {
4471 if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) {
4472 pointermin = pointermid;
4473 pointerend = pointermin;
4474 } else {
4475 pointermax = pointermid;
4476 }
4477 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4478 }
4479 return pointermid;
4480 };
4481
4482 /**
4483 * Find the differences between two texts. Assumes that the texts do not
4484 * have any common prefix or suffix.
4485 * @param {string} text1 Old string to be diffed.
4486 * @param {string} text2 New string to be diffed.
4487 * @param {boolean} checklines Speedup flag. If false, then don't run a
4488 * line-level diff first to identify the changed areas.
4489 * If true, then run a faster, slightly less optimal diff.
4490 * @param {number} deadline Time when the diff should be complete by.
4491 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4492 * @private
4493 */
4494 DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
4495 var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
4496
4497 if (!text1) {
4498
4499 // Just add some text (speedup).
4500 return [[DIFF_INSERT, text2]];
4501 }
4502
4503 if (!text2) {
4504
4505 // Just delete some text (speedup).
4506 return [[DIFF_DELETE, text1]];
4507 }
4508
4509 longtext = text1.length > text2.length ? text1 : text2;
4510 shorttext = text1.length > text2.length ? text2 : text1;
4511 i = longtext.indexOf(shorttext);
4512 if (i !== -1) {
4513
4514 // Shorter text is inside the longer text (speedup).
4515 diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
4516
4517 // Swap insertions for deletions if diff is reversed.
4518 if (text1.length > text2.length) {
4519 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
4520 }
4521 return diffs;
4522 }
4523
4524 if (shorttext.length === 1) {
4525
4526 // Single character string.
4527 // After the previous speedup, the character can't be an equality.
4528 return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4529 }
4530
4531 // Check to see if the problem can be split in two.
4532 hm = this.diffHalfMatch(text1, text2);
4533 if (hm) {
4534
4535 // A half-match was found, sort out the return data.
4536 text1A = hm[0];
4537 text1B = hm[1];
4538 text2A = hm[2];
4539 text2B = hm[3];
4540 midCommon = hm[4];
4541
4542 // Send both pairs off for separate processing.
4543 diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
4544 diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
4545
4546 // Merge the results.
4547 return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
4548 }
4549
4550 if (checklines && text1.length > 100 && text2.length > 100) {
4551 return this.diffLineMode(text1, text2, deadline);
4552 }
4553
4554 return this.diffBisect(text1, text2, deadline);
4555 };
4556
4557 /**
4558 * Do the two texts share a substring which is at least half the length of the
4559 * longer text?
4560 * This speedup can produce non-minimal diffs.
4561 * @param {string} text1 First string.
4562 * @param {string} text2 Second string.
4563 * @return {Array.<string>} Five element Array, containing the prefix of
4564 * text1, the suffix of text1, the prefix of text2, the suffix of
4565 * text2 and the common middle. Or null if there was no match.
4566 * @private
4567 */
4568 DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
4569 var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
4570
4571 longtext = text1.length > text2.length ? text1 : text2;
4572 shorttext = text1.length > text2.length ? text2 : text1;
4573 if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
4574 return null; // Pointless.
4575 }
4576 dmp = this; // 'this' becomes 'window' in a closure.
4577
4578 /**
4579 * Does a substring of shorttext exist within longtext such that the substring
4580 * is at least half the length of longtext?
4581 * Closure, but does not reference any external variables.
4582 * @param {string} longtext Longer string.
4583 * @param {string} shorttext Shorter string.
4584 * @param {number} i Start index of quarter length substring within longtext.
4585 * @return {Array.<string>} Five element Array, containing the prefix of
4586 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
4587 * of shorttext and the common middle. Or null if there was no match.
4588 * @private
4589 */
4590 function diffHalfMatchI(longtext, shorttext, i) {
4591 var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
4592
4593 // Start with a 1/4 length substring at position i as a seed.
4594 seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
4595 j = -1;
4596 bestCommon = "";
4597 while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
4598 prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j));
4599 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
4600 if (bestCommon.length < suffixLength + prefixLength) {
4601 bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
4602 bestLongtextA = longtext.substring(0, i - suffixLength);
4603 bestLongtextB = longtext.substring(i + prefixLength);
4604 bestShorttextA = shorttext.substring(0, j - suffixLength);
4605 bestShorttextB = shorttext.substring(j + prefixLength);
4606 }
4607 }
4608 if (bestCommon.length * 2 >= longtext.length) {
4609 return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
4610 } else {
4611 return null;
4612 }
4613 }
4614
4615 // First check if the second quarter is the seed for a half-match.
4616 hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
4617
4618 // Check again based on the third quarter.
4619 hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
4620 if (!hm1 && !hm2) {
4621 return null;
4622 } else if (!hm2) {
4623 hm = hm1;
4624 } else if (!hm1) {
4625 hm = hm2;
4626 } else {
4627
4628 // Both matched. Select the longest.
4629 hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
4630 }
4631
4632 // A half-match was found, sort out the return data.
4633 if (text1.length > text2.length) {
4634 text1A = hm[0];
4635 text1B = hm[1];
4636 text2A = hm[2];
4637 text2B = hm[3];
4638 } else {
4639 text2A = hm[0];
4640 text2B = hm[1];
4641 text1A = hm[2];
4642 text1B = hm[3];
4643 }
4644 midCommon = hm[4];
4645 return [text1A, text1B, text2A, text2B, midCommon];
4646 };
4647
4648 /**
4649 * Do a quick line-level diff on both strings, then rediff the parts for
4650 * greater accuracy.
4651 * This speedup can produce non-minimal diffs.
4652 * @param {string} text1 Old string to be diffed.
4653 * @param {string} text2 New string to be diffed.
4654 * @param {number} deadline Time when the diff should be complete by.
4655 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4656 * @private
4657 */
4658 DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
4659 var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
4660
4661 // Scan the text on a line-by-line basis first.
4662 a = this.diffLinesToChars(text1, text2);
4663 text1 = a.chars1;
4664 text2 = a.chars2;
4665 linearray = a.lineArray;
4666
4667 diffs = this.DiffMain(text1, text2, false, deadline);
4668
4669 // Convert the diff back to original text.
4670 this.diffCharsToLines(diffs, linearray);
4671
4672 // Eliminate freak matches (e.g. blank lines)
4673 this.diffCleanupSemantic(diffs);
4674
4675 // Rediff any replacement blocks, this time character-by-character.
4676 // Add a dummy entry at the end.
4677 diffs.push([DIFF_EQUAL, ""]);
4678 pointer = 0;
4679 countDelete = 0;
4680 countInsert = 0;
4681 textDelete = "";
4682 textInsert = "";
4683 while (pointer < diffs.length) {
4684 switch (diffs[pointer][0]) {
4685 case DIFF_INSERT:
4686 countInsert++;
4687 textInsert += diffs[pointer][1];
4688 break;
4689 case DIFF_DELETE:
4690 countDelete++;
4691 textDelete += diffs[pointer][1];
4692 break;
4693 case DIFF_EQUAL:
4694
4695 // Upon reaching an equality, check for prior redundancies.
4696 if (countDelete >= 1 && countInsert >= 1) {
4697
4698 // Delete the offending records and add the merged ones.
4699 diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert);
4700 pointer = pointer - countDelete - countInsert;
4701 a = this.DiffMain(textDelete, textInsert, false, deadline);
4702 for (j = a.length - 1; j >= 0; j--) {
4703 diffs.splice(pointer, 0, a[j]);
4704 }
4705 pointer = pointer + a.length;
4706 }
4707 countInsert = 0;
4708 countDelete = 0;
4709 textDelete = "";
4710 textInsert = "";
4711 break;
4712 }
4713 pointer++;
4714 }
4715 diffs.pop(); // Remove the dummy entry at the end.
4716
4717 return diffs;
4718 };
4719
4720 /**
4721 * Find the 'middle snake' of a diff, split the problem in two
4722 * and return the recursively constructed diff.
4723 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
4724 * @param {string} text1 Old string to be diffed.
4725 * @param {string} text2 New string to be diffed.
4726 * @param {number} deadline Time at which to bail if not yet complete.
4727 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4728 * @private
4729 */
4730 DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) {
4731 var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
4732
4733 // Cache the text lengths to prevent multiple calls.
4734 text1Length = text1.length;
4735 text2Length = text2.length;
4736 maxD = Math.ceil((text1Length + text2Length) / 2);
4737 vOffset = maxD;
4738 vLength = 2 * maxD;
4739 v1 = new Array(vLength);
4740 v2 = new Array(vLength);
4741
4742 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
4743 // integers and undefined.
4744 for (x = 0; x < vLength; x++) {
4745 v1[x] = -1;
4746 v2[x] = -1;
4747 }
4748 v1[vOffset + 1] = 0;
4749 v2[vOffset + 1] = 0;
4750 delta = text1Length - text2Length;
4751
4752 // If the total number of characters is odd, then the front path will collide
4753 // with the reverse path.
4754 front = delta % 2 !== 0;
4755
4756 // Offsets for start and end of k loop.
4757 // Prevents mapping of space beyond the grid.
4758 k1start = 0;
4759 k1end = 0;
4760 k2start = 0;
4761 k2end = 0;
4762 for (d = 0; d < maxD; d++) {
4763
4764 // Bail out if deadline is reached.
4765 if (new Date().getTime() > deadline) {
4766 break;
4767 }
4768
4769 // Walk the front path one step.
4770 for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
4771 k1Offset = vOffset + k1;
4772 if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) {
4773 x1 = v1[k1Offset + 1];
4774 } else {
4775 x1 = v1[k1Offset - 1] + 1;
4776 }
4777 y1 = x1 - k1;
4778 while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
4779 x1++;
4780 y1++;
4781 }
4782 v1[k1Offset] = x1;
4783 if (x1 > text1Length) {
4784
4785 // Ran off the right of the graph.
4786 k1end += 2;
4787 } else if (y1 > text2Length) {
4788
4789 // Ran off the bottom of the graph.
4790 k1start += 2;
4791 } else if (front) {
4792 k2Offset = vOffset + delta - k1;
4793 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
4794
4795 // Mirror x2 onto top-left coordinate system.
4796 x2 = text1Length - v2[k2Offset];
4797 if (x1 >= x2) {
4798
4799 // Overlap detected.
4800 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4801 }
4802 }
4803 }
4804 }
4805
4806 // Walk the reverse path one step.
4807 for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
4808 k2Offset = vOffset + k2;
4809 if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) {
4810 x2 = v2[k2Offset + 1];
4811 } else {
4812 x2 = v2[k2Offset - 1] + 1;
4813 }
4814 y2 = x2 - k2;
4815 while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
4816 x2++;
4817 y2++;
4818 }
4819 v2[k2Offset] = x2;
4820 if (x2 > text1Length) {
4821
4822 // Ran off the left of the graph.
4823 k2end += 2;
4824 } else if (y2 > text2Length) {
4825
4826 // Ran off the top of the graph.
4827 k2start += 2;
4828 } else if (!front) {
4829 k1Offset = vOffset + delta - k2;
4830 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
4831 x1 = v1[k1Offset];
4832 y1 = vOffset + x1 - k1Offset;
4833
4834 // Mirror x2 onto top-left coordinate system.
4835 x2 = text1Length - x2;
4836 if (x1 >= x2) {
4837
4838 // Overlap detected.
4839 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4840 }
4841 }
4842 }
4843 }
4844 }
4845
4846 // Diff took too long and hit the deadline or
4847 // number of diffs equals number of characters, no commonality at all.
4848 return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4849 };
4850
4851 /**
4852 * Given the location of the 'middle snake', split the diff in two parts
4853 * and recurse.
4854 * @param {string} text1 Old string to be diffed.
4855 * @param {string} text2 New string to be diffed.
4856 * @param {number} x Index of split point in text1.
4857 * @param {number} y Index of split point in text2.
4858 * @param {number} deadline Time at which to bail if not yet complete.
4859 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4860 * @private
4861 */
4862 DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) {
4863 var text1a, text1b, text2a, text2b, diffs, diffsb;
4864 text1a = text1.substring(0, x);
4865 text2a = text2.substring(0, y);
4866 text1b = text1.substring(x);
4867 text2b = text2.substring(y);
4868
4869 // Compute both diffs serially.
4870 diffs = this.DiffMain(text1a, text2a, false, deadline);
4871 diffsb = this.DiffMain(text1b, text2b, false, deadline);
4872
4873 return diffs.concat(diffsb);
4874 };
4875
4876 /**
4877 * Reduce the number of edits by eliminating semantically trivial equalities.
4878 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4879 */
4880 DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
4881 var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
4882 changes = false;
4883 equalities = []; // Stack of indices where equalities are found.
4884 equalitiesLength = 0; // Keeping our own length var is faster in JS.
4885 /** @type {?string} */
4886 lastequality = null;
4887
4888 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4889 pointer = 0; // Index of current position.
4890
4891 // Number of characters that changed prior to the equality.
4892 lengthInsertions1 = 0;
4893 lengthDeletions1 = 0;
4894
4895 // Number of characters that changed after the equality.
4896 lengthInsertions2 = 0;
4897 lengthDeletions2 = 0;
4898 while (pointer < diffs.length) {
4899 if (diffs[pointer][0] === DIFF_EQUAL) {
4900 // Equality found.
4901 equalities[equalitiesLength++] = pointer;
4902 lengthInsertions1 = lengthInsertions2;
4903 lengthDeletions1 = lengthDeletions2;
4904 lengthInsertions2 = 0;
4905 lengthDeletions2 = 0;
4906 lastequality = diffs[pointer][1];
4907 } else {
4908 // An insertion or deletion.
4909 if (diffs[pointer][0] === DIFF_INSERT) {
4910 lengthInsertions2 += diffs[pointer][1].length;
4911 } else {
4912 lengthDeletions2 += diffs[pointer][1].length;
4913 }
4914
4915 // Eliminate an equality that is smaller or equal to the edits on both
4916 // sides of it.
4917 if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
4918
4919 // Duplicate record.
4920 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4921
4922 // Change second copy to insert.
4923 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4924
4925 // Throw away the equality we just deleted.
4926 equalitiesLength--;
4927
4928 // Throw away the previous equality (it needs to be reevaluated).
4929 equalitiesLength--;
4930 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4931
4932 // Reset the counters.
4933 lengthInsertions1 = 0;
4934 lengthDeletions1 = 0;
4935 lengthInsertions2 = 0;
4936 lengthDeletions2 = 0;
4937 lastequality = null;
4938 changes = true;
4939 }
4940 }
4941 pointer++;
4942 }
4943
4944 // Normalize the diff.
4945 if (changes) {
4946 this.diffCleanupMerge(diffs);
4947 }
4948
4949 // Find any overlaps between deletions and insertions.
4950 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
4951 // -> <del>abc</del>xxx<ins>def</ins>
4952 // e.g: <del>xxxabc</del><ins>defxxx</ins>
4953 // -> <ins>def</ins>xxx<del>abc</del>
4954 // Only extract an overlap if it is as big as the edit ahead or behind it.
4955 pointer = 1;
4956 while (pointer < diffs.length) {
4957 if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
4958 deletion = diffs[pointer - 1][1];
4959 insertion = diffs[pointer][1];
4960 overlapLength1 = this.diffCommonOverlap(deletion, insertion);
4961 overlapLength2 = this.diffCommonOverlap(insertion, deletion);
4962 if (overlapLength1 >= overlapLength2) {
4963 if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) {
4964
4965 // Overlap found. Insert an equality and trim the surrounding edits.
4966 diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]);
4967 diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1);
4968 diffs[pointer + 1][1] = insertion.substring(overlapLength1);
4969 pointer++;
4970 }
4971 } else {
4972 if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
4973
4974 // Reverse overlap found.
4975 // Insert an equality and swap and trim the surrounding edits.
4976 diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]);
4977
4978 diffs[pointer - 1][0] = DIFF_INSERT;
4979 diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2);
4980 diffs[pointer + 1][0] = DIFF_DELETE;
4981 diffs[pointer + 1][1] = deletion.substring(overlapLength2);
4982 pointer++;
4983 }
4984 }
4985 pointer++;
4986 }
4987 pointer++;
4988 }
4989 };
4990
4991 /**
4992 * Determine if the suffix of one string is the prefix of another.
4993 * @param {string} text1 First string.
4994 * @param {string} text2 Second string.
4995 * @return {number} The number of characters common to the end of the first
4996 * string and the start of the second string.
4997 * @private
4998 */
4999 DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
5000 var text1Length, text2Length, textLength, best, length, pattern, found;
5001
5002 // Cache the text lengths to prevent multiple calls.
5003 text1Length = text1.length;
5004 text2Length = text2.length;
5005
5006 // Eliminate the null case.
5007 if (text1Length === 0 || text2Length === 0) {
5008 return 0;
5009 }
5010
5011 // Truncate the longer string.
5012 if (text1Length > text2Length) {
5013 text1 = text1.substring(text1Length - text2Length);
5014 } else if (text1Length < text2Length) {
5015 text2 = text2.substring(0, text1Length);
5016 }
5017 textLength = Math.min(text1Length, text2Length);
5018
5019 // Quick check for the worst case.
5020 if (text1 === text2) {
5021 return textLength;
5022 }
5023
5024 // Start by looking for a single character match
5025 // and increase length until no match is found.
5026 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
5027 best = 0;
5028 length = 1;
5029 while (true) {
5030 pattern = text1.substring(textLength - length);
5031 found = text2.indexOf(pattern);
5032 if (found === -1) {
5033 return best;
5034 }
5035 length += found;
5036 if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
5037 best = length;
5038 length++;
5039 }
5040 }
5041 };
5042
5043 /**
5044 * Split two texts into an array of strings. Reduce the texts to a string of
5045 * hashes where each Unicode character represents one line.
5046 * @param {string} text1 First string.
5047 * @param {string} text2 Second string.
5048 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
5049 * An object containing the encoded text1, the encoded text2 and
5050 * the array of unique strings.
5051 * The zeroth element of the array of unique strings is intentionally blank.
5052 * @private
5053 */
5054 DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) {
5055 var lineArray, lineHash, chars1, chars2;
5056 lineArray = []; // E.g. lineArray[4] === 'Hello\n'
5057 lineHash = {}; // E.g. lineHash['Hello\n'] === 4
5058
5059 // '\x00' is a valid character, but various debuggers don't like it.
5060 // So we'll insert a junk entry to avoid generating a null character.
5061 lineArray[0] = "";
5062
5063 /**
5064 * Split a text into an array of strings. Reduce the texts to a string of
5065 * hashes where each Unicode character represents one line.
5066 * Modifies linearray and linehash through being a closure.
5067 * @param {string} text String to encode.
5068 * @return {string} Encoded string.
5069 * @private
5070 */
5071 function diffLinesToCharsMunge(text) {
5072 var chars, lineStart, lineEnd, lineArrayLength, line;
5073 chars = "";
5074
5075 // Walk the text, pulling out a substring for each line.
5076 // text.split('\n') would would temporarily double our memory footprint.
5077 // Modifying text would create many large strings to garbage collect.
5078 lineStart = 0;
5079 lineEnd = -1;
5080
5081 // Keeping our own length variable is faster than looking it up.
5082 lineArrayLength = lineArray.length;
5083 while (lineEnd < text.length - 1) {
5084 lineEnd = text.indexOf("\n", lineStart);
5085 if (lineEnd === -1) {
5086 lineEnd = text.length - 1;
5087 }
5088 line = text.substring(lineStart, lineEnd + 1);
5089 lineStart = lineEnd + 1;
5090
5091 var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined;
5092
5093 if (lineHashExists) {
5094 chars += String.fromCharCode(lineHash[line]);
5095 } else {
5096 chars += String.fromCharCode(lineArrayLength);
5097 lineHash[line] = lineArrayLength;
5098 lineArray[lineArrayLength++] = line;
5099 }
5100 }
5101 return chars;
5102 }
5103
5104 chars1 = diffLinesToCharsMunge(text1);
5105 chars2 = diffLinesToCharsMunge(text2);
5106 return {
5107 chars1: chars1,
5108 chars2: chars2,
5109 lineArray: lineArray
5110 };
5111 };
5112
5113 /**
5114 * Rehydrate the text in a diff from a string of line hashes to real lines of
5115 * text.
5116 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5117 * @param {!Array.<string>} lineArray Array of unique strings.
5118 * @private
5119 */
5120 DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) {
5121 var x, chars, text, y;
5122 for (x = 0; x < diffs.length; x++) {
5123 chars = diffs[x][1];
5124 text = [];
5125 for (y = 0; y < chars.length; y++) {
5126 text[y] = lineArray[chars.charCodeAt(y)];
5127 }
5128 diffs[x][1] = text.join("");
5129 }
5130 };
5131
5132 /**
5133 * Reorder and merge like edit sections. Merge equalities.
5134 * Any edit section can move as long as it doesn't cross an equality.
5135 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5136 */
5137 DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) {
5138 var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position;
5139 diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end.
5140 pointer = 0;
5141 countDelete = 0;
5142 countInsert = 0;
5143 textDelete = "";
5144 textInsert = "";
5145
5146 while (pointer < diffs.length) {
5147 switch (diffs[pointer][0]) {
5148 case DIFF_INSERT:
5149 countInsert++;
5150 textInsert += diffs[pointer][1];
5151 pointer++;
5152 break;
5153 case DIFF_DELETE:
5154 countDelete++;
5155 textDelete += diffs[pointer][1];
5156 pointer++;
5157 break;
5158 case DIFF_EQUAL:
5159
5160 // Upon reaching an equality, check for prior redundancies.
5161 if (countDelete + countInsert > 1) {
5162 if (countDelete !== 0 && countInsert !== 0) {
5163
5164 // Factor out any common prefixes.
5165 commonlength = this.diffCommonPrefix(textInsert, textDelete);
5166 if (commonlength !== 0) {
5167 if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) {
5168 diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength);
5169 } else {
5170 diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
5171 pointer++;
5172 }
5173 textInsert = textInsert.substring(commonlength);
5174 textDelete = textDelete.substring(commonlength);
5175 }
5176
5177 // Factor out any common suffixies.
5178 commonlength = this.diffCommonSuffix(textInsert, textDelete);
5179 if (commonlength !== 0) {
5180 diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1];
5181 textInsert = textInsert.substring(0, textInsert.length - commonlength);
5182 textDelete = textDelete.substring(0, textDelete.length - commonlength);
5183 }
5184 }
5185
5186 // Delete the offending records and add the merged ones.
5187 if (countDelete === 0) {
5188 diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]);
5189 } else if (countInsert === 0) {
5190 diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]);
5191 } else {
5192 diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
5193 }
5194 pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
5195 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
5196
5197 // Merge this equality with the previous one.
5198 diffs[pointer - 1][1] += diffs[pointer][1];
5199 diffs.splice(pointer, 1);
5200 } else {
5201 pointer++;
5202 }
5203 countInsert = 0;
5204 countDelete = 0;
5205 textDelete = "";
5206 textInsert = "";
5207 break;
5208 }
5209 }
5210 if (diffs[diffs.length - 1][1] === "") {
5211 diffs.pop(); // Remove the dummy entry at the end.
5212 }
5213
5214 // Second pass: look for single edits surrounded on both sides by equalities
5215 // which can be shifted sideways to eliminate an equality.
5216 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
5217 changes = false;
5218 pointer = 1;
5219
5220 // Intentionally ignore the first and last element (don't need checking).
5221 while (pointer < diffs.length - 1) {
5222 if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
5223
5224 diffPointer = diffs[pointer][1];
5225 position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
5226
5227 // This is a single edit surrounded by equalities.
5228 if (position === diffs[pointer - 1][1]) {
5229
5230 // Shift the edit over the previous equality.
5231 diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
5232 diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
5233 diffs.splice(pointer - 1, 1);
5234 changes = true;
5235 } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
5236
5237 // Shift the edit over the next equality.
5238 diffs[pointer - 1][1] += diffs[pointer + 1][1];
5239 diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
5240 diffs.splice(pointer + 1, 1);
5241 changes = true;
5242 }
5243 }
5244 pointer++;
5245 }
5246
5247 // If shifts were made, the diff needs reordering and another shift sweep.
5248 if (changes) {
5249 this.diffCleanupMerge(diffs);
5250 }
5251 };
5252
5253 return function (o, n) {
5254 var diff, output, text;
5255 diff = new DiffMatchPatch();
5256 output = diff.DiffMain(o, n);
5257 diff.diffCleanupEfficiency(output);
5258 text = diff.diffPrettyHtml(output);
5259
5260 return text;
5261 };
5262 }();
5263
5264 }((function() { return this; }())));