PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / assets / js / libraries / qunit-2.3.3.js

qunit-2.3.3.js in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at assets/js/libraries/qunit-2.3.3.js

4,927 lines 138.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*!
2 * QUnit 2.3.3
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: 2017-06-02T14:07Z
10 */
11 (function (global$1) {
12 'use strict';
13
14 global$1 = 'default' in global$1 ? 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 }
231
232 if ((typeof obj === "undefined" ? "undefined" : _typeof(obj)) === "object") {
233 return "object";
234 }
235 }
236
237 // Safe object type checking
238 function is(type, obj) {
239 return objectType(obj) === type;
240 }
241
242 // Based on Java's String.hashCode, a simple but not
243 // rigorously collision resistant hashing function
244 function generateHash(module, testName) {
245 var str = module + "\x1C" + testName;
246 var hash = 0;
247
248 for (var i = 0; i < str.length; i++) {
249 hash = (hash << 5) - hash + str.charCodeAt(i);
250 hash |= 0;
251 }
252
253 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
254 // strictly necessary but increases user understanding that the id is a SHA-like hash
255 var hex = (0x100000000 + hash).toString(16);
256 if (hex.length < 8) {
257 hex = "0000000" + hex;
258 }
259
260 return hex.slice(-8);
261 }
262
263 // Test for equality any JavaScript type.
264 // Authors: Philippe Rathé <[email protected]>, David Chan <[email protected]>
265 var equiv = (function () {
266
267 // Value pairs queued for comparison. Used for breadth-first processing order, recursion
268 // detection and avoiding repeated comparison (see below for details).
269 // Elements are { a: val, b: val }.
270 var pairs = [];
271
272 var getProto = Object.getPrototypeOf || function (obj) {
273 return obj.__proto__;
274 };
275
276 function useStrictEquality(a, b) {
277
278 // This only gets called if a and b are not strict equal, and is used to compare on
279 // the primitive values inside object wrappers. For example:
280 // `var i = 1;`
281 // `var j = new Number(1);`
282 // Neither a nor b can be null, as a !== b and they have the same type.
283 if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
284 a = a.valueOf();
285 }
286 if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
287 b = b.valueOf();
288 }
289
290 return a === b;
291 }
292
293 function compareConstructors(a, b) {
294 var protoA = getProto(a);
295 var protoB = getProto(b);
296
297 // Comparing constructors is more strict than using `instanceof`
298 if (a.constructor === b.constructor) {
299 return true;
300 }
301
302 // Ref #851
303 // If the obj prototype descends from a null constructor, treat it
304 // as a null prototype.
305 if (protoA && protoA.constructor === null) {
306 protoA = null;
307 }
308 if (protoB && protoB.constructor === null) {
309 protoB = null;
310 }
311
312 // Allow objects with no prototype to be equivalent to
313 // objects with Object as their constructor.
314 if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) {
315 return true;
316 }
317
318 return false;
319 }
320
321 function getRegExpFlags(regexp) {
322 return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
323 }
324
325 function isContainer(val) {
326 return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
327 }
328
329 function breadthFirstCompareChild(a, b) {
330
331 // If a is a container not reference-equal to b, postpone the comparison to the
332 // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
333 // over the pair.
334 if (a === b) {
335 return true;
336 }
337 if (!isContainer(a)) {
338 return typeEquiv(a, b);
339 }
340 if (pairs.every(function (pair) {
341 return pair.a !== a || pair.b !== b;
342 })) {
343
344 // Not yet started comparing this pair
345 pairs.push({ a: a, b: b });
346 }
347 return true;
348 }
349
350 var callbacks = {
351 "string": useStrictEquality,
352 "boolean": useStrictEquality,
353 "number": useStrictEquality,
354 "null": useStrictEquality,
355 "undefined": useStrictEquality,
356 "symbol": useStrictEquality,
357 "date": useStrictEquality,
358
359 "nan": function nan() {
360 return true;
361 },
362
363 "regexp": function regexp(a, b) {
364 return a.source === b.source &&
365
366 // Include flags in the comparison
367 getRegExpFlags(a) === getRegExpFlags(b);
368 },
369
370 // abort (identical references / instance methods were skipped earlier)
371 "function": function _function() {
372 return false;
373 },
374
375 "array": function array(a, b) {
376 var i, len;
377
378 len = a.length;
379 if (len !== b.length) {
380
381 // Safe and faster
382 return false;
383 }
384
385 for (i = 0; i < len; i++) {
386
387 // Compare non-containers; queue non-reference-equal containers
388 if (!breadthFirstCompareChild(a[i], b[i])) {
389 return false;
390 }
391 }
392 return true;
393 },
394
395 // Define sets a and b to be equivalent if for each element aVal in a, there
396 // is some element bVal in b such that aVal and bVal are equivalent. Element
397 // repetitions are not counted, so these are equivalent:
398 // a = new Set( [ {}, [], [] ] );
399 // b = new Set( [ {}, {}, [] ] );
400 "set": function set$$1(a, b) {
401 var innerEq,
402 outerEq = true;
403
404 if (a.size !== b.size) {
405
406 // This optimization has certain quirks because of the lack of
407 // repetition counting. For instance, adding the same
408 // (reference-identical) element to two equivalent sets can
409 // make them non-equivalent.
410 return false;
411 }
412
413 a.forEach(function (aVal) {
414
415 // Short-circuit if the result is already known. (Using for...of
416 // with a break clause would be cleaner here, but it would cause
417 // a syntax error on older Javascript implementations even if
418 // Set is unused)
419 if (!outerEq) {
420 return;
421 }
422
423 innerEq = false;
424
425 b.forEach(function (bVal) {
426 var parentPairs;
427
428 // Likewise, short-circuit if the result is already known
429 if (innerEq) {
430 return;
431 }
432
433 // Swap out the global pairs list, as the nested call to
434 // innerEquiv will clobber its contents
435 parentPairs = pairs;
436 if (innerEquiv(bVal, aVal)) {
437 innerEq = true;
438 }
439
440 // Replace the global pairs list
441 pairs = parentPairs;
442 });
443
444 if (!innerEq) {
445 outerEq = false;
446 }
447 });
448
449 return outerEq;
450 },
451
452 // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
453 // in a, there is some key-value pair (bKey, bVal) in b such that
454 // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
455 // counted, so these are equivalent:
456 // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
457 // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
458 "map": function map(a, b) {
459 var innerEq,
460 outerEq = true;
461
462 if (a.size !== b.size) {
463
464 // This optimization has certain quirks because of the lack of
465 // repetition counting. For instance, adding the same
466 // (reference-identical) key-value pair to two equivalent maps
467 // can make them non-equivalent.
468 return false;
469 }
470
471 a.forEach(function (aVal, aKey) {
472
473 // Short-circuit if the result is already known. (Using for...of
474 // with a break clause would be cleaner here, but it would cause
475 // a syntax error on older Javascript implementations even if
476 // Map is unused)
477 if (!outerEq) {
478 return;
479 }
480
481 innerEq = false;
482
483 b.forEach(function (bVal, bKey) {
484 var parentPairs;
485
486 // Likewise, short-circuit if the result is already known
487 if (innerEq) {
488 return;
489 }
490
491 // Swap out the global pairs list, as the nested call to
492 // innerEquiv will clobber its contents
493 parentPairs = pairs;
494 if (innerEquiv([bVal, bKey], [aVal, aKey])) {
495 innerEq = true;
496 }
497
498 // Replace the global pairs list
499 pairs = parentPairs;
500 });
501
502 if (!innerEq) {
503 outerEq = false;
504 }
505 });
506
507 return outerEq;
508 },
509
510 "object": function object(a, b) {
511 var i,
512 aProperties = [],
513 bProperties = [];
514
515 if (compareConstructors(a, b) === false) {
516 return false;
517 }
518
519 // Be strict: don't ensure hasOwnProperty and go deep
520 for (i in a) {
521
522 // Collect a's properties
523 aProperties.push(i);
524
525 // Skip OOP methods that look the same
526 if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
527 continue;
528 }
529
530 // Compare non-containers; queue non-reference-equal containers
531 if (!breadthFirstCompareChild(a[i], b[i])) {
532 return false;
533 }
534 }
535
536 for (i in b) {
537
538 // Collect b's properties
539 bProperties.push(i);
540 }
541
542 // Ensures identical properties name
543 return typeEquiv(aProperties.sort(), bProperties.sort());
544 }
545 };
546
547 function typeEquiv(a, b) {
548 var type = objectType(a);
549
550 // Callbacks for containers will append to the pairs queue to achieve breadth-first
551 // search order. The pairs queue is also used to avoid reprocessing any pair of
552 // containers that are reference-equal to a previously visited pair (a special case
553 // this being recursion detection).
554 //
555 // Because of this approach, once typeEquiv returns a false value, it should not be
556 // called again without clearing the pair queue else it may wrongly report a visited
557 // pair as being equivalent.
558 return objectType(b) === type && callbacks[type](a, b);
559 }
560
561 function innerEquiv(a, b) {
562 var i, pair;
563
564 // We're done when there's nothing more to compare
565 if (arguments.length < 2) {
566 return true;
567 }
568
569 // Clear the global pair queue and add the top-level values being compared
570 pairs = [{ a: a, b: b }];
571
572 for (i = 0; i < pairs.length; i++) {
573 pair = pairs[i];
574
575 // Perform type-specific comparison on any pairs that are not strictly
576 // equal. For container types, that comparison will postpone comparison
577 // of any sub-container pair to the end of the pair queue. This gives
578 // breadth-first search order. It also avoids the reprocessing of
579 // reference-equal siblings, cousins etc, which can have a significant speed
580 // impact when comparing a container of small objects each of which has a
581 // reference to the same (singleton) large object.
582 if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
583 return false;
584 }
585 }
586
587 // ...across all consecutive argument pairs
588 return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
589 }
590
591 return innerEquiv;
592 })();
593
594 /**
595 * Config object: Maintain internal state
596 * Later exposed as QUnit.config
597 * `config` initialized at top of scope
598 */
599 var config = {
600
601 // The queue of tests to run
602 queue: [],
603
604 // Block until document ready
605 blocking: true,
606
607 // By default, run previously failed tests first
608 // very useful in combination with "Hide passed tests" checked
609 reorder: true,
610
611 // By default, modify document.title when suite is done
612 altertitle: true,
613
614 // HTML Reporter: collapse every test except the first failing test
615 // If false, all failing tests will be expanded
616 collapse: true,
617
618 // By default, scroll to top of the page when suite is done
619 scrolltop: true,
620
621 // Depth up-to which object will be dumped
622 maxDepth: 5,
623
624 // When enabled, all tests must call expect()
625 requireExpects: false,
626
627 // Placeholder for user-configurable form-exposed URL parameters
628 urlConfig: [],
629
630 // Set of all modules.
631 modules: [],
632
633 // The first unnamed module
634 currentModule: {
635 name: "",
636 tests: [],
637 childModules: [],
638 testsRun: 0,
639 unskippedTestsRun: 0
640 },
641
642 callbacks: {},
643
644 // The storage module to use for reordering tests
645 storage: localSessionStorage
646 };
647
648 // take a predefined QUnit.config and extend the defaults
649 var globalConfig = window && window.QUnit && window.QUnit.config;
650
651 // only extend the global config if there is no QUnit overload
652 if (window && window.QUnit && !window.QUnit.version) {
653 extend(config, globalConfig);
654 }
655
656 // Push a loose unnamed module to the modules collection
657 config.modules.push(config.currentModule);
658
659 // Based on jsDump by Ariel Flesler
660 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
661 var dump = (function () {
662 function quote(str) {
663 return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
664 }
665 function literal(o) {
666 return o + "";
667 }
668 function join(pre, arr, post) {
669 var s = dump.separator(),
670 base = dump.indent(),
671 inner = dump.indent(1);
672 if (arr.join) {
673 arr = arr.join("," + s + inner);
674 }
675 if (!arr) {
676 return pre + post;
677 }
678 return [pre, inner + arr, base + post].join(s);
679 }
680 function array(arr, stack) {
681 var i = arr.length,
682 ret = new Array(i);
683
684 if (dump.maxDepth && dump.depth > dump.maxDepth) {
685 return "[object Array]";
686 }
687
688 this.up();
689 while (i--) {
690 ret[i] = this.parse(arr[i], undefined, stack);
691 }
692 this.down();
693 return join("[", ret, "]");
694 }
695
696 function isArray(obj) {
697 return (
698
699 //Native Arrays
700 toString.call(obj) === "[object Array]" ||
701
702 // NodeList objects
703 typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
704 );
705 }
706
707 var reName = /^function (\w+)/,
708 dump = {
709
710 // The objType is used mostly internally, you can fix a (custom) type in advance
711 parse: function parse(obj, objType, stack) {
712 stack = stack || [];
713 var res,
714 parser,
715 parserType,
716 objIndex = stack.indexOf(obj);
717
718 if (objIndex !== -1) {
719 return "recursion(" + (objIndex - stack.length) + ")";
720 }
721
722 objType = objType || this.typeOf(obj);
723 parser = this.parsers[objType];
724 parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
725
726 if (parserType === "function") {
727 stack.push(obj);
728 res = parser.call(this, obj, stack);
729 stack.pop();
730 return res;
731 }
732 return parserType === "string" ? parser : this.parsers.error;
733 },
734 typeOf: function typeOf(obj) {
735 var type;
736
737 if (obj === null) {
738 type = "null";
739 } else if (typeof obj === "undefined") {
740 type = "undefined";
741 } else if (is("regexp", obj)) {
742 type = "regexp";
743 } else if (is("date", obj)) {
744 type = "date";
745 } else if (is("function", obj)) {
746 type = "function";
747 } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
748 type = "window";
749 } else if (obj.nodeType === 9) {
750 type = "document";
751 } else if (obj.nodeType) {
752 type = "node";
753 } else if (isArray(obj)) {
754 type = "array";
755 } else if (obj.constructor === Error.prototype.constructor) {
756 type = "error";
757 } else {
758 type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
759 }
760 return type;
761 },
762
763 separator: function separator() {
764 if (this.multiline) {
765 return this.HTML ? "<br />" : "\n";
766 } else {
767 return this.HTML ? "&#160;" : " ";
768 }
769 },
770
771 // Extra can be a number, shortcut for increasing-calling-decreasing
772 indent: function indent(extra) {
773 if (!this.multiline) {
774 return "";
775 }
776 var chr = this.indentChar;
777 if (this.HTML) {
778 chr = chr.replace(/\t/g, " ").replace(/ /g, "&#160;");
779 }
780 return new Array(this.depth + (extra || 0)).join(chr);
781 },
782 up: function up(a) {
783 this.depth += a || 1;
784 },
785 down: function down(a) {
786 this.depth -= a || 1;
787 },
788 setParser: function setParser(name, parser) {
789 this.parsers[name] = parser;
790 },
791
792 // The next 3 are exposed so you can use them
793 quote: quote,
794 literal: literal,
795 join: join,
796 depth: 1,
797 maxDepth: config.maxDepth,
798
799 // This is the list of parsers, to modify them, use dump.setParser
800 parsers: {
801 window: "[Window]",
802 document: "[Document]",
803 error: function error(_error) {
804 return "Error(\"" + _error.message + "\")";
805 },
806 unknown: "[Unknown]",
807 "null": "null",
808 "undefined": "undefined",
809 "function": function _function(fn) {
810 var ret = "function",
811
812
813 // Functions never have name in IE
814 name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
815
816 if (name) {
817 ret += " " + name;
818 }
819 ret += "(";
820
821 ret = [ret, dump.parse(fn, "functionArgs"), "){"].join("");
822 return join(ret, dump.parse(fn, "functionCode"), "}");
823 },
824 array: array,
825 nodelist: array,
826 "arguments": array,
827 object: function object(map, stack) {
828 var keys,
829 key,
830 val,
831 i,
832 nonEnumerableProperties,
833 ret = [];
834
835 if (dump.maxDepth && dump.depth > dump.maxDepth) {
836 return "[object Object]";
837 }
838
839 dump.up();
840 keys = [];
841 for (key in map) {
842 keys.push(key);
843 }
844
845 // Some properties are not always enumerable on Error objects.
846 nonEnumerableProperties = ["message", "name"];
847 for (i in nonEnumerableProperties) {
848 key = nonEnumerableProperties[i];
849 if (key in map && !inArray(key, keys)) {
850 keys.push(key);
851 }
852 }
853 keys.sort();
854 for (i = 0; i < keys.length; i++) {
855 key = keys[i];
856 val = map[key];
857 ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack));
858 }
859 dump.down();
860 return join("{", ret, "}");
861 },
862 node: function node(_node) {
863 var len,
864 i,
865 val,
866 open = dump.HTML ? "&lt;" : "<",
867 close = dump.HTML ? "&gt;" : ">",
868 tag = _node.nodeName.toLowerCase(),
869 ret = open + tag,
870 attrs = _node.attributes;
871
872 if (attrs) {
873 for (i = 0, len = attrs.length; i < len; i++) {
874 val = attrs[i].nodeValue;
875
876 // IE6 includes all attributes in .attributes, even ones not explicitly
877 // set. Those have values like undefined, null, 0, false, "" or
878 // "inherit".
879 if (val && val !== "inherit") {
880 ret += " " + attrs[i].nodeName + "=" + dump.parse(val, "attribute");
881 }
882 }
883 }
884 ret += close;
885
886 // Show content of TextNode or CDATASection
887 if (_node.nodeType === 3 || _node.nodeType === 4) {
888 ret += _node.nodeValue;
889 }
890
891 return ret + open + "/" + tag + close;
892 },
893
894 // Function calls it internally, it's the arguments part of the function
895 functionArgs: function functionArgs(fn) {
896 var args,
897 l = fn.length;
898
899 if (!l) {
900 return "";
901 }
902
903 args = new Array(l);
904 while (l--) {
905
906 // 97 is 'a'
907 args[l] = String.fromCharCode(97 + l);
908 }
909 return " " + args.join(", ") + " ";
910 },
911
912 // Object calls it internally, the key part of an item in a map
913 key: quote,
914
915 // Function calls it internally, it's the content of the function
916 functionCode: "[code]",
917
918 // Node calls it internally, it's a html attribute value
919 attribute: quote,
920 string: quote,
921 date: quote,
922 regexp: literal,
923 number: literal,
924 "boolean": literal,
925 symbol: function symbol(sym) {
926 return sym.toString();
927 }
928 },
929
930 // If true, entities are escaped ( <, >, \t, space and \n )
931 HTML: false,
932
933 // Indentation unit
934 indentChar: " ",
935
936 // If true, items in a collection, are separated by a \n, else just a space.
937 multiline: true
938 };
939
940 return dump;
941 })();
942
943 var LISTENERS = Object.create(null);
944 var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
945
946 /**
947 * Emits an event with the specified data to all currently registered listeners.
948 * Callbacks will fire in the order in which they are registered (FIFO). This
949 * function is not exposed publicly; it is used by QUnit internals to emit
950 * logging events.
951 *
952 * @private
953 * @method emit
954 * @param {String} eventName
955 * @param {Object} data
956 * @return {Void}
957 */
958 function emit(eventName, data) {
959 if (objectType(eventName) !== "string") {
960 throw new TypeError("eventName must be a string when emitting an event");
961 }
962
963 // Clone the callbacks in case one of them registers a new callback
964 var originalCallbacks = LISTENERS[eventName];
965 var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
966
967 for (var i = 0; i < callbacks.length; i++) {
968 callbacks[i](data);
969 }
970 }
971
972 /**
973 * Registers a callback as a listener to the specified event.
974 *
975 * @public
976 * @method on
977 * @param {String} eventName
978 * @param {Function} callback
979 * @return {Void}
980 */
981 function on(eventName, callback) {
982 if (objectType(eventName) !== "string") {
983 throw new TypeError("eventName must be a string when registering a listener");
984 } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
985 var events = SUPPORTED_EVENTS.join(", ");
986 throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
987 } else if (objectType(callback) !== "function") {
988 throw new TypeError("callback must be a function when registering a listener");
989 }
990
991 if (!LISTENERS[eventName]) {
992 LISTENERS[eventName] = [];
993 }
994
995 // Don't register the same callback more than once
996 if (!inArray(callback, LISTENERS[eventName])) {
997 LISTENERS[eventName].push(callback);
998 }
999 }
1000
1001 // Register logging callbacks
1002 function registerLoggingCallbacks(obj) {
1003 var i,
1004 l,
1005 key,
1006 callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
1007
1008 function registerLoggingCallback(key) {
1009 var loggingCallback = function loggingCallback(callback) {
1010 if (objectType(callback) !== "function") {
1011 throw new Error("QUnit logging methods require a callback function as their first parameters.");
1012 }
1013
1014 config.callbacks[key].push(callback);
1015 };
1016
1017 return loggingCallback;
1018 }
1019
1020 for (i = 0, l = callbackNames.length; i < l; i++) {
1021 key = callbackNames[i];
1022
1023 // Initialize key collection of logging callback
1024 if (objectType(config.callbacks[key]) === "undefined") {
1025 config.callbacks[key] = [];
1026 }
1027
1028 obj[key] = registerLoggingCallback(key);
1029 }
1030 }
1031
1032 function runLoggingCallbacks(key, args) {
1033 var i, l, callbacks;
1034
1035 callbacks = config.callbacks[key];
1036 for (i = 0, l = callbacks.length; i < l; i++) {
1037 callbacks[i](args);
1038 }
1039 }
1040
1041 // Doesn't support IE9, it will return undefined on these browsers
1042 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1043 var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
1044
1045 function extractStacktrace(e, offset) {
1046 offset = offset === undefined ? 4 : offset;
1047
1048 var stack, include, i;
1049
1050 if (e && e.stack) {
1051 stack = e.stack.split("\n");
1052 if (/^error$/i.test(stack[0])) {
1053 stack.shift();
1054 }
1055 if (fileName) {
1056 include = [];
1057 for (i = offset; i < stack.length; i++) {
1058 if (stack[i].indexOf(fileName) !== -1) {
1059 break;
1060 }
1061 include.push(stack[i]);
1062 }
1063 if (include.length) {
1064 return include.join("\n");
1065 }
1066 }
1067 return stack[offset];
1068 }
1069 }
1070
1071 function sourceFromStacktrace(offset) {
1072 var error = new Error();
1073
1074 // Support: Safari <=7 only, IE <=10 - 11 only
1075 // Not all browsers generate the `stack` property for `new Error()`, see also #636
1076 if (!error.stack) {
1077 try {
1078 throw error;
1079 } catch (err) {
1080 error = err;
1081 }
1082 }
1083
1084 return extractStacktrace(error, offset);
1085 }
1086
1087 var priorityCount = 0;
1088 var unitSampler = void 0;
1089
1090 /**
1091 * Advances the ProcessingQueue to the next item if it is ready.
1092 * @param {Boolean} last
1093 */
1094 function advance() {
1095 var start = now();
1096 config.depth = (config.depth || 0) + 1;
1097
1098 while (config.queue.length && !config.blocking) {
1099 var elapsedTime = now() - start;
1100
1101 if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
1102 if (priorityCount > 0) {
1103 priorityCount--;
1104 }
1105
1106 config.queue.shift()();
1107 } else {
1108 setTimeout(advance, 13);
1109 break;
1110 }
1111 }
1112
1113 config.depth--;
1114
1115 if (!config.blocking && !config.queue.length && config.depth === 0) {
1116 done();
1117 }
1118 }
1119
1120 function addToQueueImmediate(callback) {
1121 if (objectType(callback) === "array") {
1122 while (callback.length) {
1123 addToQueueImmediate(callback.pop());
1124 }
1125
1126 return;
1127 }
1128
1129 config.queue.unshift(callback);
1130 priorityCount++;
1131 }
1132
1133 /**
1134 * Adds a function to the ProcessingQueue for execution.
1135 * @param {Function|Array} callback
1136 * @param {Boolean} priority
1137 * @param {String} seed
1138 */
1139 function addToQueue(callback, prioritize, seed) {
1140 if (prioritize) {
1141 config.queue.splice(priorityCount++, 0, callback);
1142 } else if (seed) {
1143 if (!unitSampler) {
1144 unitSampler = unitSamplerGenerator(seed);
1145 }
1146
1147 // Insert into a random position after all prioritized items
1148 var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
1149 config.queue.splice(priorityCount + index, 0, callback);
1150 } else {
1151 config.queue.push(callback);
1152 }
1153 }
1154
1155 /**
1156 * Creates a seeded "sample" generator which is used for randomizing tests.
1157 */
1158 function unitSamplerGenerator(seed) {
1159
1160 // 32-bit xorshift, requires only a nonzero seed
1161 // http://excamera.com/sphinx/article-xorshift.html
1162 var sample = parseInt(generateHash(seed), 16) || -1;
1163 return function () {
1164 sample ^= sample << 13;
1165 sample ^= sample >>> 17;
1166 sample ^= sample << 5;
1167
1168 // ECMAScript has no unsigned number type
1169 if (sample < 0) {
1170 sample += 0x100000000;
1171 }
1172
1173 return sample / 0x100000000;
1174 };
1175 }
1176
1177 /**
1178 * This function is called when the ProcessingQueue is done processing all
1179 * items. It handles emitting the final run events.
1180 */
1181 function done() {
1182 var storage = config.storage;
1183
1184 ProcessingQueue.finished = true;
1185
1186 var runtime = now() - config.started;
1187 var passed = config.stats.all - config.stats.bad;
1188
1189 emit("runEnd", globalSuite.end(true));
1190 runLoggingCallbacks("done", {
1191 passed: passed,
1192 failed: config.stats.bad,
1193 total: config.stats.all,
1194 runtime: runtime
1195 });
1196
1197 // Clear own storage items if all tests passed
1198 if (storage && config.stats.bad === 0) {
1199 for (var i = storage.length - 1; i >= 0; i--) {
1200 var key = storage.key(i);
1201
1202 if (key.indexOf("qunit-test-") === 0) {
1203 storage.removeItem(key);
1204 }
1205 }
1206 }
1207 }
1208
1209 var ProcessingQueue = {
1210 finished: false,
1211 add: addToQueue,
1212 addImmediate: addToQueueImmediate,
1213 advance: advance
1214 };
1215
1216 var TestReport = function () {
1217 function TestReport(name, suite, options) {
1218 classCallCheck(this, TestReport);
1219
1220 this.name = name;
1221 this.suiteName = suite.name;
1222 this.fullName = suite.fullName.concat(name);
1223 this.runtime = 0;
1224 this.assertions = [];
1225
1226 this.skipped = !!options.skip;
1227 this.todo = !!options.todo;
1228
1229 this.valid = options.valid;
1230
1231 this._startTime = 0;
1232 this._endTime = 0;
1233
1234 suite.pushTest(this);
1235 }
1236
1237 createClass(TestReport, [{
1238 key: "start",
1239 value: function start(recordTime) {
1240 if (recordTime) {
1241 this._startTime = Date.now();
1242 }
1243
1244 return {
1245 name: this.name,
1246 suiteName: this.suiteName,
1247 fullName: this.fullName.slice()
1248 };
1249 }
1250 }, {
1251 key: "end",
1252 value: function end(recordTime) {
1253 if (recordTime) {
1254 this._endTime = Date.now();
1255 }
1256
1257 return extend(this.start(), {
1258 runtime: this.getRuntime(),
1259 status: this.getStatus(),
1260 errors: this.getFailedAssertions(),
1261 assertions: this.getAssertions()
1262 });
1263 }
1264 }, {
1265 key: "pushAssertion",
1266 value: function pushAssertion(assertion) {
1267 this.assertions.push(assertion);
1268 }
1269 }, {
1270 key: "getRuntime",
1271 value: function getRuntime() {
1272 return this._endTime - this._startTime;
1273 }
1274 }, {
1275 key: "getStatus",
1276 value: function getStatus() {
1277 if (this.skipped) {
1278 return "skipped";
1279 }
1280
1281 var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
1282
1283 if (!testPassed) {
1284 return "failed";
1285 } else if (this.todo) {
1286 return "todo";
1287 } else {
1288 return "passed";
1289 }
1290 }
1291 }, {
1292 key: "getFailedAssertions",
1293 value: function getFailedAssertions() {
1294 return this.assertions.filter(function (assertion) {
1295 return !assertion.passed;
1296 });
1297 }
1298 }, {
1299 key: "getAssertions",
1300 value: function getAssertions() {
1301 return this.assertions.slice();
1302 }
1303
1304 // Remove actual and expected values from assertions. This is to prevent
1305 // leaking memory throughout a test suite.
1306
1307 }, {
1308 key: "slimAssertions",
1309 value: function slimAssertions() {
1310 this.assertions = this.assertions.map(function (assertion) {
1311 delete assertion.actual;
1312 delete assertion.expected;
1313 return assertion;
1314 });
1315 }
1316 }]);
1317 return TestReport;
1318 }();
1319
1320 var focused = false;
1321
1322 function Test(settings) {
1323 var i, l;
1324
1325 ++Test.count;
1326
1327 this.expected = null;
1328 extend(this, settings);
1329 this.assertions = [];
1330 this.semaphore = 0;
1331 this.module = config.currentModule;
1332 this.stack = sourceFromStacktrace(3);
1333 this.steps = [];
1334
1335 this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
1336 todo: settings.todo,
1337 skip: settings.skip,
1338 valid: this.valid()
1339 });
1340
1341 // Register unique strings
1342 for (i = 0, l = this.module.tests; i < l.length; i++) {
1343 if (this.module.tests[i].name === this.testName) {
1344 this.testName += " ";
1345 }
1346 }
1347
1348 this.testId = generateHash(this.module.name, this.testName);
1349
1350 this.module.tests.push({
1351 name: this.testName,
1352 testId: this.testId,
1353 skip: !!settings.skip
1354 });
1355
1356 if (settings.skip) {
1357
1358 // Skipped tests will fully ignore any sent callback
1359 this.callback = function () {};
1360 this.async = false;
1361 this.expected = 0;
1362 } else {
1363 this.assert = new Assert(this);
1364 }
1365 }
1366
1367 Test.count = 0;
1368
1369 function getNotStartedModules(startModule) {
1370 var module = startModule,
1371 modules = [];
1372
1373 while (module && module.testsRun === 0) {
1374 modules.push(module);
1375 module = module.parentModule;
1376 }
1377
1378 return modules;
1379 }
1380
1381 Test.prototype = {
1382 before: function before() {
1383 var i,
1384 startModule,
1385 module = this.module,
1386 notStartedModules = getNotStartedModules(module);
1387
1388 for (i = notStartedModules.length - 1; i >= 0; i--) {
1389 startModule = notStartedModules[i];
1390 startModule.stats = { all: 0, bad: 0, started: now() };
1391 emit("suiteStart", startModule.suiteReport.start(true));
1392 runLoggingCallbacks("moduleStart", {
1393 name: startModule.name,
1394 tests: startModule.tests
1395 });
1396 }
1397
1398 config.current = this;
1399
1400 this.testEnvironment = extend({}, module.testEnvironment);
1401
1402 this.started = now();
1403 emit("testStart", this.testReport.start(true));
1404 runLoggingCallbacks("testStart", {
1405 name: this.testName,
1406 module: module.name,
1407 testId: this.testId,
1408 previousFailure: this.previousFailure
1409 });
1410
1411 if (!config.pollution) {
1412 saveGlobal();
1413 }
1414 },
1415
1416 run: function run() {
1417 var promise;
1418
1419 config.current = this;
1420
1421 this.callbackStarted = now();
1422
1423 if (config.notrycatch) {
1424 runTest(this);
1425 return;
1426 }
1427
1428 try {
1429 runTest(this);
1430 } catch (e) {
1431 this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
1432
1433 // Else next test will carry the responsibility
1434 saveGlobal();
1435
1436 // Restart the tests if they're blocking
1437 if (config.blocking) {
1438 internalRecover(this);
1439 }
1440 }
1441
1442 function runTest(test) {
1443 promise = test.callback.call(test.testEnvironment, test.assert);
1444 test.resolvePromise(promise);
1445 }
1446 },
1447
1448 after: function after() {
1449 checkPollution();
1450 },
1451
1452 queueHook: function queueHook(hook, hookName, hookOwner) {
1453 var promise,
1454 test = this;
1455 return function runHook() {
1456 if (hookName === "before") {
1457 if (hookOwner.unskippedTestsRun !== 0) {
1458 return;
1459 }
1460
1461 test.preserveEnvironment = true;
1462 }
1463
1464 if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
1465 return;
1466 }
1467
1468 config.current = test;
1469 if (config.notrycatch) {
1470 callHook();
1471 return;
1472 }
1473 try {
1474 callHook();
1475 } catch (error) {
1476 test.pushFailure(hookName + " failed on " + test.testName + ": " + (error.message || error), extractStacktrace(error, 0));
1477 }
1478
1479 function callHook() {
1480 promise = hook.call(test.testEnvironment, test.assert);
1481 test.resolvePromise(promise, hookName);
1482 }
1483 };
1484 },
1485
1486 // Currently only used for module level hooks, can be used to add global level ones
1487 hooks: function hooks(handler) {
1488 var hooks = [];
1489
1490 function processHooks(test, module) {
1491 if (module.parentModule) {
1492 processHooks(test, module.parentModule);
1493 }
1494 if (module.hooks && objectType(module.hooks[handler]) === "function") {
1495 hooks.push(test.queueHook(module.hooks[handler], handler, module));
1496 }
1497 }
1498
1499 // Hooks are ignored on skipped tests
1500 if (!this.skip) {
1501 processHooks(this, this.module);
1502 }
1503 return hooks;
1504 },
1505
1506 finish: function finish() {
1507 config.current = this;
1508 if (config.requireExpects && this.expected === null) {
1509 this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
1510 } else if (this.expected !== null && this.expected !== this.assertions.length) {
1511 this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack);
1512 } else if (this.expected === null && !this.assertions.length) {
1513 this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack);
1514 }
1515
1516 var i,
1517 module = this.module,
1518 moduleName = module.name,
1519 testName = this.testName,
1520 skipped = !!this.skip,
1521 todo = !!this.todo,
1522 bad = 0,
1523 storage = config.storage;
1524
1525 this.runtime = now() - this.started;
1526
1527 config.stats.all += this.assertions.length;
1528 module.stats.all += this.assertions.length;
1529
1530 for (i = 0; i < this.assertions.length; i++) {
1531 if (!this.assertions[i].result) {
1532 bad++;
1533 config.stats.bad++;
1534 module.stats.bad++;
1535 }
1536 }
1537
1538 notifyTestsRan(module, skipped);
1539
1540 // Store result when possible
1541 if (storage) {
1542 if (bad) {
1543 storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
1544 } else {
1545 storage.removeItem("qunit-test-" + moduleName + "-" + testName);
1546 }
1547 }
1548
1549 // After emitting the js-reporters event we cleanup the assertion data to
1550 // avoid leaking it. It is not used by the legacy testDone callbacks.
1551 emit("testEnd", this.testReport.end(true));
1552 this.testReport.slimAssertions();
1553
1554 runLoggingCallbacks("testDone", {
1555 name: testName,
1556 module: moduleName,
1557 skipped: skipped,
1558 todo: todo,
1559 failed: bad,
1560 passed: this.assertions.length - bad,
1561 total: this.assertions.length,
1562 runtime: skipped ? 0 : this.runtime,
1563
1564 // HTML Reporter use
1565 assertions: this.assertions,
1566 testId: this.testId,
1567
1568 // Source of Test
1569 source: this.stack
1570 });
1571
1572 if (module.testsRun === numberOfTests(module)) {
1573 logSuiteEnd(module);
1574
1575 // Check if the parent modules, iteratively, are done. If that the case,
1576 // we emit the `suiteEnd` event and trigger `moduleDone` callback.
1577 var parent = module.parentModule;
1578 while (parent && parent.testsRun === numberOfTests(parent)) {
1579 logSuiteEnd(parent);
1580 parent = parent.parentModule;
1581 }
1582 }
1583
1584 config.current = undefined;
1585
1586 function logSuiteEnd(module) {
1587 emit("suiteEnd", module.suiteReport.end(true));
1588 runLoggingCallbacks("moduleDone", {
1589 name: module.name,
1590 tests: module.tests,
1591 failed: module.stats.bad,
1592 passed: module.stats.all - module.stats.bad,
1593 total: module.stats.all,
1594 runtime: now() - module.stats.started
1595 });
1596 }
1597 },
1598
1599 preserveTestEnvironment: function preserveTestEnvironment() {
1600 if (this.preserveEnvironment) {
1601 this.module.testEnvironment = this.testEnvironment;
1602 this.testEnvironment = extend({}, this.module.testEnvironment);
1603 }
1604 },
1605
1606 queue: function queue() {
1607 var test = this;
1608
1609 if (!this.valid()) {
1610 return;
1611 }
1612
1613 function runTest() {
1614
1615 // Each of these can by async
1616 ProcessingQueue.addImmediate([function () {
1617 test.before();
1618 }, test.hooks("before"), function () {
1619 test.preserveTestEnvironment();
1620 }, test.hooks("beforeEach"), function () {
1621 test.run();
1622 }, test.hooks("afterEach").reverse(), test.hooks("after").reverse(), function () {
1623 test.after();
1624 }, function () {
1625 test.finish();
1626 }]);
1627 }
1628
1629 var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
1630
1631 // Prioritize previously failed tests, detected from storage
1632 var prioritize = config.reorder && !!previousFailCount;
1633
1634 this.previousFailure = !!previousFailCount;
1635
1636 ProcessingQueue.add(runTest, prioritize, config.seed);
1637
1638 // If the queue has already finished, we manually process the new test
1639 if (ProcessingQueue.finished) {
1640 ProcessingQueue.advance();
1641 }
1642 },
1643
1644
1645 pushResult: function pushResult(resultInfo) {
1646 if (this !== config.current) {
1647 throw new Error("Assertion occured after test had finished.");
1648 }
1649
1650 // Destructure of resultInfo = { result, actual, expected, message, negative }
1651 var source,
1652 details = {
1653 module: this.module.name,
1654 name: this.testName,
1655 result: resultInfo.result,
1656 message: resultInfo.message,
1657 actual: resultInfo.actual,
1658 expected: resultInfo.expected,
1659 testId: this.testId,
1660 negative: resultInfo.negative || false,
1661 runtime: now() - this.started,
1662 todo: !!this.todo
1663 };
1664
1665 if (!resultInfo.result) {
1666 source = resultInfo.source || sourceFromStacktrace();
1667
1668 if (source) {
1669 details.source = source;
1670 }
1671 }
1672
1673 this.logAssertion(details);
1674
1675 this.assertions.push({
1676 result: !!resultInfo.result,
1677 message: resultInfo.message
1678 });
1679 },
1680
1681 pushFailure: function pushFailure(message, source, actual) {
1682 if (!(this instanceof Test)) {
1683 throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
1684 }
1685
1686 this.pushResult({
1687 result: false,
1688 message: message || "error",
1689 actual: actual || null,
1690 expected: null,
1691 source: source
1692 });
1693 },
1694
1695 /**
1696 * Log assertion details using both the old QUnit.log interface and
1697 * QUnit.on( "assertion" ) interface.
1698 *
1699 * @private
1700 */
1701 logAssertion: function logAssertion(details) {
1702 runLoggingCallbacks("log", details);
1703
1704 var assertion = {
1705 passed: details.result,
1706 actual: details.actual,
1707 expected: details.expected,
1708 message: details.message,
1709 stack: details.source,
1710 todo: details.todo
1711 };
1712 this.testReport.pushAssertion(assertion);
1713 emit("assertion", assertion);
1714 },
1715
1716
1717 resolvePromise: function resolvePromise(promise, phase) {
1718 var then,
1719 resume,
1720 message,
1721 test = this;
1722 if (promise != null) {
1723 then = promise.then;
1724 if (objectType(then) === "function") {
1725 resume = internalStop(test);
1726 then.call(promise, function () {
1727 resume();
1728 }, function (error) {
1729 message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
1730 test.pushFailure(message, extractStacktrace(error, 0));
1731
1732 // Else next test will carry the responsibility
1733 saveGlobal();
1734
1735 // Unblock
1736 resume();
1737 });
1738 }
1739 }
1740 },
1741
1742 valid: function valid() {
1743 var filter = config.filter,
1744 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
1745 module = config.module && config.module.toLowerCase(),
1746 fullName = this.module.name + ": " + this.testName;
1747
1748 function moduleChainNameMatch(testModule) {
1749 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1750 if (testModuleName === module) {
1751 return true;
1752 } else if (testModule.parentModule) {
1753 return moduleChainNameMatch(testModule.parentModule);
1754 } else {
1755 return false;
1756 }
1757 }
1758
1759 function moduleChainIdMatch(testModule) {
1760 return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
1761 }
1762
1763 // Internally-generated tests are always valid
1764 if (this.callback && this.callback.validTest) {
1765 return true;
1766 }
1767
1768 if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
1769
1770 return false;
1771 }
1772
1773 if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
1774
1775 return false;
1776 }
1777
1778 if (module && !moduleChainNameMatch(this.module)) {
1779 return false;
1780 }
1781
1782 if (!filter) {
1783 return true;
1784 }
1785
1786 return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
1787 },
1788
1789 regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
1790 var regex = new RegExp(pattern, flags);
1791 var match = regex.test(fullName);
1792
1793 return match !== exclude;
1794 },
1795
1796 stringFilter: function stringFilter(filter, fullName) {
1797 filter = filter.toLowerCase();
1798 fullName = fullName.toLowerCase();
1799
1800 var include = filter.charAt(0) !== "!";
1801 if (!include) {
1802 filter = filter.slice(1);
1803 }
1804
1805 // If the filter matches, we need to honour include
1806 if (fullName.indexOf(filter) !== -1) {
1807 return include;
1808 }
1809
1810 // Otherwise, do the opposite
1811 return !include;
1812 }
1813 };
1814
1815 function pushFailure() {
1816 if (!config.current) {
1817 throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
1818 }
1819
1820 // Gets current test obj
1821 var currentTest = config.current;
1822
1823 return currentTest.pushFailure.apply(currentTest, arguments);
1824 }
1825
1826 function saveGlobal() {
1827 config.pollution = [];
1828
1829 if (config.noglobals) {
1830 for (var key in global$1) {
1831 if (hasOwn.call(global$1, key)) {
1832
1833 // In Opera sometimes DOM element ids show up here, ignore them
1834 if (/^qunit-test-output/.test(key)) {
1835 continue;
1836 }
1837 config.pollution.push(key);
1838 }
1839 }
1840 }
1841 }
1842
1843 function checkPollution() {
1844 var newGlobals,
1845 deletedGlobals,
1846 old = config.pollution;
1847
1848 saveGlobal();
1849
1850 newGlobals = diff(config.pollution, old);
1851 if (newGlobals.length > 0) {
1852 pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
1853 }
1854
1855 deletedGlobals = diff(old, config.pollution);
1856 if (deletedGlobals.length > 0) {
1857 pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
1858 }
1859 }
1860
1861 // Will be exposed as QUnit.test
1862 function test(testName, callback) {
1863 if (focused) {
1864 return;
1865 }
1866
1867 var newTest = new Test({
1868 testName: testName,
1869 callback: callback
1870 });
1871
1872 newTest.queue();
1873 }
1874
1875 function todo(testName, callback) {
1876 if (focused) {
1877 return;
1878 }
1879
1880 var newTest = new Test({
1881 testName: testName,
1882 callback: callback,
1883 todo: true
1884 });
1885
1886 newTest.queue();
1887 }
1888
1889 // Will be exposed as QUnit.skip
1890 function skip(testName) {
1891 if (focused) {
1892 return;
1893 }
1894
1895 var test = new Test({
1896 testName: testName,
1897 skip: true
1898 });
1899
1900 test.queue();
1901 }
1902
1903 // Will be exposed as QUnit.only
1904 function only(testName, callback) {
1905 if (focused) {
1906 return;
1907 }
1908
1909 config.queue.length = 0;
1910 focused = true;
1911
1912 var newTest = new Test({
1913 testName: testName,
1914 callback: callback
1915 });
1916
1917 newTest.queue();
1918 }
1919
1920 // Put a hold on processing and return a function that will release it.
1921 function internalStop(test) {
1922 var released = false;
1923
1924 test.semaphore += 1;
1925 config.blocking = true;
1926
1927 // Set a recovery timeout, if so configured.
1928 if (config.testTimeout && defined.setTimeout) {
1929 clearTimeout(config.timeout);
1930 config.timeout = setTimeout(function () {
1931 pushFailure("Test timed out", sourceFromStacktrace(2));
1932 internalRecover(test);
1933 }, config.testTimeout);
1934 }
1935
1936 return function resume() {
1937 if (released) {
1938 return;
1939 }
1940
1941 released = true;
1942 test.semaphore -= 1;
1943 internalStart(test);
1944 };
1945 }
1946
1947 // Forcefully release all processing holds.
1948 function internalRecover(test) {
1949 test.semaphore = 0;
1950 internalStart(test);
1951 }
1952
1953 // Release a processing hold, scheduling a resumption attempt if no holds remain.
1954 function internalStart(test) {
1955
1956 // If semaphore is non-numeric, throw error
1957 if (isNaN(test.semaphore)) {
1958 test.semaphore = 0;
1959
1960 pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
1961 return;
1962 }
1963
1964 // Don't start until equal number of stop-calls
1965 if (test.semaphore > 0) {
1966 return;
1967 }
1968
1969 // Throw an Error if start is called more often than stop
1970 if (test.semaphore < 0) {
1971 test.semaphore = 0;
1972
1973 pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
1974 return;
1975 }
1976
1977 // Add a slight delay to allow more assertions etc.
1978 if (defined.setTimeout) {
1979 if (config.timeout) {
1980 clearTimeout(config.timeout);
1981 }
1982 config.timeout = setTimeout(function () {
1983 if (test.semaphore > 0) {
1984 return;
1985 }
1986
1987 if (config.timeout) {
1988 clearTimeout(config.timeout);
1989 }
1990
1991 begin();
1992 }, 13);
1993 } else {
1994 begin();
1995 }
1996 }
1997
1998 function collectTests(module) {
1999 var tests = [].concat(module.tests);
2000 var modules = [].concat(toConsumableArray(module.childModules));
2001
2002 // Do a breadth-first traversal of the child modules
2003 while (modules.length) {
2004 var nextModule = modules.shift();
2005 tests.push.apply(tests, nextModule.tests);
2006 modules.push.apply(modules, toConsumableArray(nextModule.childModules));
2007 }
2008
2009 return tests;
2010 }
2011
2012 function numberOfTests(module) {
2013 return collectTests(module).length;
2014 }
2015
2016 function numberOfUnskippedTests(module) {
2017 return collectTests(module).filter(function (test) {
2018 return !test.skip;
2019 }).length;
2020 }
2021
2022 function notifyTestsRan(module, skipped) {
2023 module.testsRun++;
2024 if (!skipped) {
2025 module.unskippedTestsRun++;
2026 }
2027 while (module = module.parentModule) {
2028 module.testsRun++;
2029 if (!skipped) {
2030 module.unskippedTestsRun++;
2031 }
2032 }
2033 }
2034
2035 /**
2036 * Returns a function that proxies to the given method name on the globals
2037 * console object. The proxy will also detect if the console doesn't exist and
2038 * will appropriately no-op. This allows support for IE9, which doesn't have a
2039 * console if the developer tools are not open.
2040 */
2041 function consoleProxy(method) {
2042 return function () {
2043 if (console) {
2044 console[method].apply(console, arguments);
2045 }
2046 };
2047 }
2048
2049 var Logger = {
2050 warn: consoleProxy("warn")
2051 };
2052
2053 var Assert = function () {
2054 function Assert(testContext) {
2055 classCallCheck(this, Assert);
2056
2057 this.test = testContext;
2058 }
2059
2060 // Assert helpers
2061
2062 // Documents a "step", which is a string value, in a test as a passing assertion
2063
2064
2065 createClass(Assert, [{
2066 key: "step",
2067 value: function step(message) {
2068 var result = !!message;
2069
2070 this.test.steps.push(message);
2071
2072 return this.pushResult({
2073 result: result,
2074 message: message || "You must provide a message to assert.step"
2075 });
2076 }
2077
2078 // Verifies the steps in a test match a given array of string values
2079
2080 }, {
2081 key: "verifySteps",
2082 value: function verifySteps(steps, message) {
2083 this.deepEqual(this.test.steps, steps, message);
2084 }
2085
2086 // Specify the number of expected assertions to guarantee that failed test
2087 // (no assertions are run at all) don't slip through.
2088
2089 }, {
2090 key: "expect",
2091 value: function expect(asserts) {
2092 if (arguments.length === 1) {
2093 this.test.expected = asserts;
2094 } else {
2095 return this.test.expected;
2096 }
2097 }
2098
2099 // Put a hold on processing and return a function that will release it a maximum of once.
2100
2101 }, {
2102 key: "async",
2103 value: function async(count) {
2104 var test$$1 = this.test;
2105
2106 var popped = false,
2107 acceptCallCount = count;
2108
2109 if (typeof acceptCallCount === "undefined") {
2110 acceptCallCount = 1;
2111 }
2112
2113 var resume = internalStop(test$$1);
2114
2115 return function done() {
2116 if (config.current !== test$$1) {
2117 throw Error("assert.async callback called after test finished.");
2118 }
2119
2120 if (popped) {
2121 test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
2122 return;
2123 }
2124
2125 acceptCallCount -= 1;
2126 if (acceptCallCount > 0) {
2127 return;
2128 }
2129
2130 popped = true;
2131 resume();
2132 };
2133 }
2134
2135 // Exports test.push() to the user API
2136 // Alias of pushResult.
2137
2138 }, {
2139 key: "push",
2140 value: function push(result, actual, expected, message, negative) {
2141 Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/).");
2142
2143 var currentAssert = this instanceof Assert ? this : config.current.assert;
2144 return currentAssert.pushResult({
2145 result: result,
2146 actual: actual,
2147 expected: expected,
2148 message: message,
2149 negative: negative
2150 });
2151 }
2152 }, {
2153 key: "pushResult",
2154 value: function pushResult(resultInfo) {
2155
2156 // Destructure of resultInfo = { result, actual, expected, message, negative }
2157 var assert = this;
2158 var currentTest = assert instanceof Assert && assert.test || config.current;
2159
2160 // Backwards compatibility fix.
2161 // Allows the direct use of global exported assertions and QUnit.assert.*
2162 // Although, it's use is not recommended as it can leak assertions
2163 // to other tests from async tests, because we only get a reference to the current test,
2164 // not exactly the test where assertion were intended to be called.
2165 if (!currentTest) {
2166 throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
2167 }
2168
2169 if (!(assert instanceof Assert)) {
2170 assert = currentTest.assert;
2171 }
2172
2173 return assert.test.pushResult(resultInfo);
2174 }
2175 }, {
2176 key: "ok",
2177 value: function ok(result, message) {
2178 if (!message) {
2179 message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
2180 }
2181
2182 this.pushResult({
2183 result: !!result,
2184 actual: result,
2185 expected: true,
2186 message: message
2187 });
2188 }
2189 }, {
2190 key: "notOk",
2191 value: function notOk(result, message) {
2192 if (!message) {
2193 message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
2194 }
2195
2196 this.pushResult({
2197 result: !result,
2198 actual: result,
2199 expected: false,
2200 message: message
2201 });
2202 }
2203 }, {
2204 key: "equal",
2205 value: function equal(actual, expected, message) {
2206
2207 // eslint-disable-next-line eqeqeq
2208 var result = expected == actual;
2209
2210 this.pushResult({
2211 result: result,
2212 actual: actual,
2213 expected: expected,
2214 message: message
2215 });
2216 }
2217 }, {
2218 key: "notEqual",
2219 value: function notEqual(actual, expected, message) {
2220
2221 // eslint-disable-next-line eqeqeq
2222 var result = expected != actual;
2223
2224 this.pushResult({
2225 result: result,
2226 actual: actual,
2227 expected: expected,
2228 message: message,
2229 negative: true
2230 });
2231 }
2232 }, {
2233 key: "propEqual",
2234 value: function propEqual(actual, expected, message) {
2235 actual = objectValues(actual);
2236 expected = objectValues(expected);
2237
2238 this.pushResult({
2239 result: equiv(actual, expected),
2240 actual: actual,
2241 expected: expected,
2242 message: message
2243 });
2244 }
2245 }, {
2246 key: "notPropEqual",
2247 value: function notPropEqual(actual, expected, message) {
2248 actual = objectValues(actual);
2249 expected = objectValues(expected);
2250
2251 this.pushResult({
2252 result: !equiv(actual, expected),
2253 actual: actual,
2254 expected: expected,
2255 message: message,
2256 negative: true
2257 });
2258 }
2259 }, {
2260 key: "deepEqual",
2261 value: function deepEqual(actual, expected, message) {
2262 this.pushResult({
2263 result: equiv(actual, expected),
2264 actual: actual,
2265 expected: expected,
2266 message: message
2267 });
2268 }
2269 }, {
2270 key: "notDeepEqual",
2271 value: function notDeepEqual(actual, expected, message) {
2272 this.pushResult({
2273 result: !equiv(actual, expected),
2274 actual: actual,
2275 expected: expected,
2276 message: message,
2277 negative: true
2278 });
2279 }
2280 }, {
2281 key: "strictEqual",
2282 value: function strictEqual(actual, expected, message) {
2283 this.pushResult({
2284 result: expected === actual,
2285 actual: actual,
2286 expected: expected,
2287 message: message
2288 });
2289 }
2290 }, {
2291 key: "notStrictEqual",
2292 value: function notStrictEqual(actual, expected, message) {
2293 this.pushResult({
2294 result: expected !== actual,
2295 actual: actual,
2296 expected: expected,
2297 message: message,
2298 negative: true
2299 });
2300 }
2301 }, {
2302 key: "throws",
2303 value: function throws(block, expected, message) {
2304 var actual = void 0,
2305 result = false;
2306
2307 var currentTest = this instanceof Assert && this.test || config.current;
2308
2309 // 'expected' is optional unless doing string comparison
2310 if (objectType(expected) === "string") {
2311 if (message == null) {
2312 message = expected;
2313 expected = null;
2314 } else {
2315 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.");
2316 }
2317 }
2318
2319 currentTest.ignoreGlobalErrors = true;
2320 try {
2321 block.call(currentTest.testEnvironment);
2322 } catch (e) {
2323 actual = e;
2324 }
2325 currentTest.ignoreGlobalErrors = false;
2326
2327 if (actual) {
2328 var expectedType = objectType(expected);
2329
2330 // We don't want to validate thrown error
2331 if (!expected) {
2332 result = true;
2333 expected = null;
2334
2335 // Expected is a regexp
2336 } else if (expectedType === "regexp") {
2337 result = expected.test(errorString(actual));
2338
2339 // Expected is a constructor, maybe an Error constructor
2340 } else if (expectedType === "function" && actual instanceof expected) {
2341 result = true;
2342
2343 // Expected is an Error object
2344 } else if (expectedType === "object") {
2345 result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
2346
2347 // Expected is a validation function which returns true if validation passed
2348 } else if (expectedType === "function" && expected.call({}, actual) === true) {
2349 expected = null;
2350 result = true;
2351 }
2352 }
2353
2354 currentTest.assert.pushResult({
2355 result: result,
2356 actual: actual,
2357 expected: expected,
2358 message: message
2359 });
2360 }
2361 }]);
2362 return Assert;
2363 }();
2364
2365 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
2366 // Known to us are: Closure Compiler, Narwhal
2367 // eslint-disable-next-line dot-notation
2368
2369
2370 Assert.prototype.raises = Assert.prototype["throws"];
2371
2372 /**
2373 * Converts an error into a simple string for comparisons.
2374 *
2375 * @param {Error} error
2376 * @return {String}
2377 */
2378 function errorString(error) {
2379 var resultErrorString = error.toString();
2380
2381 if (resultErrorString.substring(0, 7) === "[object") {
2382 var name = error.name ? error.name.toString() : "Error";
2383 var message = error.message ? error.message.toString() : "";
2384
2385 if (name && message) {
2386 return name + ": " + message;
2387 } else if (name) {
2388 return name;
2389 } else if (message) {
2390 return message;
2391 } else {
2392 return "Error";
2393 }
2394 } else {
2395 return resultErrorString;
2396 }
2397 }
2398
2399 /* global module, exports, define */
2400 function exportQUnit(QUnit) {
2401
2402 if (defined.document) {
2403
2404 // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
2405 if (window.QUnit && window.QUnit.version) {
2406 throw new Error("QUnit has already been defined.");
2407 }
2408
2409 window.QUnit = QUnit;
2410 }
2411
2412 // For nodejs
2413 if (typeof module !== "undefined" && module && module.exports) {
2414 module.exports = QUnit;
2415
2416 // For consistency with CommonJS environments' exports
2417 module.exports.QUnit = QUnit;
2418 }
2419
2420 // For CommonJS with exports, but without module.exports, like Rhino
2421 if (typeof exports !== "undefined" && exports) {
2422 exports.QUnit = QUnit;
2423 }
2424
2425 if (typeof define === "function" && define.amd) {
2426 define(function () {
2427 return QUnit;
2428 });
2429 QUnit.config.autostart = false;
2430 }
2431
2432 // For Web/Service Workers
2433 if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
2434 self$1.QUnit = QUnit;
2435 }
2436 }
2437
2438 var SuiteReport = function () {
2439 function SuiteReport(name, parentSuite) {
2440 classCallCheck(this, SuiteReport);
2441
2442 this.name = name;
2443 this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
2444
2445 this.tests = [];
2446 this.childSuites = [];
2447
2448 if (parentSuite) {
2449 parentSuite.pushChildSuite(this);
2450 }
2451 }
2452
2453 createClass(SuiteReport, [{
2454 key: "start",
2455 value: function start(recordTime) {
2456 if (recordTime) {
2457 this._startTime = Date.now();
2458 }
2459
2460 return {
2461 name: this.name,
2462 fullName: this.fullName.slice(),
2463 tests: this.tests.map(function (test) {
2464 return test.start();
2465 }),
2466 childSuites: this.childSuites.map(function (suite) {
2467 return suite.start();
2468 }),
2469 testCounts: {
2470 total: this.getTestCounts().total
2471 }
2472 };
2473 }
2474 }, {
2475 key: "end",
2476 value: function end(recordTime) {
2477 if (recordTime) {
2478 this._endTime = Date.now();
2479 }
2480
2481 return {
2482 name: this.name,
2483 fullName: this.fullName.slice(),
2484 tests: this.tests.map(function (test) {
2485 return test.end();
2486 }),
2487 childSuites: this.childSuites.map(function (suite) {
2488 return suite.end();
2489 }),
2490 testCounts: this.getTestCounts(),
2491 runtime: this.getRuntime(),
2492 status: this.getStatus()
2493 };
2494 }
2495 }, {
2496 key: "pushChildSuite",
2497 value: function pushChildSuite(suite) {
2498 this.childSuites.push(suite);
2499 }
2500 }, {
2501 key: "pushTest",
2502 value: function pushTest(test) {
2503 this.tests.push(test);
2504 }
2505 }, {
2506 key: "getRuntime",
2507 value: function getRuntime() {
2508 return this._endTime - this._startTime;
2509 }
2510 }, {
2511 key: "getTestCounts",
2512 value: function getTestCounts() {
2513 var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
2514
2515 counts = this.tests.reduce(function (counts, test) {
2516 if (test.valid) {
2517 counts[test.getStatus()]++;
2518 counts.total++;
2519 }
2520
2521 return counts;
2522 }, counts);
2523
2524 return this.childSuites.reduce(function (counts, suite) {
2525 return suite.getTestCounts(counts);
2526 }, counts);
2527 }
2528 }, {
2529 key: "getStatus",
2530 value: function getStatus() {
2531 var _getTestCounts = this.getTestCounts(),
2532 total = _getTestCounts.total,
2533 failed = _getTestCounts.failed,
2534 skipped = _getTestCounts.skipped,
2535 todo = _getTestCounts.todo;
2536
2537 if (failed) {
2538 return "failed";
2539 } else {
2540 if (skipped === total) {
2541 return "skipped";
2542 } else if (todo === total) {
2543 return "todo";
2544 } else {
2545 return "passed";
2546 }
2547 }
2548 }
2549 }]);
2550 return SuiteReport;
2551 }();
2552
2553 // Handle an unhandled exception. By convention, returns true if further
2554 // error handling should be suppressed and false otherwise.
2555 // In this case, we will only suppress further error handling if the
2556 // "ignoreGlobalErrors" configuration option is enabled.
2557 function onError(error) {
2558 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
2559 args[_key - 1] = arguments[_key];
2560 }
2561
2562 if (config.current) {
2563 if (config.current.ignoreGlobalErrors) {
2564 return true;
2565 }
2566 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2567 } else {
2568 test("global failure", extend(function () {
2569 pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
2570 }, { validTest: true }));
2571 }
2572
2573 return false;
2574 }
2575
2576 var QUnit = {};
2577 var globalSuite = new SuiteReport();
2578
2579 // The initial "currentModule" represents the global (or top-level) module that
2580 // is not explicitly defined by the user, therefore we add the "globalSuite" to
2581 // it since each module has a suiteReport associated with it.
2582 config.currentModule.suiteReport = globalSuite;
2583
2584 var moduleStack = [];
2585 var globalStartCalled = false;
2586 var runStarted = false;
2587
2588 // Figure out if we're running the tests from a server or not
2589 QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
2590
2591 // Expose the current QUnit version
2592 QUnit.version = "2.3.3";
2593
2594 function createModule(name, testEnvironment) {
2595 var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
2596 var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
2597 var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
2598
2599 var module = {
2600 name: moduleName,
2601 parentModule: parentModule,
2602 tests: [],
2603 moduleId: generateHash(moduleName),
2604 testsRun: 0,
2605 unskippedTestsRun: 0,
2606 childModules: [],
2607 suiteReport: new SuiteReport(name, parentSuite)
2608 };
2609
2610 var env = {};
2611 if (parentModule) {
2612 parentModule.childModules.push(module);
2613 extend(env, parentModule.testEnvironment);
2614 }
2615 extend(env, testEnvironment);
2616 module.testEnvironment = env;
2617
2618 config.modules.push(module);
2619 return module;
2620 }
2621
2622 extend(QUnit, {
2623 on: on,
2624
2625 // Call on start of module test to prepend name to all tests
2626 module: function module(name, testEnvironment, executeNow) {
2627 if (arguments.length === 2) {
2628 if (objectType(testEnvironment) === "function") {
2629 executeNow = testEnvironment;
2630 testEnvironment = undefined;
2631 }
2632 }
2633
2634 var module = createModule(name, testEnvironment);
2635
2636 // Move any hooks to a 'hooks' object
2637 if (module.testEnvironment) {
2638 module.hooks = {
2639 before: module.testEnvironment.before,
2640 beforeEach: module.testEnvironment.beforeEach,
2641 afterEach: module.testEnvironment.afterEach,
2642 after: module.testEnvironment.after
2643 };
2644
2645 delete module.testEnvironment.before;
2646 delete module.testEnvironment.beforeEach;
2647 delete module.testEnvironment.afterEach;
2648 delete module.testEnvironment.after;
2649 }
2650
2651 var moduleFns = {
2652 before: setHook(module, "before"),
2653 beforeEach: setHook(module, "beforeEach"),
2654 afterEach: setHook(module, "afterEach"),
2655 after: setHook(module, "after")
2656 };
2657
2658 var currentModule = config.currentModule;
2659 if (objectType(executeNow) === "function") {
2660 moduleStack.push(module);
2661 config.currentModule = module;
2662 executeNow.call(module.testEnvironment, moduleFns);
2663 moduleStack.pop();
2664 module = module.parentModule || currentModule;
2665 }
2666
2667 config.currentModule = module;
2668 },
2669
2670 test: test,
2671
2672 todo: todo,
2673
2674 skip: skip,
2675
2676 only: only,
2677
2678 start: function start(count) {
2679 var globalStartAlreadyCalled = globalStartCalled;
2680
2681 if (!config.current) {
2682 globalStartCalled = true;
2683
2684 if (runStarted) {
2685 throw new Error("Called start() while test already started running");
2686 } else if (globalStartAlreadyCalled || count > 1) {
2687 throw new Error("Called start() outside of a test context too many times");
2688 } else if (config.autostart) {
2689 throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
2690 } else if (!config.pageLoaded) {
2691
2692 // The page isn't completely loaded yet, so we set autostart and then
2693 // load if we're in Node or wait for the browser's load event.
2694 config.autostart = true;
2695
2696 // Starts from Node even if .load was not previously called. We still return
2697 // early otherwise we'll wind up "beginning" twice.
2698 if (!defined.document) {
2699 QUnit.load();
2700 }
2701
2702 return;
2703 }
2704 } else {
2705 throw new Error("QUnit.start cannot be called inside a test context.");
2706 }
2707
2708 scheduleBegin();
2709 },
2710
2711 config: config,
2712
2713 is: is,
2714
2715 objectType: objectType,
2716
2717 extend: extend,
2718
2719 load: function load() {
2720 config.pageLoaded = true;
2721
2722 // Initialize the configuration options
2723 extend(config, {
2724 stats: { all: 0, bad: 0 },
2725 started: 0,
2726 updateRate: 1000,
2727 autostart: true,
2728 filter: ""
2729 }, true);
2730
2731 if (!runStarted) {
2732 config.blocking = false;
2733
2734 if (config.autostart) {
2735 scheduleBegin();
2736 }
2737 }
2738 },
2739
2740 stack: function stack(offset) {
2741 offset = (offset || 0) + 2;
2742 return sourceFromStacktrace(offset);
2743 },
2744
2745 onError: onError
2746 });
2747
2748 QUnit.pushFailure = pushFailure;
2749 QUnit.assert = Assert.prototype;
2750 QUnit.equiv = equiv;
2751 QUnit.dump = dump;
2752
2753 registerLoggingCallbacks(QUnit);
2754
2755 function scheduleBegin() {
2756
2757 runStarted = true;
2758
2759 // Add a slight delay to allow definition of more modules and tests.
2760 if (defined.setTimeout) {
2761 setTimeout(function () {
2762 begin();
2763 }, 13);
2764 } else {
2765 begin();
2766 }
2767 }
2768
2769 function begin() {
2770 var i,
2771 l,
2772 modulesLog = [];
2773
2774 // If the test run hasn't officially begun yet
2775 if (!config.started) {
2776
2777 // Record the time of the test run's beginning
2778 config.started = now();
2779
2780 // Delete the loose unnamed module if unused.
2781 if (config.modules[0].name === "" && config.modules[0].tests.length === 0) {
2782 config.modules.shift();
2783 }
2784
2785 // Avoid unnecessary information by not logging modules' test environments
2786 for (i = 0, l = config.modules.length; i < l; i++) {
2787 modulesLog.push({
2788 name: config.modules[i].name,
2789 tests: config.modules[i].tests
2790 });
2791 }
2792
2793 // The test run is officially beginning now
2794 emit("runStart", globalSuite.start(true));
2795 runLoggingCallbacks("begin", {
2796 totalTests: Test.count,
2797 modules: modulesLog
2798 });
2799 }
2800
2801 config.blocking = false;
2802 ProcessingQueue.advance();
2803 }
2804
2805 function setHook(module, hookName) {
2806 if (!module.hooks) {
2807 module.hooks = {};
2808 }
2809
2810 return function (callback) {
2811 module.hooks[hookName] = callback;
2812 };
2813 }
2814
2815 exportQUnit(QUnit);
2816
2817 (function () {
2818
2819 if (typeof window === "undefined" || typeof document === "undefined") {
2820 return;
2821 }
2822
2823 var config = QUnit.config,
2824 hasOwn = Object.prototype.hasOwnProperty;
2825
2826 // Stores fixture HTML for resetting later
2827 function storeFixture() {
2828
2829 // Avoid overwriting user-defined values
2830 if (hasOwn.call(config, "fixture")) {
2831 return;
2832 }
2833
2834 var fixture = document.getElementById("qunit-fixture");
2835 if (fixture) {
2836 config.fixture = fixture.innerHTML;
2837 }
2838 }
2839
2840 QUnit.begin(storeFixture);
2841
2842 // Resets the fixture DOM element if available.
2843 function resetFixture() {
2844 if (config.fixture == null) {
2845 return;
2846 }
2847
2848 var fixture = document.getElementById("qunit-fixture");
2849 if (fixture) {
2850 fixture.innerHTML = config.fixture;
2851 }
2852 }
2853
2854 QUnit.testStart(resetFixture);
2855 })();
2856
2857 (function () {
2858
2859 // Only interact with URLs via window.location
2860 var location = typeof window !== "undefined" && window.location;
2861 if (!location) {
2862 return;
2863 }
2864
2865 var urlParams = getUrlParams();
2866
2867 QUnit.urlParams = urlParams;
2868
2869 // Match module/test by inclusion in an array
2870 QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
2871 QUnit.config.testId = [].concat(urlParams.testId || []);
2872
2873 // Exact case-insensitive match of the module name
2874 QUnit.config.module = urlParams.module;
2875
2876 // Regular expression or case-insenstive substring match against "moduleName: testName"
2877 QUnit.config.filter = urlParams.filter;
2878
2879 // Test order randomization
2880 if (urlParams.seed === true) {
2881
2882 // Generate a random seed if the option is specified without a value
2883 QUnit.config.seed = Math.random().toString(36).slice(2);
2884 } else if (urlParams.seed) {
2885 QUnit.config.seed = urlParams.seed;
2886 }
2887
2888 // Add URL-parameter-mapped config values with UI form rendering data
2889 QUnit.config.urlConfig.push({
2890 id: "hidepassed",
2891 label: "Hide passed tests",
2892 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
2893 }, {
2894 id: "noglobals",
2895 label: "Check for Globals",
2896 tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). Stored as query-strings."
2897 }, {
2898 id: "notrycatch",
2899 label: "No try-catch",
2900 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + "exceptions in IE reasonable. Stored as query-strings."
2901 });
2902
2903 QUnit.begin(function () {
2904 var i,
2905 option,
2906 urlConfig = QUnit.config.urlConfig;
2907
2908 for (i = 0; i < urlConfig.length; i++) {
2909
2910 // Options can be either strings or objects with nonempty "id" properties
2911 option = QUnit.config.urlConfig[i];
2912 if (typeof option !== "string") {
2913 option = option.id;
2914 }
2915
2916 if (QUnit.config[option] === undefined) {
2917 QUnit.config[option] = urlParams[option];
2918 }
2919 }
2920 });
2921
2922 function getUrlParams() {
2923 var i, param, name, value;
2924 var urlParams = Object.create(null);
2925 var params = location.search.slice(1).split("&");
2926 var length = params.length;
2927
2928 for (i = 0; i < length; i++) {
2929 if (params[i]) {
2930 param = params[i].split("=");
2931 name = decodeQueryParam(param[0]);
2932
2933 // Allow just a key to turn on a flag, e.g., test.html?noglobals
2934 value = param.length === 1 || decodeQueryParam(param.slice(1).join("="));
2935 if (name in urlParams) {
2936 urlParams[name] = [].concat(urlParams[name], value);
2937 } else {
2938 urlParams[name] = value;
2939 }
2940 }
2941 }
2942
2943 return urlParams;
2944 }
2945
2946 function decodeQueryParam(param) {
2947 return decodeURIComponent(param.replace(/\+/g, "%20"));
2948 }
2949 })();
2950
2951 var stats = {
2952 passedTests: 0,
2953 failedTests: 0,
2954 skippedTests: 0,
2955 todoTests: 0
2956 };
2957
2958 // Escape text for attribute or text content.
2959 function escapeText(s) {
2960 if (!s) {
2961 return "";
2962 }
2963 s = s + "";
2964
2965 // Both single quotes and double quotes (for attributes)
2966 return s.replace(/['"<>&]/g, function (s) {
2967 switch (s) {
2968 case "'":
2969 return "&#039;";
2970 case "\"":
2971 return "&quot;";
2972 case "<":
2973 return "&lt;";
2974 case ">":
2975 return "&gt;";
2976 case "&":
2977 return "&amp;";
2978 }
2979 });
2980 }
2981
2982 (function () {
2983
2984 // Don't load the HTML Reporter on non-browser environments
2985 if (typeof window === "undefined" || !window.document) {
2986 return;
2987 }
2988
2989 var config = QUnit.config,
2990 document$$1 = window.document,
2991 collapseNext = false,
2992 hasOwn = Object.prototype.hasOwnProperty,
2993 unfilteredUrl = setUrl({ filter: undefined, module: undefined,
2994 moduleId: undefined, testId: undefined }),
2995 modulesList = [];
2996
2997 function addEvent(elem, type, fn) {
2998 elem.addEventListener(type, fn, false);
2999 }
3000
3001 function removeEvent(elem, type, fn) {
3002 elem.removeEventListener(type, fn, false);
3003 }
3004
3005 function addEvents(elems, type, fn) {
3006 var i = elems.length;
3007 while (i--) {
3008 addEvent(elems[i], type, fn);
3009 }
3010 }
3011
3012 function hasClass(elem, name) {
3013 return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
3014 }
3015
3016 function addClass(elem, name) {
3017 if (!hasClass(elem, name)) {
3018 elem.className += (elem.className ? " " : "") + name;
3019 }
3020 }
3021
3022 function toggleClass(elem, name, force) {
3023 if (force || typeof force === "undefined" && !hasClass(elem, name)) {
3024 addClass(elem, name);
3025 } else {
3026 removeClass(elem, name);
3027 }
3028 }
3029
3030 function removeClass(elem, name) {
3031 var set = " " + elem.className + " ";
3032
3033 // Class name may appear multiple times
3034 while (set.indexOf(" " + name + " ") >= 0) {
3035 set = set.replace(" " + name + " ", " ");
3036 }
3037
3038 // Trim for prettiness
3039 elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
3040 }
3041
3042 function id(name) {
3043 return document$$1.getElementById && document$$1.getElementById(name);
3044 }
3045
3046 function abortTests() {
3047 var abortButton = id("qunit-abort-tests-button");
3048 if (abortButton) {
3049 abortButton.disabled = true;
3050 abortButton.innerHTML = "Aborting...";
3051 }
3052 QUnit.config.queue.length = 0;
3053 return false;
3054 }
3055
3056 function interceptNavigation(ev) {
3057 applyUrlParams();
3058
3059 if (ev && ev.preventDefault) {
3060 ev.preventDefault();
3061 }
3062
3063 return false;
3064 }
3065
3066 function getUrlConfigHtml() {
3067 var i,
3068 j,
3069 val,
3070 escaped,
3071 escapedTooltip,
3072 selection = false,
3073 urlConfig = config.urlConfig,
3074 urlConfigHtml = "";
3075
3076 for (i = 0; i < urlConfig.length; i++) {
3077
3078 // Options can be either strings or objects with nonempty "id" properties
3079 val = config.urlConfig[i];
3080 if (typeof val === "string") {
3081 val = {
3082 id: val,
3083 label: val
3084 };
3085 }
3086
3087 escaped = escapeText(val.id);
3088 escapedTooltip = escapeText(val.tooltip);
3089
3090 if (!val.value || typeof val.value === "string") {
3091 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>";
3092 } else {
3093 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val.label + ": </label><select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3094
3095 if (QUnit.is("array", val.value)) {
3096 for (j = 0; j < val.value.length; j++) {
3097 escaped = escapeText(val.value[j]);
3098 urlConfigHtml += "<option value='" + escaped + "'" + (config[val.id] === val.value[j] ? (selection = true) && " selected='selected'" : "") + ">" + escaped + "</option>";
3099 }
3100 } else {
3101 for (j in val.value) {
3102 if (hasOwn.call(val.value, j)) {
3103 urlConfigHtml += "<option value='" + escapeText(j) + "'" + (config[val.id] === j ? (selection = true) && " selected='selected'" : "") + ">" + escapeText(val.value[j]) + "</option>";
3104 }
3105 }
3106 }
3107 if (config[val.id] && !selection) {
3108 escaped = escapeText(config[val.id]);
3109 urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3110 }
3111 urlConfigHtml += "</select>";
3112 }
3113 }
3114
3115 return urlConfigHtml;
3116 }
3117
3118 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3119 // Updates the URL with the new state of `config.urlConfig` values.
3120 function toolbarChanged() {
3121 var updatedUrl,
3122 value,
3123 tests,
3124 field = this,
3125 params = {};
3126
3127 // Detect if field is a select menu or a checkbox
3128 if ("selectedIndex" in field) {
3129 value = field.options[field.selectedIndex].value || undefined;
3130 } else {
3131 value = field.checked ? field.defaultValue || true : undefined;
3132 }
3133
3134 params[field.name] = value;
3135 updatedUrl = setUrl(params);
3136
3137 // Check if we can apply the change without a page refresh
3138 if ("hidepassed" === field.name && "replaceState" in window.history) {
3139 QUnit.urlParams[field.name] = value;
3140 config[field.name] = value || false;
3141 tests = id("qunit-tests");
3142 if (tests) {
3143 toggleClass(tests, "hidepass", value || false);
3144 }
3145 window.history.replaceState(null, "", updatedUrl);
3146 } else {
3147 window.location = updatedUrl;
3148 }
3149 }
3150
3151 function setUrl(params) {
3152 var key,
3153 arrValue,
3154 i,
3155 querystring = "?",
3156 location = window.location;
3157
3158 params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
3159
3160 for (key in params) {
3161
3162 // Skip inherited or undefined properties
3163 if (hasOwn.call(params, key) && params[key] !== undefined) {
3164
3165 // Output a parameter for each value of this key (but usually just one)
3166 arrValue = [].concat(params[key]);
3167 for (i = 0; i < arrValue.length; i++) {
3168 querystring += encodeURIComponent(key);
3169 if (arrValue[i] !== true) {
3170 querystring += "=" + encodeURIComponent(arrValue[i]);
3171 }
3172 querystring += "&";
3173 }
3174 }
3175 }
3176 return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
3177 }
3178
3179 function applyUrlParams() {
3180 var i,
3181 selectedModules = [],
3182 modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
3183 filter = id("qunit-filter-input").value;
3184
3185 for (i = 0; i < modulesList.length; i++) {
3186 if (modulesList[i].checked) {
3187 selectedModules.push(modulesList[i].value);
3188 }
3189 }
3190
3191 window.location = setUrl({
3192 filter: filter === "" ? undefined : filter,
3193 moduleId: selectedModules.length === 0 ? undefined : selectedModules,
3194
3195 // Remove module and testId filter
3196 module: undefined,
3197 testId: undefined
3198 });
3199 }
3200
3201 function toolbarUrlConfigContainer() {
3202 var urlConfigContainer = document$$1.createElement("span");
3203
3204 urlConfigContainer.innerHTML = getUrlConfigHtml();
3205 addClass(urlConfigContainer, "qunit-url-config");
3206
3207 addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
3208 addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
3209
3210 return urlConfigContainer;
3211 }
3212
3213 function abortTestsButton() {
3214 var button = document$$1.createElement("button");
3215 button.id = "qunit-abort-tests-button";
3216 button.innerHTML = "Abort";
3217 addEvent(button, "click", abortTests);
3218 return button;
3219 }
3220
3221 function toolbarLooseFilter() {
3222 var filter = document$$1.createElement("form"),
3223 label = document$$1.createElement("label"),
3224 input = document$$1.createElement("input"),
3225 button = document$$1.createElement("button");
3226
3227 addClass(filter, "qunit-filter");
3228
3229 label.innerHTML = "Filter: ";
3230
3231 input.type = "text";
3232 input.value = config.filter || "";
3233 input.name = "filter";
3234 input.id = "qunit-filter-input";
3235
3236 button.innerHTML = "Go";
3237
3238 label.appendChild(input);
3239
3240 filter.appendChild(label);
3241 filter.appendChild(document$$1.createTextNode(" "));
3242 filter.appendChild(button);
3243 addEvent(filter, "submit", interceptNavigation);
3244
3245 return filter;
3246 }
3247
3248 function moduleListHtml() {
3249 var i,
3250 checked,
3251 html = "";
3252
3253 for (i = 0; i < config.modules.length; i++) {
3254 if (config.modules[i].name !== "") {
3255 checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1;
3256 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>";
3257 }
3258 }
3259
3260 return html;
3261 }
3262
3263 function toolbarModuleFilter() {
3264 var allCheckbox,
3265 commit,
3266 reset,
3267 moduleFilter = document$$1.createElement("form"),
3268 label = document$$1.createElement("label"),
3269 moduleSearch = document$$1.createElement("input"),
3270 dropDown = document$$1.createElement("div"),
3271 actions = document$$1.createElement("span"),
3272 dropDownList = document$$1.createElement("ul"),
3273 dirty = false;
3274
3275 moduleSearch.id = "qunit-modulefilter-search";
3276 addEvent(moduleSearch, "input", searchInput);
3277 addEvent(moduleSearch, "input", searchFocus);
3278 addEvent(moduleSearch, "focus", searchFocus);
3279 addEvent(moduleSearch, "click", searchFocus);
3280
3281 label.id = "qunit-modulefilter-search-container";
3282 label.innerHTML = "Module: ";
3283 label.appendChild(moduleSearch);
3284
3285 actions.id = "qunit-modulefilter-actions";
3286 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>";
3287 allCheckbox = actions.lastChild.firstChild;
3288 commit = actions.firstChild;
3289 reset = commit.nextSibling;
3290 addEvent(commit, "click", applyUrlParams);
3291
3292 dropDownList.id = "qunit-modulefilter-dropdown-list";
3293 dropDownList.innerHTML = moduleListHtml();
3294
3295 dropDown.id = "qunit-modulefilter-dropdown";
3296 dropDown.style.display = "none";
3297 dropDown.appendChild(actions);
3298 dropDown.appendChild(dropDownList);
3299 addEvent(dropDown, "change", selectionChange);
3300 selectionChange();
3301
3302 moduleFilter.id = "qunit-modulefilter";
3303 moduleFilter.appendChild(label);
3304 moduleFilter.appendChild(dropDown);
3305 addEvent(moduleFilter, "submit", interceptNavigation);
3306 addEvent(moduleFilter, "reset", function () {
3307
3308 // Let the reset happen, then update styles
3309 window.setTimeout(selectionChange);
3310 });
3311
3312 // Enables show/hide for the dropdown
3313 function searchFocus() {
3314 if (dropDown.style.display !== "none") {
3315 return;
3316 }
3317
3318 dropDown.style.display = "block";
3319 addEvent(document$$1, "click", hideHandler);
3320 addEvent(document$$1, "keydown", hideHandler);
3321
3322 // Hide on Escape keydown or outside-container click
3323 function hideHandler(e) {
3324 var inContainer = moduleFilter.contains(e.target);
3325
3326 if (e.keyCode === 27 || !inContainer) {
3327 if (e.keyCode === 27 && inContainer) {
3328 moduleSearch.focus();
3329 }
3330 dropDown.style.display = "none";
3331 removeEvent(document$$1, "click", hideHandler);
3332 removeEvent(document$$1, "keydown", hideHandler);
3333 moduleSearch.value = "";
3334 searchInput();
3335 }
3336 }
3337 }
3338
3339 // Processes module search box input
3340 function searchInput() {
3341 var i,
3342 item,
3343 searchText = moduleSearch.value.toLowerCase(),
3344 listItems = dropDownList.children;
3345
3346 for (i = 0; i < listItems.length; i++) {
3347 item = listItems[i];
3348 if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) {
3349 item.style.display = "";
3350 } else {
3351 item.style.display = "none";
3352 }
3353 }
3354 }
3355
3356 // Processes selection changes
3357 function selectionChange(evt) {
3358 var i,
3359 item,
3360 checkbox = evt && evt.target || allCheckbox,
3361 modulesList = dropDownList.getElementsByTagName("input"),
3362 selectedNames = [];
3363
3364 toggleClass(checkbox.parentNode, "checked", checkbox.checked);
3365
3366 dirty = false;
3367 if (checkbox.checked && checkbox !== allCheckbox) {
3368 allCheckbox.checked = false;
3369 removeClass(allCheckbox.parentNode, "checked");
3370 }
3371 for (i = 0; i < modulesList.length; i++) {
3372 item = modulesList[i];
3373 if (!evt) {
3374 toggleClass(item.parentNode, "checked", item.checked);
3375 } else if (checkbox === allCheckbox && checkbox.checked) {
3376 item.checked = false;
3377 removeClass(item.parentNode, "checked");
3378 }
3379 dirty = dirty || item.checked !== item.defaultChecked;
3380 if (item.checked) {
3381 selectedNames.push(item.parentNode.textContent);
3382 }
3383 }
3384
3385 commit.style.display = reset.style.display = dirty ? "" : "none";
3386 moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent;
3387 moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent);
3388 }
3389
3390 return moduleFilter;
3391 }
3392
3393 function appendToolbar() {
3394 var toolbar = id("qunit-testrunner-toolbar");
3395
3396 if (toolbar) {
3397 toolbar.appendChild(toolbarUrlConfigContainer());
3398 toolbar.appendChild(toolbarModuleFilter());
3399 toolbar.appendChild(toolbarLooseFilter());
3400 toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
3401 }
3402 }
3403
3404 function appendHeader() {
3405 var header = id("qunit-header");
3406
3407 if (header) {
3408 header.innerHTML = "<a href='" + escapeText(unfilteredUrl) + "'>" + header.innerHTML + "</a> ";
3409 }
3410 }
3411
3412 function appendBanner() {
3413 var banner = id("qunit-banner");
3414
3415 if (banner) {
3416 banner.className = "";
3417 }
3418 }
3419
3420 function appendTestResults() {
3421 var tests = id("qunit-tests"),
3422 result = id("qunit-testresult"),
3423 controls;
3424
3425 if (result) {
3426 result.parentNode.removeChild(result);
3427 }
3428
3429 if (tests) {
3430 tests.innerHTML = "";
3431 result = document$$1.createElement("p");
3432 result.id = "qunit-testresult";
3433 result.className = "result";
3434 tests.parentNode.insertBefore(result, tests);
3435 result.innerHTML = "<div id=\"qunit-testresult-display\">Running...<br />&#160;</div>" + "<div id=\"qunit-testresult-controls\"></div>" + "<div class=\"clearfix\"></div>";
3436 controls = id("qunit-testresult-controls");
3437 }
3438
3439 if (controls) {
3440 controls.appendChild(abortTestsButton());
3441 }
3442 }
3443
3444 function appendFilteredTest() {
3445 var testId = QUnit.config.testId;
3446 if (!testId || testId.length <= 0) {
3447 return "";
3448 }
3449 return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId.join(", ")) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl) + "'>Run all tests</a></div>";
3450 }
3451
3452 function appendUserAgent() {
3453 var userAgent = id("qunit-userAgent");
3454
3455 if (userAgent) {
3456 userAgent.innerHTML = "";
3457 userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
3458 }
3459 }
3460
3461 function appendInterface() {
3462 var qunit = id("qunit");
3463
3464 if (qunit) {
3465 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>";
3466 }
3467
3468 appendHeader();
3469 appendBanner();
3470 appendTestResults();
3471 appendUserAgent();
3472 appendToolbar();
3473 }
3474
3475 function appendTestsList(modules) {
3476 var i, l, x, z, test, moduleObj;
3477
3478 for (i = 0, l = modules.length; i < l; i++) {
3479 moduleObj = modules[i];
3480
3481 for (x = 0, z = moduleObj.tests.length; x < z; x++) {
3482 test = moduleObj.tests[x];
3483
3484 appendTest(test.name, test.testId, moduleObj.name);
3485 }
3486 }
3487 }
3488
3489 function appendTest(name, testId, moduleName) {
3490 var title,
3491 rerunTrigger,
3492 testBlock,
3493 assertList,
3494 tests = id("qunit-tests");
3495
3496 if (!tests) {
3497 return;
3498 }
3499
3500 title = document$$1.createElement("strong");
3501 title.innerHTML = getNameHtml(name, moduleName);
3502
3503 rerunTrigger = document$$1.createElement("a");
3504 rerunTrigger.innerHTML = "Rerun";
3505 rerunTrigger.href = setUrl({ testId: testId });
3506
3507 testBlock = document$$1.createElement("li");
3508 testBlock.appendChild(title);
3509 testBlock.appendChild(rerunTrigger);
3510 testBlock.id = "qunit-test-output-" + testId;
3511
3512 assertList = document$$1.createElement("ol");
3513 assertList.className = "qunit-assert-list";
3514
3515 testBlock.appendChild(assertList);
3516
3517 tests.appendChild(testBlock);
3518 }
3519
3520 // HTML Reporter initialization and load
3521 QUnit.begin(function (details) {
3522 var i, moduleObj, tests;
3523
3524 // Sort modules by name for the picker
3525 for (i = 0; i < details.modules.length; i++) {
3526 moduleObj = details.modules[i];
3527 if (moduleObj.name) {
3528 modulesList.push(moduleObj.name);
3529 }
3530 }
3531 modulesList.sort(function (a, b) {
3532 return a.localeCompare(b);
3533 });
3534
3535 // Initialize QUnit elements
3536 appendInterface();
3537 appendTestsList(details.modules);
3538 tests = id("qunit-tests");
3539 if (tests && config.hidepassed) {
3540 addClass(tests, "hidepass");
3541 }
3542 });
3543
3544 QUnit.done(function (details) {
3545 var banner = id("qunit-banner"),
3546 tests = id("qunit-tests"),
3547 abortButton = id("qunit-abort-tests-button"),
3548 totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
3549 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(""),
3550 test,
3551 assertLi,
3552 assertList;
3553
3554 // Update remaing tests to aborted
3555 if (abortButton && abortButton.disabled) {
3556 html = "Tests aborted after " + details.runtime + " milliseconds.";
3557
3558 for (var i = 0; i < tests.children.length; i++) {
3559 test = tests.children[i];
3560 if (test.className === "" || test.className === "running") {
3561 test.className = "aborted";
3562 assertList = test.getElementsByTagName("ol")[0];
3563 assertLi = document$$1.createElement("li");
3564 assertLi.className = "fail";
3565 assertLi.innerHTML = "Test aborted.";
3566 assertList.appendChild(assertLi);
3567 }
3568 }
3569 }
3570
3571 if (banner && (!abortButton || abortButton.disabled === false)) {
3572 banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
3573 }
3574
3575 if (abortButton) {
3576 abortButton.parentNode.removeChild(abortButton);
3577 }
3578
3579 if (tests) {
3580 id("qunit-testresult-display").innerHTML = html;
3581 }
3582
3583 if (config.altertitle && document$$1.title) {
3584
3585 // Show ✖ for good, ✔ for bad suite result in title
3586 // use escape sequences in case file gets loaded with non-utf-8-charset
3587 document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
3588 }
3589
3590 // Scroll back to top to show results
3591 if (config.scrolltop && window.scrollTo) {
3592 window.scrollTo(0, 0);
3593 }
3594 });
3595
3596 function getNameHtml(name, module) {
3597 var nameHtml = "";
3598
3599 if (module) {
3600 nameHtml = "<span class='module-name'>" + escapeText(module) + "</span>: ";
3601 }
3602
3603 nameHtml += "<span class='test-name'>" + escapeText(name) + "</span>";
3604
3605 return nameHtml;
3606 }
3607
3608 QUnit.testStart(function (details) {
3609 var running, testBlock, bad;
3610
3611 testBlock = id("qunit-test-output-" + details.testId);
3612 if (testBlock) {
3613 testBlock.className = "running";
3614 } else {
3615
3616 // Report later registered tests
3617 appendTest(details.name, details.testId, details.module);
3618 }
3619
3620 running = id("qunit-testresult-display");
3621 if (running) {
3622 bad = QUnit.config.reorder && details.previousFailure;
3623
3624 running.innerHTML = (bad ? "Rerunning previously failed test: <br />" : "Running: <br />") + getNameHtml(details.name, details.module);
3625 }
3626 });
3627
3628 function stripHtml(string) {
3629
3630 // Strip tags, html entity and whitespaces
3631 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
3632 }
3633
3634 QUnit.log(function (details) {
3635 var assertList,
3636 assertLi,
3637 message,
3638 expected,
3639 actual,
3640 diff,
3641 showDiff = false,
3642 testItem = id("qunit-test-output-" + details.testId);
3643
3644 if (!testItem) {
3645 return;
3646 }
3647
3648 message = escapeText(details.message) || (details.result ? "okay" : "failed");
3649 message = "<span class='test-message'>" + message + "</span>";
3650 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
3651
3652 // The pushFailure doesn't provide details.expected
3653 // when it calls, it's implicit to also not show expected and diff stuff
3654 // Also, we need to check details.expected existence, as it can exist and be undefined
3655 if (!details.result && hasOwn.call(details, "expected")) {
3656 if (details.negative) {
3657 expected = "NOT " + QUnit.dump.parse(details.expected);
3658 } else {
3659 expected = QUnit.dump.parse(details.expected);
3660 }
3661
3662 actual = QUnit.dump.parse(details.actual);
3663 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected) + "</pre></td></tr>";
3664
3665 if (actual !== expected) {
3666
3667 message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
3668
3669 if (typeof details.actual === "number" && typeof details.expected === "number") {
3670 if (!isNaN(details.actual) && !isNaN(details.expected)) {
3671 showDiff = true;
3672 diff = details.actual - details.expected;
3673 diff = (diff > 0 ? "+" : "") + diff;
3674 }
3675 } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
3676 diff = QUnit.diff(expected, actual);
3677
3678 // don't show diff if there is zero overlap
3679 showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
3680 }
3681
3682 if (showDiff) {
3683 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
3684 }
3685 } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) {
3686 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>";
3687 } else {
3688 message += "<tr class='test-message'><th>Message: </th><td>" + "Diff suppressed as the expected and actual results have an equivalent" + " serialization</td></tr>";
3689 }
3690
3691 if (details.source) {
3692 message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>";
3693 }
3694
3695 message += "</table>";
3696
3697 // This occurs when pushFailure is set and we have an extracted stack trace
3698 } else if (!details.result && details.source) {
3699 message += "<table>" + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + "</pre></td></tr>" + "</table>";
3700 }
3701
3702 assertList = testItem.getElementsByTagName("ol")[0];
3703
3704 assertLi = document$$1.createElement("li");
3705 assertLi.className = details.result ? "pass" : "fail";
3706 assertLi.innerHTML = message;
3707 assertList.appendChild(assertLi);
3708 });
3709
3710 QUnit.testDone(function (details) {
3711 var testTitle,
3712 time,
3713 testItem,
3714 assertList,
3715 good,
3716 bad,
3717 testCounts,
3718 skipped,
3719 sourceName,
3720 tests = id("qunit-tests");
3721
3722 if (!tests) {
3723 return;
3724 }
3725
3726 testItem = id("qunit-test-output-" + details.testId);
3727
3728 assertList = testItem.getElementsByTagName("ol")[0];
3729
3730 good = details.passed;
3731 bad = details.failed;
3732
3733 // This test passed if it has no unexpected failed assertions
3734 var testPassed = details.failed > 0 ? details.todo : !details.todo;
3735
3736 if (testPassed) {
3737
3738 // Collapse the passing tests
3739 addClass(assertList, "qunit-collapsed");
3740 } else if (config.collapse) {
3741 if (!collapseNext) {
3742
3743 // Skip collapsing the first failing test
3744 collapseNext = true;
3745 } else {
3746
3747 // Collapse remaining tests
3748 addClass(assertList, "qunit-collapsed");
3749 }
3750 }
3751
3752 // The testItem.firstChild is the test name
3753 testTitle = testItem.firstChild;
3754
3755 testCounts = bad ? "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " : "";
3756
3757 testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
3758
3759 if (details.skipped) {
3760 stats.skippedTests++;
3761
3762 testItem.className = "skipped";
3763 skipped = document$$1.createElement("em");
3764 skipped.className = "qunit-skipped-label";
3765 skipped.innerHTML = "skipped";
3766 testItem.insertBefore(skipped, testTitle);
3767 } else {
3768 addEvent(testTitle, "click", function () {
3769 toggleClass(assertList, "qunit-collapsed");
3770 });
3771
3772 testItem.className = testPassed ? "pass" : "fail";
3773
3774 if (details.todo) {
3775 var todoLabel = document$$1.createElement("em");
3776 todoLabel.className = "qunit-todo-label";
3777 todoLabel.innerHTML = "todo";
3778 testItem.className += " todo";
3779 testItem.insertBefore(todoLabel, testTitle);
3780 }
3781
3782 time = document$$1.createElement("span");
3783 time.className = "runtime";
3784 time.innerHTML = details.runtime + " ms";
3785 testItem.insertBefore(time, assertList);
3786
3787 if (!testPassed) {
3788 stats.failedTests++;
3789 } else if (details.todo) {
3790 stats.todoTests++;
3791 } else {
3792 stats.passedTests++;
3793 }
3794 }
3795
3796 // Show the source of the test when showing assertions
3797 if (details.source) {
3798 sourceName = document$$1.createElement("p");
3799 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
3800 addClass(sourceName, "qunit-source");
3801 if (testPassed) {
3802 addClass(sourceName, "qunit-collapsed");
3803 }
3804 addEvent(testTitle, "click", function () {
3805 toggleClass(sourceName, "qunit-collapsed");
3806 });
3807 testItem.appendChild(sourceName);
3808 }
3809 });
3810
3811 // Avoid readyState issue with phantomjs
3812 // Ref: #818
3813 var notPhantom = function (p) {
3814 return !(p && p.version && p.version.major > 0);
3815 }(window.phantom);
3816
3817 if (notPhantom && document$$1.readyState === "complete") {
3818 QUnit.load();
3819 } else {
3820 addEvent(window, "load", QUnit.load);
3821 }
3822
3823 // Wrap window.onerror. We will call the original window.onerror to see if
3824 // the existing handler fully handles the error; if not, we will call the
3825 // QUnit.onError function.
3826 var originalWindowOnError = window.onerror;
3827
3828 // Cover uncaught exceptions
3829 // Returning true will suppress the default browser handler,
3830 // returning false will let it run.
3831 window.onerror = function (message, fileName, lineNumber) {
3832 var ret = false;
3833 if (originalWindowOnError) {
3834 for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
3835 args[_key - 3] = arguments[_key];
3836 }
3837
3838 ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
3839 }
3840
3841 // Treat return value as window.onerror itself does,
3842 // Only do our handling if not suppressed.
3843 if (ret !== true) {
3844 var error = {
3845 message: message,
3846 fileName: fileName,
3847 lineNumber: lineNumber
3848 };
3849
3850 ret = QUnit.onError(error);
3851 }
3852
3853 return ret;
3854 };
3855 })();
3856
3857 /*
3858 * This file is a modified version of google-diff-match-patch's JavaScript implementation
3859 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
3860 * modifications are licensed as more fully set forth in LICENSE.txt.
3861 *
3862 * The original source of google-diff-match-patch is attributable and licensed as follows:
3863 *
3864 * Copyright 2006 Google Inc.
3865 * https://code.google.com/p/google-diff-match-patch/
3866 *
3867 * Licensed under the Apache License, Version 2.0 (the "License");
3868 * you may not use this file except in compliance with the License.
3869 * You may obtain a copy of the License at
3870 *
3871 * https://www.apache.org/licenses/LICENSE-2.0
3872 *
3873 * Unless required by applicable law or agreed to in writing, software
3874 * distributed under the License is distributed on an "AS IS" BASIS,
3875 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3876 * See the License for the specific language governing permissions and
3877 * limitations under the License.
3878 *
3879 * More Info:
3880 * https://code.google.com/p/google-diff-match-patch/
3881 *
3882 * Usage: QUnit.diff(expected, actual)
3883 *
3884 */
3885 QUnit.diff = function () {
3886 function DiffMatchPatch() {}
3887
3888 // DIFF FUNCTIONS
3889
3890 /**
3891 * The data structure representing a diff is an array of tuples:
3892 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
3893 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
3894 */
3895 var DIFF_DELETE = -1,
3896 DIFF_INSERT = 1,
3897 DIFF_EQUAL = 0;
3898
3899 /**
3900 * Find the differences between two texts. Simplifies the problem by stripping
3901 * any common prefix or suffix off the texts before diffing.
3902 * @param {string} text1 Old string to be diffed.
3903 * @param {string} text2 New string to be diffed.
3904 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
3905 * then don't run a line-level diff first to identify the changed areas.
3906 * Defaults to true, which does a faster, slightly less optimal diff.
3907 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
3908 */
3909 DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
3910 var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs;
3911
3912 // The diff must be complete in up to 1 second.
3913 deadline = new Date().getTime() + 1000;
3914
3915 // Check for null inputs.
3916 if (text1 === null || text2 === null) {
3917 throw new Error("Null input. (DiffMain)");
3918 }
3919
3920 // Check for equality (speedup).
3921 if (text1 === text2) {
3922 if (text1) {
3923 return [[DIFF_EQUAL, text1]];
3924 }
3925 return [];
3926 }
3927
3928 if (typeof optChecklines === "undefined") {
3929 optChecklines = true;
3930 }
3931
3932 checklines = optChecklines;
3933
3934 // Trim off common prefix (speedup).
3935 commonlength = this.diffCommonPrefix(text1, text2);
3936 commonprefix = text1.substring(0, commonlength);
3937 text1 = text1.substring(commonlength);
3938 text2 = text2.substring(commonlength);
3939
3940 // Trim off common suffix (speedup).
3941 commonlength = this.diffCommonSuffix(text1, text2);
3942 commonsuffix = text1.substring(text1.length - commonlength);
3943 text1 = text1.substring(0, text1.length - commonlength);
3944 text2 = text2.substring(0, text2.length - commonlength);
3945
3946 // Compute the diff on the middle block.
3947 diffs = this.diffCompute(text1, text2, checklines, deadline);
3948
3949 // Restore the prefix and suffix.
3950 if (commonprefix) {
3951 diffs.unshift([DIFF_EQUAL, commonprefix]);
3952 }
3953 if (commonsuffix) {
3954 diffs.push([DIFF_EQUAL, commonsuffix]);
3955 }
3956 this.diffCleanupMerge(diffs);
3957 return diffs;
3958 };
3959
3960 /**
3961 * Reduce the number of edits by eliminating operationally trivial equalities.
3962 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3963 */
3964 DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
3965 var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
3966 changes = false;
3967 equalities = []; // Stack of indices where equalities are found.
3968 equalitiesLength = 0; // Keeping our own length var is faster in JS.
3969 /** @type {?string} */
3970 lastequality = null;
3971
3972 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
3973 pointer = 0; // Index of current position.
3974
3975 // Is there an insertion operation before the last equality.
3976 preIns = false;
3977
3978 // Is there a deletion operation before the last equality.
3979 preDel = false;
3980
3981 // Is there an insertion operation after the last equality.
3982 postIns = false;
3983
3984 // Is there a deletion operation after the last equality.
3985 postDel = false;
3986 while (pointer < diffs.length) {
3987
3988 // Equality found.
3989 if (diffs[pointer][0] === DIFF_EQUAL) {
3990 if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
3991
3992 // Candidate found.
3993 equalities[equalitiesLength++] = pointer;
3994 preIns = postIns;
3995 preDel = postDel;
3996 lastequality = diffs[pointer][1];
3997 } else {
3998
3999 // Not a candidate, and can never become one.
4000 equalitiesLength = 0;
4001 lastequality = null;
4002 }
4003 postIns = postDel = false;
4004
4005 // An insertion or deletion.
4006 } else {
4007
4008 if (diffs[pointer][0] === DIFF_DELETE) {
4009 postDel = true;
4010 } else {
4011 postIns = true;
4012 }
4013
4014 /*
4015 * Five types to be split:
4016 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
4017 * <ins>A</ins>X<ins>C</ins><del>D</del>
4018 * <ins>A</ins><del>B</del>X<ins>C</ins>
4019 * <ins>A</del>X<ins>C</ins><del>D</del>
4020 * <ins>A</ins><del>B</del>X<del>C</del>
4021 */
4022 if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
4023
4024 // Duplicate record.
4025 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4026
4027 // Change second copy to insert.
4028 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4029 equalitiesLength--; // Throw away the equality we just deleted;
4030 lastequality = null;
4031 if (preIns && preDel) {
4032
4033 // No changes made which could affect previous entry, keep going.
4034 postIns = postDel = true;
4035 equalitiesLength = 0;
4036 } else {
4037 equalitiesLength--; // Throw away the previous equality.
4038 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4039 postIns = postDel = false;
4040 }
4041 changes = true;
4042 }
4043 }
4044 pointer++;
4045 }
4046
4047 if (changes) {
4048 this.diffCleanupMerge(diffs);
4049 }
4050 };
4051
4052 /**
4053 * Convert a diff array into a pretty HTML report.
4054 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4055 * @param {integer} string to be beautified.
4056 * @return {string} HTML representation.
4057 */
4058 DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
4059 var op,
4060 data,
4061 x,
4062 html = [];
4063 for (x = 0; x < diffs.length; x++) {
4064 op = diffs[x][0]; // Operation (insert, delete, equal)
4065 data = diffs[x][1]; // Text of change.
4066 switch (op) {
4067 case DIFF_INSERT:
4068 html[x] = "<ins>" + escapeText(data) + "</ins>";
4069 break;
4070 case DIFF_DELETE:
4071 html[x] = "<del>" + escapeText(data) + "</del>";
4072 break;
4073 case DIFF_EQUAL:
4074 html[x] = "<span>" + escapeText(data) + "</span>";
4075 break;
4076 }
4077 }
4078 return html.join("");
4079 };
4080
4081 /**
4082 * Determine the common prefix of two strings.
4083 * @param {string} text1 First string.
4084 * @param {string} text2 Second string.
4085 * @return {number} The number of characters common to the start of each
4086 * string.
4087 */
4088 DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
4089 var pointermid, pointermax, pointermin, pointerstart;
4090
4091 // Quick check for common null cases.
4092 if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
4093 return 0;
4094 }
4095
4096 // Binary search.
4097 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4098 pointermin = 0;
4099 pointermax = Math.min(text1.length, text2.length);
4100 pointermid = pointermax;
4101 pointerstart = 0;
4102 while (pointermin < pointermid) {
4103 if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
4104 pointermin = pointermid;
4105 pointerstart = pointermin;
4106 } else {
4107 pointermax = pointermid;
4108 }
4109 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4110 }
4111 return pointermid;
4112 };
4113
4114 /**
4115 * Determine the common suffix of two strings.
4116 * @param {string} text1 First string.
4117 * @param {string} text2 Second string.
4118 * @return {number} The number of characters common to the end of each string.
4119 */
4120 DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
4121 var pointermid, pointermax, pointermin, pointerend;
4122
4123 // Quick check for common null cases.
4124 if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
4125 return 0;
4126 }
4127
4128 // Binary search.
4129 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
4130 pointermin = 0;
4131 pointermax = Math.min(text1.length, text2.length);
4132 pointermid = pointermax;
4133 pointerend = 0;
4134 while (pointermin < pointermid) {
4135 if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) {
4136 pointermin = pointermid;
4137 pointerend = pointermin;
4138 } else {
4139 pointermax = pointermid;
4140 }
4141 pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4142 }
4143 return pointermid;
4144 };
4145
4146 /**
4147 * Find the differences between two texts. Assumes that the texts do not
4148 * have any common prefix or suffix.
4149 * @param {string} text1 Old string to be diffed.
4150 * @param {string} text2 New string to be diffed.
4151 * @param {boolean} checklines Speedup flag. If false, then don't run a
4152 * line-level diff first to identify the changed areas.
4153 * If true, then run a faster, slightly less optimal diff.
4154 * @param {number} deadline Time when the diff should be complete by.
4155 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4156 * @private
4157 */
4158 DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
4159 var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
4160
4161 if (!text1) {
4162
4163 // Just add some text (speedup).
4164 return [[DIFF_INSERT, text2]];
4165 }
4166
4167 if (!text2) {
4168
4169 // Just delete some text (speedup).
4170 return [[DIFF_DELETE, text1]];
4171 }
4172
4173 longtext = text1.length > text2.length ? text1 : text2;
4174 shorttext = text1.length > text2.length ? text2 : text1;
4175 i = longtext.indexOf(shorttext);
4176 if (i !== -1) {
4177
4178 // Shorter text is inside the longer text (speedup).
4179 diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
4180
4181 // Swap insertions for deletions if diff is reversed.
4182 if (text1.length > text2.length) {
4183 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
4184 }
4185 return diffs;
4186 }
4187
4188 if (shorttext.length === 1) {
4189
4190 // Single character string.
4191 // After the previous speedup, the character can't be an equality.
4192 return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4193 }
4194
4195 // Check to see if the problem can be split in two.
4196 hm = this.diffHalfMatch(text1, text2);
4197 if (hm) {
4198
4199 // A half-match was found, sort out the return data.
4200 text1A = hm[0];
4201 text1B = hm[1];
4202 text2A = hm[2];
4203 text2B = hm[3];
4204 midCommon = hm[4];
4205
4206 // Send both pairs off for separate processing.
4207 diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
4208 diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
4209
4210 // Merge the results.
4211 return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
4212 }
4213
4214 if (checklines && text1.length > 100 && text2.length > 100) {
4215 return this.diffLineMode(text1, text2, deadline);
4216 }
4217
4218 return this.diffBisect(text1, text2, deadline);
4219 };
4220
4221 /**
4222 * Do the two texts share a substring which is at least half the length of the
4223 * longer text?
4224 * This speedup can produce non-minimal diffs.
4225 * @param {string} text1 First string.
4226 * @param {string} text2 Second string.
4227 * @return {Array.<string>} Five element Array, containing the prefix of
4228 * text1, the suffix of text1, the prefix of text2, the suffix of
4229 * text2 and the common middle. Or null if there was no match.
4230 * @private
4231 */
4232 DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
4233 var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
4234
4235 longtext = text1.length > text2.length ? text1 : text2;
4236 shorttext = text1.length > text2.length ? text2 : text1;
4237 if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
4238 return null; // Pointless.
4239 }
4240 dmp = this; // 'this' becomes 'window' in a closure.
4241
4242 /**
4243 * Does a substring of shorttext exist within longtext such that the substring
4244 * is at least half the length of longtext?
4245 * Closure, but does not reference any external variables.
4246 * @param {string} longtext Longer string.
4247 * @param {string} shorttext Shorter string.
4248 * @param {number} i Start index of quarter length substring within longtext.
4249 * @return {Array.<string>} Five element Array, containing the prefix of
4250 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
4251 * of shorttext and the common middle. Or null if there was no match.
4252 * @private
4253 */
4254 function diffHalfMatchI(longtext, shorttext, i) {
4255 var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
4256
4257 // Start with a 1/4 length substring at position i as a seed.
4258 seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
4259 j = -1;
4260 bestCommon = "";
4261 while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
4262 prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j));
4263 suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
4264 if (bestCommon.length < suffixLength + prefixLength) {
4265 bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
4266 bestLongtextA = longtext.substring(0, i - suffixLength);
4267 bestLongtextB = longtext.substring(i + prefixLength);
4268 bestShorttextA = shorttext.substring(0, j - suffixLength);
4269 bestShorttextB = shorttext.substring(j + prefixLength);
4270 }
4271 }
4272 if (bestCommon.length * 2 >= longtext.length) {
4273 return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
4274 } else {
4275 return null;
4276 }
4277 }
4278
4279 // First check if the second quarter is the seed for a half-match.
4280 hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
4281
4282 // Check again based on the third quarter.
4283 hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
4284 if (!hm1 && !hm2) {
4285 return null;
4286 } else if (!hm2) {
4287 hm = hm1;
4288 } else if (!hm1) {
4289 hm = hm2;
4290 } else {
4291
4292 // Both matched. Select the longest.
4293 hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
4294 }
4295
4296 // A half-match was found, sort out the return data.
4297 if (text1.length > text2.length) {
4298 text1A = hm[0];
4299 text1B = hm[1];
4300 text2A = hm[2];
4301 text2B = hm[3];
4302 } else {
4303 text2A = hm[0];
4304 text2B = hm[1];
4305 text1A = hm[2];
4306 text1B = hm[3];
4307 }
4308 midCommon = hm[4];
4309 return [text1A, text1B, text2A, text2B, midCommon];
4310 };
4311
4312 /**
4313 * Do a quick line-level diff on both strings, then rediff the parts for
4314 * greater accuracy.
4315 * This speedup can produce non-minimal diffs.
4316 * @param {string} text1 Old string to be diffed.
4317 * @param {string} text2 New string to be diffed.
4318 * @param {number} deadline Time when the diff should be complete by.
4319 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4320 * @private
4321 */
4322 DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
4323 var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
4324
4325 // Scan the text on a line-by-line basis first.
4326 a = this.diffLinesToChars(text1, text2);
4327 text1 = a.chars1;
4328 text2 = a.chars2;
4329 linearray = a.lineArray;
4330
4331 diffs = this.DiffMain(text1, text2, false, deadline);
4332
4333 // Convert the diff back to original text.
4334 this.diffCharsToLines(diffs, linearray);
4335
4336 // Eliminate freak matches (e.g. blank lines)
4337 this.diffCleanupSemantic(diffs);
4338
4339 // Rediff any replacement blocks, this time character-by-character.
4340 // Add a dummy entry at the end.
4341 diffs.push([DIFF_EQUAL, ""]);
4342 pointer = 0;
4343 countDelete = 0;
4344 countInsert = 0;
4345 textDelete = "";
4346 textInsert = "";
4347 while (pointer < diffs.length) {
4348 switch (diffs[pointer][0]) {
4349 case DIFF_INSERT:
4350 countInsert++;
4351 textInsert += diffs[pointer][1];
4352 break;
4353 case DIFF_DELETE:
4354 countDelete++;
4355 textDelete += diffs[pointer][1];
4356 break;
4357 case DIFF_EQUAL:
4358
4359 // Upon reaching an equality, check for prior redundancies.
4360 if (countDelete >= 1 && countInsert >= 1) {
4361
4362 // Delete the offending records and add the merged ones.
4363 diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert);
4364 pointer = pointer - countDelete - countInsert;
4365 a = this.DiffMain(textDelete, textInsert, false, deadline);
4366 for (j = a.length - 1; j >= 0; j--) {
4367 diffs.splice(pointer, 0, a[j]);
4368 }
4369 pointer = pointer + a.length;
4370 }
4371 countInsert = 0;
4372 countDelete = 0;
4373 textDelete = "";
4374 textInsert = "";
4375 break;
4376 }
4377 pointer++;
4378 }
4379 diffs.pop(); // Remove the dummy entry at the end.
4380
4381 return diffs;
4382 };
4383
4384 /**
4385 * Find the 'middle snake' of a diff, split the problem in two
4386 * and return the recursively constructed diff.
4387 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
4388 * @param {string} text1 Old string to be diffed.
4389 * @param {string} text2 New string to be diffed.
4390 * @param {number} deadline Time at which to bail if not yet complete.
4391 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4392 * @private
4393 */
4394 DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) {
4395 var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
4396
4397 // Cache the text lengths to prevent multiple calls.
4398 text1Length = text1.length;
4399 text2Length = text2.length;
4400 maxD = Math.ceil((text1Length + text2Length) / 2);
4401 vOffset = maxD;
4402 vLength = 2 * maxD;
4403 v1 = new Array(vLength);
4404 v2 = new Array(vLength);
4405
4406 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
4407 // integers and undefined.
4408 for (x = 0; x < vLength; x++) {
4409 v1[x] = -1;
4410 v2[x] = -1;
4411 }
4412 v1[vOffset + 1] = 0;
4413 v2[vOffset + 1] = 0;
4414 delta = text1Length - text2Length;
4415
4416 // If the total number of characters is odd, then the front path will collide
4417 // with the reverse path.
4418 front = delta % 2 !== 0;
4419
4420 // Offsets for start and end of k loop.
4421 // Prevents mapping of space beyond the grid.
4422 k1start = 0;
4423 k1end = 0;
4424 k2start = 0;
4425 k2end = 0;
4426 for (d = 0; d < maxD; d++) {
4427
4428 // Bail out if deadline is reached.
4429 if (new Date().getTime() > deadline) {
4430 break;
4431 }
4432
4433 // Walk the front path one step.
4434 for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
4435 k1Offset = vOffset + k1;
4436 if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) {
4437 x1 = v1[k1Offset + 1];
4438 } else {
4439 x1 = v1[k1Offset - 1] + 1;
4440 }
4441 y1 = x1 - k1;
4442 while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
4443 x1++;
4444 y1++;
4445 }
4446 v1[k1Offset] = x1;
4447 if (x1 > text1Length) {
4448
4449 // Ran off the right of the graph.
4450 k1end += 2;
4451 } else if (y1 > text2Length) {
4452
4453 // Ran off the bottom of the graph.
4454 k1start += 2;
4455 } else if (front) {
4456 k2Offset = vOffset + delta - k1;
4457 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
4458
4459 // Mirror x2 onto top-left coordinate system.
4460 x2 = text1Length - v2[k2Offset];
4461 if (x1 >= x2) {
4462
4463 // Overlap detected.
4464 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4465 }
4466 }
4467 }
4468 }
4469
4470 // Walk the reverse path one step.
4471 for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
4472 k2Offset = vOffset + k2;
4473 if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) {
4474 x2 = v2[k2Offset + 1];
4475 } else {
4476 x2 = v2[k2Offset - 1] + 1;
4477 }
4478 y2 = x2 - k2;
4479 while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
4480 x2++;
4481 y2++;
4482 }
4483 v2[k2Offset] = x2;
4484 if (x2 > text1Length) {
4485
4486 // Ran off the left of the graph.
4487 k2end += 2;
4488 } else if (y2 > text2Length) {
4489
4490 // Ran off the top of the graph.
4491 k2start += 2;
4492 } else if (!front) {
4493 k1Offset = vOffset + delta - k2;
4494 if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
4495 x1 = v1[k1Offset];
4496 y1 = vOffset + x1 - k1Offset;
4497
4498 // Mirror x2 onto top-left coordinate system.
4499 x2 = text1Length - x2;
4500 if (x1 >= x2) {
4501
4502 // Overlap detected.
4503 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4504 }
4505 }
4506 }
4507 }
4508 }
4509
4510 // Diff took too long and hit the deadline or
4511 // number of diffs equals number of characters, no commonality at all.
4512 return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4513 };
4514
4515 /**
4516 * Given the location of the 'middle snake', split the diff in two parts
4517 * and recurse.
4518 * @param {string} text1 Old string to be diffed.
4519 * @param {string} text2 New string to be diffed.
4520 * @param {number} x Index of split point in text1.
4521 * @param {number} y Index of split point in text2.
4522 * @param {number} deadline Time at which to bail if not yet complete.
4523 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4524 * @private
4525 */
4526 DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) {
4527 var text1a, text1b, text2a, text2b, diffs, diffsb;
4528 text1a = text1.substring(0, x);
4529 text2a = text2.substring(0, y);
4530 text1b = text1.substring(x);
4531 text2b = text2.substring(y);
4532
4533 // Compute both diffs serially.
4534 diffs = this.DiffMain(text1a, text2a, false, deadline);
4535 diffsb = this.DiffMain(text1b, text2b, false, deadline);
4536
4537 return diffs.concat(diffsb);
4538 };
4539
4540 /**
4541 * Reduce the number of edits by eliminating semantically trivial equalities.
4542 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4543 */
4544 DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
4545 var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
4546 changes = false;
4547 equalities = []; // Stack of indices where equalities are found.
4548 equalitiesLength = 0; // Keeping our own length var is faster in JS.
4549 /** @type {?string} */
4550 lastequality = null;
4551
4552 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
4553 pointer = 0; // Index of current position.
4554
4555 // Number of characters that changed prior to the equality.
4556 lengthInsertions1 = 0;
4557 lengthDeletions1 = 0;
4558
4559 // Number of characters that changed after the equality.
4560 lengthInsertions2 = 0;
4561 lengthDeletions2 = 0;
4562 while (pointer < diffs.length) {
4563 if (diffs[pointer][0] === DIFF_EQUAL) {
4564 // Equality found.
4565 equalities[equalitiesLength++] = pointer;
4566 lengthInsertions1 = lengthInsertions2;
4567 lengthDeletions1 = lengthDeletions2;
4568 lengthInsertions2 = 0;
4569 lengthDeletions2 = 0;
4570 lastequality = diffs[pointer][1];
4571 } else {
4572 // An insertion or deletion.
4573 if (diffs[pointer][0] === DIFF_INSERT) {
4574 lengthInsertions2 += diffs[pointer][1].length;
4575 } else {
4576 lengthDeletions2 += diffs[pointer][1].length;
4577 }
4578
4579 // Eliminate an equality that is smaller or equal to the edits on both
4580 // sides of it.
4581 if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
4582
4583 // Duplicate record.
4584 diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4585
4586 // Change second copy to insert.
4587 diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4588
4589 // Throw away the equality we just deleted.
4590 equalitiesLength--;
4591
4592 // Throw away the previous equality (it needs to be reevaluated).
4593 equalitiesLength--;
4594 pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4595
4596 // Reset the counters.
4597 lengthInsertions1 = 0;
4598 lengthDeletions1 = 0;
4599 lengthInsertions2 = 0;
4600 lengthDeletions2 = 0;
4601 lastequality = null;
4602 changes = true;
4603 }
4604 }
4605 pointer++;
4606 }
4607
4608 // Normalize the diff.
4609 if (changes) {
4610 this.diffCleanupMerge(diffs);
4611 }
4612
4613 // Find any overlaps between deletions and insertions.
4614 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
4615 // -> <del>abc</del>xxx<ins>def</ins>
4616 // e.g: <del>xxxabc</del><ins>defxxx</ins>
4617 // -> <ins>def</ins>xxx<del>abc</del>
4618 // Only extract an overlap if it is as big as the edit ahead or behind it.
4619 pointer = 1;
4620 while (pointer < diffs.length) {
4621 if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
4622 deletion = diffs[pointer - 1][1];
4623 insertion = diffs[pointer][1];
4624 overlapLength1 = this.diffCommonOverlap(deletion, insertion);
4625 overlapLength2 = this.diffCommonOverlap(insertion, deletion);
4626 if (overlapLength1 >= overlapLength2) {
4627 if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) {
4628
4629 // Overlap found. Insert an equality and trim the surrounding edits.
4630 diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]);
4631 diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1);
4632 diffs[pointer + 1][1] = insertion.substring(overlapLength1);
4633 pointer++;
4634 }
4635 } else {
4636 if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
4637
4638 // Reverse overlap found.
4639 // Insert an equality and swap and trim the surrounding edits.
4640 diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]);
4641
4642 diffs[pointer - 1][0] = DIFF_INSERT;
4643 diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2);
4644 diffs[pointer + 1][0] = DIFF_DELETE;
4645 diffs[pointer + 1][1] = deletion.substring(overlapLength2);
4646 pointer++;
4647 }
4648 }
4649 pointer++;
4650 }
4651 pointer++;
4652 }
4653 };
4654
4655 /**
4656 * Determine if the suffix of one string is the prefix of another.
4657 * @param {string} text1 First string.
4658 * @param {string} text2 Second string.
4659 * @return {number} The number of characters common to the end of the first
4660 * string and the start of the second string.
4661 * @private
4662 */
4663 DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
4664 var text1Length, text2Length, textLength, best, length, pattern, found;
4665
4666 // Cache the text lengths to prevent multiple calls.
4667 text1Length = text1.length;
4668 text2Length = text2.length;
4669
4670 // Eliminate the null case.
4671 if (text1Length === 0 || text2Length === 0) {
4672 return 0;
4673 }
4674
4675 // Truncate the longer string.
4676 if (text1Length > text2Length) {
4677 text1 = text1.substring(text1Length - text2Length);
4678 } else if (text1Length < text2Length) {
4679 text2 = text2.substring(0, text1Length);
4680 }
4681 textLength = Math.min(text1Length, text2Length);
4682
4683 // Quick check for the worst case.
4684 if (text1 === text2) {
4685 return textLength;
4686 }
4687
4688 // Start by looking for a single character match
4689 // and increase length until no match is found.
4690 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
4691 best = 0;
4692 length = 1;
4693 while (true) {
4694 pattern = text1.substring(textLength - length);
4695 found = text2.indexOf(pattern);
4696 if (found === -1) {
4697 return best;
4698 }
4699 length += found;
4700 if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
4701 best = length;
4702 length++;
4703 }
4704 }
4705 };
4706
4707 /**
4708 * Split two texts into an array of strings. Reduce the texts to a string of
4709 * hashes where each Unicode character represents one line.
4710 * @param {string} text1 First string.
4711 * @param {string} text2 Second string.
4712 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
4713 * An object containing the encoded text1, the encoded text2 and
4714 * the array of unique strings.
4715 * The zeroth element of the array of unique strings is intentionally blank.
4716 * @private
4717 */
4718 DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) {
4719 var lineArray, lineHash, chars1, chars2;
4720 lineArray = []; // E.g. lineArray[4] === 'Hello\n'
4721 lineHash = {}; // E.g. lineHash['Hello\n'] === 4
4722
4723 // '\x00' is a valid character, but various debuggers don't like it.
4724 // So we'll insert a junk entry to avoid generating a null character.
4725 lineArray[0] = "";
4726
4727 /**
4728 * Split a text into an array of strings. Reduce the texts to a string of
4729 * hashes where each Unicode character represents one line.
4730 * Modifies linearray and linehash through being a closure.
4731 * @param {string} text String to encode.
4732 * @return {string} Encoded string.
4733 * @private
4734 */
4735 function diffLinesToCharsMunge(text) {
4736 var chars, lineStart, lineEnd, lineArrayLength, line;
4737 chars = "";
4738
4739 // Walk the text, pulling out a substring for each line.
4740 // text.split('\n') would would temporarily double our memory footprint.
4741 // Modifying text would create many large strings to garbage collect.
4742 lineStart = 0;
4743 lineEnd = -1;
4744
4745 // Keeping our own length variable is faster than looking it up.
4746 lineArrayLength = lineArray.length;
4747 while (lineEnd < text.length - 1) {
4748 lineEnd = text.indexOf("\n", lineStart);
4749 if (lineEnd === -1) {
4750 lineEnd = text.length - 1;
4751 }
4752 line = text.substring(lineStart, lineEnd + 1);
4753 lineStart = lineEnd + 1;
4754
4755 if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined) {
4756 chars += String.fromCharCode(lineHash[line]);
4757 } else {
4758 chars += String.fromCharCode(lineArrayLength);
4759 lineHash[line] = lineArrayLength;
4760 lineArray[lineArrayLength++] = line;
4761 }
4762 }
4763 return chars;
4764 }
4765
4766 chars1 = diffLinesToCharsMunge(text1);
4767 chars2 = diffLinesToCharsMunge(text2);
4768 return {
4769 chars1: chars1,
4770 chars2: chars2,
4771 lineArray: lineArray
4772 };
4773 };
4774
4775 /**
4776 * Rehydrate the text in a diff from a string of line hashes to real lines of
4777 * text.
4778 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4779 * @param {!Array.<string>} lineArray Array of unique strings.
4780 * @private
4781 */
4782 DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) {
4783 var x, chars, text, y;
4784 for (x = 0; x < diffs.length; x++) {
4785 chars = diffs[x][1];
4786 text = [];
4787 for (y = 0; y < chars.length; y++) {
4788 text[y] = lineArray[chars.charCodeAt(y)];
4789 }
4790 diffs[x][1] = text.join("");
4791 }
4792 };
4793
4794 /**
4795 * Reorder and merge like edit sections. Merge equalities.
4796 * Any edit section can move as long as it doesn't cross an equality.
4797 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4798 */
4799 DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) {
4800 var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position;
4801 diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end.
4802 pointer = 0;
4803 countDelete = 0;
4804 countInsert = 0;
4805 textDelete = "";
4806 textInsert = "";
4807
4808 while (pointer < diffs.length) {
4809 switch (diffs[pointer][0]) {
4810 case DIFF_INSERT:
4811 countInsert++;
4812 textInsert += diffs[pointer][1];
4813 pointer++;
4814 break;
4815 case DIFF_DELETE:
4816 countDelete++;
4817 textDelete += diffs[pointer][1];
4818 pointer++;
4819 break;
4820 case DIFF_EQUAL:
4821
4822 // Upon reaching an equality, check for prior redundancies.
4823 if (countDelete + countInsert > 1) {
4824 if (countDelete !== 0 && countInsert !== 0) {
4825
4826 // Factor out any common prefixes.
4827 commonlength = this.diffCommonPrefix(textInsert, textDelete);
4828 if (commonlength !== 0) {
4829 if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) {
4830 diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength);
4831 } else {
4832 diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
4833 pointer++;
4834 }
4835 textInsert = textInsert.substring(commonlength);
4836 textDelete = textDelete.substring(commonlength);
4837 }
4838
4839 // Factor out any common suffixies.
4840 commonlength = this.diffCommonSuffix(textInsert, textDelete);
4841 if (commonlength !== 0) {
4842 diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1];
4843 textInsert = textInsert.substring(0, textInsert.length - commonlength);
4844 textDelete = textDelete.substring(0, textDelete.length - commonlength);
4845 }
4846 }
4847
4848 // Delete the offending records and add the merged ones.
4849 if (countDelete === 0) {
4850 diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]);
4851 } else if (countInsert === 0) {
4852 diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]);
4853 } else {
4854 diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
4855 }
4856 pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
4857 } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
4858
4859 // Merge this equality with the previous one.
4860 diffs[pointer - 1][1] += diffs[pointer][1];
4861 diffs.splice(pointer, 1);
4862 } else {
4863 pointer++;
4864 }
4865 countInsert = 0;
4866 countDelete = 0;
4867 textDelete = "";
4868 textInsert = "";
4869 break;
4870 }
4871 }
4872 if (diffs[diffs.length - 1][1] === "") {
4873 diffs.pop(); // Remove the dummy entry at the end.
4874 }
4875
4876 // Second pass: look for single edits surrounded on both sides by equalities
4877 // which can be shifted sideways to eliminate an equality.
4878 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
4879 changes = false;
4880 pointer = 1;
4881
4882 // Intentionally ignore the first and last element (don't need checking).
4883 while (pointer < diffs.length - 1) {
4884 if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
4885
4886 diffPointer = diffs[pointer][1];
4887 position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
4888
4889 // This is a single edit surrounded by equalities.
4890 if (position === diffs[pointer - 1][1]) {
4891
4892 // Shift the edit over the previous equality.
4893 diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
4894 diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
4895 diffs.splice(pointer - 1, 1);
4896 changes = true;
4897 } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
4898
4899 // Shift the edit over the next equality.
4900 diffs[pointer - 1][1] += diffs[pointer + 1][1];
4901 diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
4902 diffs.splice(pointer + 1, 1);
4903 changes = true;
4904 }
4905 }
4906 pointer++;
4907 }
4908
4909 // If shifts were made, the diff needs reordering and another shift sweep.
4910 if (changes) {
4911 this.diffCleanupMerge(diffs);
4912 }
4913 };
4914
4915 return function (o, n) {
4916 var diff, output, text;
4917 diff = new DiffMatchPatch();
4918 output = diff.DiffMain(o, n);
4919 diff.diffCleanupEfficiency(output);
4920 text = diff.diffPrettyHtml(output);
4921
4922 return text;
4923 };
4924 }();
4925
4926 }((function() { return this; }())));
4927