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