Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/js/qunit/qunit.js
4501 views
1
/*!
2
* QUnit 2.23.1
3
* https://qunitjs.com/
4
*
5
* Copyright OpenJS Foundation and other contributors
6
* Released under the MIT license
7
* https://jquery.org/license
8
*/
9
(function () {
10
'use strict';
11
12
function _arrayLikeToArray(r, a) {
13
(null == a || a > r.length) && (a = r.length);
14
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
15
return n;
16
}
17
function _arrayWithHoles(r) {
18
if (Array.isArray(r)) return r;
19
}
20
function _arrayWithoutHoles(r) {
21
if (Array.isArray(r)) return _arrayLikeToArray(r);
22
}
23
function _classCallCheck(a, n) {
24
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
25
}
26
function _defineProperties(e, r) {
27
for (var t = 0; t < r.length; t++) {
28
var o = r[t];
29
o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o);
30
}
31
}
32
function _createClass(e, r, t) {
33
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
34
writable: !1
35
}), e;
36
}
37
function _createForOfIteratorHelper(r, e) {
38
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
39
if (!t) {
40
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
41
t && (r = t);
42
var n = 0,
43
F = function () {};
44
return {
45
s: F,
46
n: function () {
47
return n >= r.length ? {
48
done: !0
49
} : {
50
done: !1,
51
value: r[n++]
52
};
53
},
54
e: function (r) {
55
throw r;
56
},
57
f: F
58
};
59
}
60
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
61
}
62
var o,
63
a = !0,
64
u = !1;
65
return {
66
s: function () {
67
t = t.call(r);
68
},
69
n: function () {
70
var r = t.next();
71
return a = r.done, r;
72
},
73
e: function (r) {
74
u = !0, o = r;
75
},
76
f: function () {
77
try {
78
a || null == t.return || t.return();
79
} finally {
80
if (u) throw o;
81
}
82
}
83
};
84
}
85
function _iterableToArray(r) {
86
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
87
}
88
function _iterableToArrayLimit(r, l) {
89
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
90
if (null != t) {
91
var e,
92
n,
93
i,
94
u,
95
a = [],
96
f = !0,
97
o = !1;
98
try {
99
if (i = (t = t.call(r)).next, 0 === l) {
100
if (Object(t) !== t) return;
101
f = !1;
102
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
103
} catch (r) {
104
o = !0, n = r;
105
} finally {
106
try {
107
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
108
} finally {
109
if (o) throw n;
110
}
111
}
112
return a;
113
}
114
}
115
function _nonIterableRest() {
116
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
117
}
118
function _nonIterableSpread() {
119
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
120
}
121
function _slicedToArray(r, e) {
122
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
123
}
124
function _toConsumableArray(r) {
125
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
126
}
127
function _toPrimitive(t, r) {
128
if ("object" != typeof t || !t) return t;
129
var e = t[Symbol.toPrimitive];
130
if (void 0 !== e) {
131
var i = e.call(t, r || "default");
132
if ("object" != typeof i) return i;
133
throw new TypeError("@@toPrimitive must return a primitive value.");
134
}
135
return ("string" === r ? String : Number)(t);
136
}
137
function _toPropertyKey(t) {
138
var i = _toPrimitive(t, "string");
139
return "symbol" == typeof i ? i : i + "";
140
}
141
function _typeof(o) {
142
"@babel/helpers - typeof";
143
144
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
145
return typeof o;
146
} : function (o) {
147
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
148
}, _typeof(o);
149
}
150
function _unsupportedIterableToArray(r, a) {
151
if (r) {
152
if ("string" == typeof r) return _arrayLikeToArray(r, a);
153
var t = {}.toString.call(r).slice(8, -1);
154
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
155
}
156
}
157
158
// We don't use global-this-polyfill [1], because it modifies
159
// the globals scope by default. QUnit must not affect the host context
160
// as developers may test their project may be such a polyfill, and/or
161
// they may intentionally test their project with and without certain
162
// polyfills and we must not affect that. It also uses an obscure
163
// mechanism that seems to sometimes causes a runtime error in older
164
// browsers (specifically Safari and IE versions that support
165
// Object.defineProperty but then report _T_ as undefined).
166
// [1] https://github.com/ungap/global-this/blob/v0.4.4/esm/index.js
167
//
168
// Another way is `Function('return this')()`, but doing so relies
169
// on eval which will cause a CSP error on some servers.
170
//
171
// Instead, simply check the four options that already exist
172
// in all supported environments.
173
function getGlobalThis() {
174
if (typeof globalThis !== 'undefined') {
175
// For SpiderMonkey, modern browsers, and recent Node.js
176
// eslint-disable-next-line no-undef
177
return globalThis;
178
}
179
if (typeof self !== 'undefined') {
180
// For web workers
181
// eslint-disable-next-line no-undef
182
return self;
183
}
184
if (typeof window$1 !== 'undefined') {
185
// For document context in browsers
186
return window$1;
187
}
188
if (typeof global !== 'undefined') {
189
// For Node.js
190
// eslint-disable-next-line no-undef
191
return global;
192
}
193
throw new Error('Unable to locate global object');
194
}
195
196
// This avoids a simple `export const` assignment as that would lead Rollup
197
// to change getGlobalThis and use the same (generated) variable name there.
198
var g = getGlobalThis();
199
200
// These optional globals are undefined in one or more environments:
201
// modern browser, old browser, Node.js, SpiderMonkey.
202
// Calling code must check these for truthy-ness before use.
203
var console$1 = g.console;
204
var setTimeout$1 = g.setTimeout;
205
var clearTimeout = g.clearTimeout;
206
var process$1 = g.process;
207
var window$1 = g.window;
208
var document = window$1 && window$1.document;
209
var navigator = window$1 && window$1.navigator;
210
var localSessionStorage = function () {
211
var x = 'qunit-test-string';
212
try {
213
g.sessionStorage.setItem(x, x);
214
g.sessionStorage.removeItem(x);
215
return g.sessionStorage;
216
} catch (e) {
217
return undefined;
218
}
219
}();
220
221
// Basic fallback for ES6 Map
222
// Support: IE 9-10, Safari 7, PhantomJS; Map is undefined
223
// Support: iOS 8; `new Map(iterable)` is not supported
224
//
225
// Fallback for ES7 Map#keys
226
// Support: IE 11; Map#keys is undefined
227
var StringMap = typeof g.Map === 'function' && typeof g.Map.prototype.keys === 'function' && typeof g.Symbol === 'function' && _typeof(g.Symbol.iterator) === 'symbol' ? g.Map : function StringMap(input) {
228
var _this = this;
229
var store = Object.create(null);
230
var hasOwn = Object.prototype.hasOwnProperty;
231
this.has = function (strKey) {
232
return hasOwn.call(store, strKey);
233
};
234
this.get = function (strKey) {
235
return store[strKey];
236
};
237
this.set = function (strKey, val) {
238
if (!hasOwn.call(store, strKey)) {
239
this.size++;
240
}
241
store[strKey] = val;
242
return this;
243
};
244
this.delete = function (strKey) {
245
if (hasOwn.call(store, strKey)) {
246
delete store[strKey];
247
this.size--;
248
}
249
};
250
this.forEach = function (callback) {
251
for (var strKey in store) {
252
callback(store[strKey], strKey);
253
}
254
};
255
this.keys = function () {
256
return Object.keys(store);
257
};
258
this.clear = function () {
259
store = Object.create(null);
260
this.size = 0;
261
};
262
this.size = 0;
263
if (input) {
264
input.forEach(function (val, strKey) {
265
_this.set(strKey, val);
266
});
267
}
268
};
269
270
// Basic fallback for ES6 Set
271
// Support: IE 11, `new Set(iterable)` parameter not yet implemented
272
// Test for Set#values() which came after Set(iterable).
273
var StringSet = typeof g.Set === 'function' && typeof g.Set.prototype.values === 'function' ? g.Set : function (input) {
274
var set = Object.create(null);
275
if (Array.isArray(input)) {
276
input.forEach(function (item) {
277
set[item] = true;
278
});
279
}
280
return {
281
add: function add(value) {
282
set[value] = true;
283
},
284
has: function has(value) {
285
return value in set;
286
},
287
get size() {
288
return Object.keys(set).length;
289
}
290
};
291
};
292
293
var toString = Object.prototype.toString;
294
var hasOwn$1 = Object.prototype.hasOwnProperty;
295
var performance = {
296
// eslint-disable-next-line compat/compat -- Checked
297
now: window$1 && window$1.performance && window$1.performance.now ? window$1.performance.now.bind(window$1.performance) : Date.now
298
};
299
300
// Returns a new Array with the elements that are in a but not in b
301
function diff$1(a, b) {
302
return a.filter(function (a) {
303
return b.indexOf(a) === -1;
304
});
305
}
306
307
/**
308
* Determines whether an element exists in a given array or not.
309
*
310
* @method inArray
311
* @param {any} elem
312
* @param {Array} array
313
* @return {boolean}
314
*/
315
var inArray = Array.prototype.includes ? function (elem, array) {
316
return array.includes(elem);
317
} : function (elem, array) {
318
return array.indexOf(elem) !== -1;
319
};
320
321
/**
322
* Recursively clone an object into a plain array or object, taking only the
323
* own enumerable properties.
324
*
325
* @param {any} obj
326
* @param {bool} [allowArray=true]
327
* @return {Object|Array}
328
*/
329
function objectValues(obj) {
330
var allowArray = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
331
var vals = allowArray && is('array', obj) ? [] : {};
332
for (var key in obj) {
333
if (hasOwn$1.call(obj, key)) {
334
var val = obj[key];
335
vals[key] = val === Object(val) ? objectValues(val, allowArray) : val;
336
}
337
}
338
return vals;
339
}
340
341
/**
342
* Recursively clone an object into a plain object, taking only the
343
* subset of own enumerable properties that exist a given model.
344
*
345
* @param {any} obj
346
* @param {any} model
347
* @return {Object}
348
*/
349
function objectValuesSubset(obj, model) {
350
// Return primitive values unchanged to avoid false positives or confusing
351
// results from assert.propContains().
352
// E.g. an actual null or false wrongly equaling an empty object,
353
// or an actual string being reported as object not matching a partial object.
354
if (obj !== Object(obj)) {
355
return obj;
356
}
357
358
// Unlike objectValues(), subset arrays to a plain objects as well.
359
// This enables subsetting [20, 30] with {1: 30}.
360
var subset = {};
361
for (var key in model) {
362
if (hasOwn$1.call(model, key) && hasOwn$1.call(obj, key)) {
363
subset[key] = objectValuesSubset(obj[key], model[key]);
364
}
365
}
366
return subset;
367
}
368
function extend(a, b, undefOnly) {
369
for (var prop in b) {
370
if (hasOwn$1.call(b, prop)) {
371
if (b[prop] === undefined) {
372
delete a[prop];
373
} else if (!(undefOnly && typeof a[prop] !== 'undefined')) {
374
a[prop] = b[prop];
375
}
376
}
377
}
378
return a;
379
}
380
function objectType(obj) {
381
if (typeof obj === 'undefined') {
382
return 'undefined';
383
}
384
385
// Consider: typeof null === object
386
if (obj === null) {
387
return 'null';
388
}
389
var match = toString.call(obj).match(/^\[object\s(.*)\]$/);
390
var type = match && match[1];
391
switch (type) {
392
case 'Number':
393
if (isNaN(obj)) {
394
return 'nan';
395
}
396
return 'number';
397
case 'String':
398
case 'Boolean':
399
case 'Array':
400
case 'Set':
401
case 'Map':
402
case 'Date':
403
case 'RegExp':
404
case 'Function':
405
case 'Symbol':
406
return type.toLowerCase();
407
default:
408
return _typeof(obj);
409
}
410
}
411
412
// Safe object type checking
413
function is(type, obj) {
414
return objectType(obj) === type;
415
}
416
417
// Based on Java's String.hashCode, a simple but not
418
// rigorously collision resistant hashing function
419
function generateHash(module, testName) {
420
var str = module + '\x1C' + testName;
421
var hash = 0;
422
for (var i = 0; i < str.length; i++) {
423
hash = (hash << 5) - hash + str.charCodeAt(i);
424
hash |= 0;
425
}
426
427
// Convert the possibly negative integer hash code into an 8 character hex string, which isn't
428
// strictly necessary but increases user understanding that the id is a SHA-like hash
429
var hex = (0x100000000 + hash).toString(16);
430
if (hex.length < 8) {
431
hex = '0000000' + hex;
432
}
433
return hex.slice(-8);
434
}
435
436
/**
437
* Converts an error into a simple string for comparisons.
438
*
439
* @param {Error|any} error
440
* @return {string}
441
*/
442
function errorString(error) {
443
// Use String() instead of toString() to handle non-object values like undefined or null.
444
var resultErrorString = String(error);
445
446
// If the error wasn't a subclass of Error but something like
447
// an object literal with name and message properties...
448
if (resultErrorString.slice(0, 7) === '[object') {
449
// Based on https://es5.github.io/#x15.11.4.4
450
return (error.name || 'Error') + (error.message ? ": ".concat(error.message) : '');
451
} else {
452
return resultErrorString;
453
}
454
}
455
function escapeText(str) {
456
if (!str) {
457
return '';
458
}
459
460
// Both single quotes and double quotes (for attributes)
461
return ('' + str).replace(/['"<>&]/g, function (s) {
462
switch (s) {
463
case "'":
464
return '&#039;';
465
case '"':
466
return '&quot;';
467
case '<':
468
return '&lt;';
469
case '>':
470
return '&gt;';
471
case '&':
472
return '&amp;';
473
}
474
});
475
}
476
477
var BOXABLE_TYPES = new StringSet(['boolean', 'number', 'string']);
478
479
// Memory for previously seen containers (object, array, map, set).
480
// Used for recursion detection, and to avoid repeated comparison.
481
//
482
// Elements are { a: val, b: val }.
483
var memory = [];
484
function useStrictEquality(a, b) {
485
return a === b;
486
}
487
function useObjectValueEquality(a, b) {
488
return a === b || a.valueOf() === b.valueOf();
489
}
490
function compareConstructors(a, b) {
491
// Comparing constructors is more strict than using `instanceof`
492
return getConstructor(a) === getConstructor(b);
493
}
494
function getConstructor(obj) {
495
var proto = Object.getPrototypeOf(obj);
496
497
// If the obj prototype descends from a null constructor, treat it
498
// as a null prototype.
499
// Ref https://github.com/qunitjs/qunit/issues/851
500
//
501
// Allow objects with no prototype, from Object.create(null), to be equivalent to
502
// plain objects that have Object as their constructor.
503
return !proto || proto.constructor === null ? Object : obj.constructor;
504
}
505
function getRegExpFlags(regexp) {
506
return 'flags' in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
507
}
508
509
// Specialised comparisons after entryTypeCallbacks.object, based on `objectType()`
510
var objTypeCallbacks = {
511
undefined: useStrictEquality,
512
null: useStrictEquality,
513
// Handle boxed boolean
514
boolean: useObjectValueEquality,
515
number: function number(a, b) {
516
// Handle NaN and boxed number
517
return a === b || a.valueOf() === b.valueOf() || isNaN(a.valueOf()) && isNaN(b.valueOf());
518
},
519
// Handle boxed string
520
string: useObjectValueEquality,
521
symbol: useStrictEquality,
522
date: useObjectValueEquality,
523
nan: function nan() {
524
return true;
525
},
526
regexp: function regexp(a, b) {
527
return a.source === b.source &&
528
// Include flags in the comparison
529
getRegExpFlags(a) === getRegExpFlags(b);
530
},
531
// identical reference only
532
function: useStrictEquality,
533
array: function array(a, b) {
534
if (a.length !== b.length) {
535
// Safe and faster
536
return false;
537
}
538
for (var i = 0; i < a.length; i++) {
539
if (!typeEquiv(a[i], b[i])) {
540
return false;
541
}
542
}
543
return true;
544
},
545
// Define sets a and b to be equivalent if for each element aVal in a, there
546
// is some element bVal in b such that aVal and bVal are equivalent. Element
547
// repetitions are not counted, so these are equivalent:
548
// a = new Set( [ X={}, Y=[], Y ] );
549
// b = new Set( [ Y, X, X ] );
550
set: function set(a, b) {
551
if (a.size !== b.size) {
552
// This optimization has certain quirks because of the lack of
553
// repetition counting. For instance, adding the same
554
// (reference-identical) element to two equivalent sets can
555
// make them non-equivalent.
556
return false;
557
}
558
var outerEq = true;
559
a.forEach(function (aVal) {
560
// Short-circuit if the result is already known. (Using for...of
561
// with a break clause would be cleaner here, but it would cause
562
// a syntax error on older JavaScript implementations even if
563
// Set is unused)
564
if (!outerEq) {
565
return;
566
}
567
var innerEq = false;
568
b.forEach(function (bVal) {
569
// Likewise, short-circuit if the result is already known
570
if (innerEq) {
571
return;
572
}
573
574
// Swap out the global memory, as nested typeEquiv() would clobber it
575
var originalMemory = memory;
576
memory = [];
577
if (typeEquiv(bVal, aVal)) {
578
innerEq = true;
579
}
580
// Restore
581
memory = originalMemory;
582
});
583
if (!innerEq) {
584
outerEq = false;
585
}
586
});
587
return outerEq;
588
},
589
// Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
590
// in a, there is some key-value pair (bKey, bVal) in b such that
591
// [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
592
// counted, so these are equivalent:
593
// a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
594
// b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
595
map: function map(a, b) {
596
if (a.size !== b.size) {
597
// This optimization has certain quirks because of the lack of
598
// repetition counting. For instance, adding the same
599
// (reference-identical) key-value pair to two equivalent maps
600
// can make them non-equivalent.
601
return false;
602
}
603
var outerEq = true;
604
a.forEach(function (aVal, aKey) {
605
// Short-circuit if the result is already known. (Using for...of
606
// with a break clause would be cleaner here, but it would cause
607
// a syntax error on older JavaScript implementations even if
608
// Map is unused)
609
if (!outerEq) {
610
return;
611
}
612
var innerEq = false;
613
b.forEach(function (bVal, bKey) {
614
// Likewise, short-circuit if the result is already known
615
if (innerEq) {
616
return;
617
}
618
619
// Swap out the global memory, as nested typeEquiv() would clobber it
620
var originalMemory = memory;
621
memory = [];
622
if (objTypeCallbacks.array([bVal, bKey], [aVal, aKey])) {
623
innerEq = true;
624
}
625
// Restore
626
memory = originalMemory;
627
});
628
if (!innerEq) {
629
outerEq = false;
630
}
631
});
632
return outerEq;
633
}
634
};
635
636
// Entry points from typeEquiv, based on `typeof`
637
var entryTypeCallbacks = {
638
undefined: useStrictEquality,
639
null: useStrictEquality,
640
boolean: useStrictEquality,
641
number: function number(a, b) {
642
// Handle NaN
643
return a === b || isNaN(a) && isNaN(b);
644
},
645
string: useStrictEquality,
646
symbol: useStrictEquality,
647
function: useStrictEquality,
648
object: function object(a, b) {
649
// Handle memory (skip recursion)
650
if (memory.some(function (pair) {
651
return pair.a === a && pair.b === b;
652
})) {
653
return true;
654
}
655
memory.push({
656
a: a,
657
b: b
658
});
659
var aObjType = objectType(a);
660
var bObjType = objectType(b);
661
if (aObjType !== 'object' || bObjType !== 'object') {
662
// Handle literal `null`
663
// Handle: Array, Map/Set, Date, Regxp/Function, boxed primitives
664
return aObjType === bObjType && objTypeCallbacks[aObjType](a, b);
665
}
666
667
// NOTE: Literal null must not make it here as it would throw
668
if (compareConstructors(a, b) === false) {
669
return false;
670
}
671
var aProperties = [];
672
var bProperties = [];
673
674
// Be strict and go deep, no filtering with hasOwnProperty.
675
for (var i in a) {
676
// Collect a's properties
677
aProperties.push(i);
678
679
// Skip OOP methods that look the same
680
if (a.constructor !== Object && typeof a.constructor !== 'undefined' && typeof a[i] === 'function' && typeof b[i] === 'function' && a[i].toString() === b[i].toString()) {
681
continue;
682
}
683
if (!typeEquiv(a[i], b[i])) {
684
return false;
685
}
686
}
687
for (var _i in b) {
688
// Collect b's properties
689
bProperties.push(_i);
690
}
691
return objTypeCallbacks.array(aProperties.sort(), bProperties.sort());
692
}
693
};
694
function typeEquiv(a, b) {
695
// Optimization: Only perform type-specific comparison when pairs are not strictly equal.
696
if (a === b) {
697
return true;
698
}
699
var aType = _typeof(a);
700
var bType = _typeof(b);
701
if (aType !== bType) {
702
// Support comparing primitive to boxed primitives
703
// Try again after possibly unwrapping one
704
return (aType === 'object' && BOXABLE_TYPES.has(objectType(a)) ? a.valueOf() : a) === (bType === 'object' && BOXABLE_TYPES.has(objectType(b)) ? b.valueOf() : b);
705
}
706
return entryTypeCallbacks[aType](a, b);
707
}
708
function innerEquiv(a, b) {
709
var res = typeEquiv(a, b);
710
// Release any retained objects and reset recursion detection for next call
711
memory = [];
712
return res;
713
}
714
715
/**
716
* Test any two types of JavaScript values for equality.
717
*
718
* @author Philippe Rathé <[email protected]>
719
* @author David Chan <[email protected]>
720
*/
721
function equiv(a, b) {
722
if (arguments.length === 2) {
723
return a === b || innerEquiv(a, b);
724
}
725
726
// Given 0 or 1 arguments, just return true (nothing to compare).
727
// Given (A,B,C,D) compare C,D then B,C then A,B.
728
var i = arguments.length - 1;
729
while (i > 0) {
730
if (!innerEquiv(arguments[i - 1], arguments[i])) {
731
return false;
732
}
733
i--;
734
}
735
return true;
736
}
737
738
/**
739
* Config object: Maintain internal state
740
* Later exposed as QUnit.config
741
* `config` initialized at top of scope
742
*/
743
var config = {
744
// HTML Reporter: Modify document.title when suite is done
745
altertitle: true,
746
// TODO: Move here from /src/core.js in QUnit 3.
747
// autostart: true,
748
749
// HTML Reporter: collapse every test except the first failing test
750
// If false, all failing tests will be expanded
751
collapse: true,
752
countStepsAsOne: false,
753
// TODO: Make explicit in QUnit 3.
754
// current: undefined,
755
756
// whether or not to fail when there are zero tests
757
// defaults to `true`
758
failOnZeroTests: true,
759
// Select by pattern or case-insensitive substring match against "moduleName: testName"
760
filter: undefined,
761
// TODO: Make explicit in QUnit 3.
762
// fixture: undefined,
763
764
// Depth up-to which object will be dumped
765
maxDepth: 5,
766
// Select case-insensitive match of the module name
767
module: undefined,
768
// HTML Reporter: Select module/test by array of internal IDs
769
moduleId: undefined,
770
// By default, run previously failed tests first
771
// very useful in combination with "Hide passed tests" checked
772
reorder: true,
773
// When enabled, all tests must call expect()
774
requireExpects: false,
775
// By default, scroll to top of the page when suite is done
776
scrolltop: true,
777
// TODO: Make explicit in QUnit 3.
778
// seed: undefined,
779
780
// The storage module to use for reordering tests
781
storage: localSessionStorage,
782
testId: undefined,
783
// The updateRate controls how often QUnit will yield the main thread
784
// between tests. This is mainly for the benefit of the HTML Reporter,
785
// so that the browser can visually paint DOM changes with test results.
786
// This also helps avoid causing browsers to prompt a warning about
787
// long-running scripts.
788
// TODO: Move here from /src/core.js in QUnit 3.
789
// updateRate: 1000,
790
791
// HTML Reporter: List of URL parameters that are given visual controls
792
urlConfig: [],
793
// Internal: The first unnamed module
794
//
795
// By being defined as the intial value for currentModule, it is the
796
// receptacle and implied parent for any global tests. It is as if we
797
// called `QUnit.module( "" );` before any other tests were defined.
798
//
799
// If we reach begin() and no tests were put in it, we dequeue it as if it
800
// never existed, and in that case never expose it to the events and
801
// callbacks API.
802
//
803
// When global tests are defined, then this unnamed module will execute
804
// as any other module, including moduleStart/moduleDone events etc.
805
//
806
// Since this module isn't explicitly created by the user, they have no
807
// access to add hooks for it. The hooks object is defined to comply
808
// with internal expectations of test.js, but they will be empty.
809
// To apply hooks, place tests explicitly in a QUnit.module(), and use
810
// its hooks accordingly.
811
//
812
// For global hooks that apply to all tests and all modules, use QUnit.hooks.
813
//
814
// NOTE: This is *not* a "global module". It is not an ancestor of all modules
815
// and tests. It is merely the parent for any tests defined globally,
816
// before the first QUnit.module(). As such, the events for this unnamed
817
// module will fire as normal, right after its last test, and *not* at
818
// the end of the test run.
819
//
820
// NOTE: This also should probably also not become a global module, unless
821
// we keep it out of the public API. For example, it would likely not
822
// improve the user interface and plugin behaviour if all modules became
823
// wrapped between the start and end events of this module, and thus
824
// needlessly add indentation, indirection, or other visible noise.
825
// Unit tests for the callbacks API would detect that as a regression.
826
currentModule: {
827
name: '',
828
tests: [],
829
childModules: [],
830
testsRun: 0,
831
testsIgnored: 0,
832
hooks: {
833
before: [],
834
beforeEach: [],
835
afterEach: [],
836
after: []
837
}
838
},
839
// Internal: Exposed to make resets easier
840
// Ref https://github.com/qunitjs/qunit/pull/1598
841
globalHooks: {},
842
// Internal: ProcessingQueue singleton, created in /src/core.js
843
pq: null,
844
// Internal: Created in /src/core.js
845
// TODO: Move definitions here in QUnit 3.0.
846
// started: 0,
847
848
// Internal state
849
_deprecated_timeout_shown: false,
850
_deprecated_countEachStep_shown: false,
851
blocking: true,
852
callbacks: {},
853
modules: [],
854
queue: [],
855
stats: {
856
all: 0,
857
bad: 0,
858
testCount: 0
859
}
860
};
861
function readFlatPreconfigBoolean(val, dest) {
862
if (typeof val === 'boolean' || typeof val === 'string' && val !== '') {
863
config[dest] = val === true || val === 'true';
864
}
865
}
866
function readFlatPreconfigNumber(val, dest) {
867
if (typeof val === 'number' || typeof val === 'string' && /^[0-9]+$/.test(val)) {
868
config[dest] = +val;
869
}
870
}
871
function readFlatPreconfigString(val, dest) {
872
if (typeof val === 'string' && val !== '') {
873
config[dest] = val;
874
}
875
}
876
function readFlatPreconfigStringOrBoolean(val, dest) {
877
if (typeof val === 'boolean' || typeof val === 'string' && val !== '') {
878
config[dest] = val;
879
}
880
}
881
function readFlatPreconfigStringArray(val, dest) {
882
if (typeof val === 'string' && val !== '') {
883
config[dest] = [val];
884
}
885
}
886
function readFlatPreconfig(obj) {
887
readFlatPreconfigBoolean(obj.qunit_config_altertitle, 'altertitle');
888
readFlatPreconfigBoolean(obj.qunit_config_autostart, 'autostart');
889
readFlatPreconfigBoolean(obj.qunit_config_collapse, 'collapse');
890
readFlatPreconfigBoolean(obj.qunit_config_failonzerotests, 'failOnZeroTests');
891
readFlatPreconfigString(obj.qunit_config_filter, 'filter');
892
readFlatPreconfigString(obj.qunit_config_fixture, 'fixture');
893
readFlatPreconfigBoolean(obj.qunit_config_hidepassed, 'hidepassed');
894
readFlatPreconfigNumber(obj.qunit_config_maxdepth, 'maxDepth');
895
readFlatPreconfigString(obj.qunit_config_module, 'module');
896
readFlatPreconfigStringArray(obj.qunit_config_moduleid, 'moduleId');
897
readFlatPreconfigBoolean(obj.qunit_config_noglobals, 'noglobals');
898
readFlatPreconfigBoolean(obj.qunit_config_notrycatch, 'notrycatch');
899
readFlatPreconfigBoolean(obj.qunit_config_reorder, 'reorder');
900
readFlatPreconfigBoolean(obj.qunit_config_requireexpects, 'requireExpects');
901
readFlatPreconfigBoolean(obj.qunit_config_scrolltop, 'scrolltop');
902
readFlatPreconfigStringOrBoolean(obj.qunit_config_seed, 'seed');
903
readFlatPreconfigStringArray(obj.qunit_config_testid, 'testId');
904
readFlatPreconfigNumber(obj.qunit_config_testtimeout, 'testTimeout');
905
}
906
if (process$1 && 'env' in process$1) {
907
readFlatPreconfig(process$1.env);
908
}
909
readFlatPreconfig(g);
910
911
// Apply a predefined QUnit.config object
912
//
913
// Ignore QUnit.config if it is a QUnit distribution instead of preconfig.
914
// That means QUnit was loaded twice! (Use the same approach as export.js)
915
var preConfig = g && g.QUnit && !g.QUnit.version && g.QUnit.config;
916
if (preConfig) {
917
extend(config, preConfig);
918
}
919
920
// Push a loose unnamed module to the modules collection
921
config.modules.push(config.currentModule);
922
if (config.seed === 'true' || config.seed === true) {
923
// Generate a random seed
924
// Length of `Math.random()` fraction, in base 36, may vary from 6-14.
925
// Pad and take slice to a consistent 10-digit value.
926
config.seed = (Math.random().toString(36) + '0000000000').slice(2, 12);
927
}
928
929
var dump = (function () {
930
function quote(str) {
931
return '"' + str.toString().replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
932
}
933
function literal(o) {
934
return o + '';
935
}
936
function join(pre, arr, post) {
937
var s = dump.separator();
938
var inner = dump.indent(1);
939
if (arr.join) {
940
arr = arr.join(',' + s + inner);
941
}
942
if (!arr) {
943
return pre + post;
944
}
945
var base = dump.indent();
946
return [pre, inner + arr, base + post].join(s);
947
}
948
function array(arr, stack) {
949
if (dump.maxDepth && dump.depth > dump.maxDepth) {
950
return '[object Array]';
951
}
952
this.up();
953
var i = arr.length;
954
var ret = new Array(i);
955
while (i--) {
956
ret[i] = this.parse(arr[i], undefined, stack);
957
}
958
this.down();
959
return join('[', ret, ']');
960
}
961
function isArray(obj) {
962
return (
963
// Native Arrays
964
toString.call(obj) === '[object Array]' ||
965
// NodeList objects
966
typeof obj.length === 'number' && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
967
);
968
}
969
var reName = /^function (\w+)/;
970
var dump = {
971
// The objType is used mostly internally, you can fix a (custom) type in advance
972
parse: function parse(obj, objType, stack) {
973
stack = stack || [];
974
var objIndex = stack.indexOf(obj);
975
if (objIndex !== -1) {
976
return "recursion(".concat(objIndex - stack.length, ")");
977
}
978
objType = objType || this.typeOf(obj);
979
var parser = this.parsers[objType];
980
var parserType = _typeof(parser);
981
if (parserType === 'function') {
982
stack.push(obj);
983
var res = parser.call(this, obj, stack);
984
stack.pop();
985
return res;
986
}
987
if (parserType === 'string') {
988
return parser;
989
}
990
return '[ERROR: Missing QUnit.dump formatter for type ' + objType + ']';
991
},
992
typeOf: function typeOf(obj) {
993
var type;
994
if (obj === null) {
995
type = 'null';
996
} else if (typeof obj === 'undefined') {
997
type = 'undefined';
998
} else if (is('regexp', obj)) {
999
type = 'regexp';
1000
} else if (is('date', obj)) {
1001
type = 'date';
1002
} else if (is('function', obj)) {
1003
type = 'function';
1004
} else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
1005
type = 'window';
1006
} else if (obj.nodeType === 9) {
1007
type = 'document';
1008
} else if (obj.nodeType) {
1009
type = 'node';
1010
} else if (isArray(obj)) {
1011
type = 'array';
1012
} else if (obj.constructor === Error.prototype.constructor) {
1013
type = 'error';
1014
} else {
1015
type = _typeof(obj);
1016
}
1017
return type;
1018
},
1019
separator: function separator() {
1020
if (this.multiline) {
1021
return this.HTML ? '<br />' : '\n';
1022
} else {
1023
return this.HTML ? '&#160;' : ' ';
1024
}
1025
},
1026
// Extra can be a number, shortcut for increasing-calling-decreasing
1027
indent: function indent(extra) {
1028
if (!this.multiline) {
1029
return '';
1030
}
1031
var chr = this.indentChar;
1032
if (this.HTML) {
1033
chr = chr.replace(/\t/g, ' ').replace(/ /g, '&#160;');
1034
}
1035
return new Array(this.depth + (extra || 0)).join(chr);
1036
},
1037
up: function up(a) {
1038
this.depth += a || 1;
1039
},
1040
down: function down(a) {
1041
this.depth -= a || 1;
1042
},
1043
setParser: function setParser(name, parser) {
1044
this.parsers[name] = parser;
1045
},
1046
// The next 3 are exposed so you can use them
1047
quote: quote,
1048
literal: literal,
1049
join: join,
1050
depth: 1,
1051
maxDepth: config.maxDepth,
1052
// This is the list of parsers, to modify them, use dump.setParser
1053
parsers: {
1054
window: '[Window]',
1055
document: '[Document]',
1056
error: function error(_error) {
1057
return 'Error("' + _error.message + '")';
1058
},
1059
// This has been unused since QUnit 1.0.0.
1060
// @todo Deprecate and remove.
1061
unknown: '[Unknown]',
1062
null: 'null',
1063
undefined: 'undefined',
1064
function: function _function(fn) {
1065
var ret = 'function';
1066
1067
// Functions never have name in IE
1068
var name = 'name' in fn ? fn.name : (reName.exec(fn) || [])[1];
1069
if (name) {
1070
ret += ' ' + name;
1071
}
1072
ret += '(';
1073
ret = [ret, dump.parse(fn, 'functionArgs'), '){'].join('');
1074
return join(ret, dump.parse(fn, 'functionCode'), '}');
1075
},
1076
array: array,
1077
nodelist: array,
1078
arguments: array,
1079
object: function object(map, stack) {
1080
var ret = [];
1081
if (dump.maxDepth && dump.depth > dump.maxDepth) {
1082
return '[object Object]';
1083
}
1084
dump.up();
1085
var keys = [];
1086
for (var key in map) {
1087
keys.push(key);
1088
}
1089
1090
// Some properties are not always enumerable on Error objects.
1091
var nonEnumerableProperties = ['message', 'name'];
1092
for (var i in nonEnumerableProperties) {
1093
var _key = nonEnumerableProperties[i];
1094
if (_key in map && !inArray(_key, keys)) {
1095
keys.push(_key);
1096
}
1097
}
1098
keys.sort();
1099
for (var _i = 0; _i < keys.length; _i++) {
1100
var _key2 = keys[_i];
1101
var val = map[_key2];
1102
ret.push(dump.parse(_key2, 'key') + ': ' + dump.parse(val, undefined, stack));
1103
}
1104
dump.down();
1105
return join('{', ret, '}');
1106
},
1107
node: function node(_node) {
1108
var open = dump.HTML ? '&lt;' : '<';
1109
var close = dump.HTML ? '&gt;' : '>';
1110
var tag = _node.nodeName.toLowerCase();
1111
var ret = open + tag;
1112
var attrs = _node.attributes;
1113
if (attrs) {
1114
for (var i = 0; i < attrs.length; i++) {
1115
var val = attrs[i].nodeValue;
1116
1117
// IE6 includes all attributes in .attributes, even ones not explicitly
1118
// set. Those have values like undefined, null, 0, false, "" or
1119
// "inherit".
1120
if (val && val !== 'inherit') {
1121
ret += ' ' + attrs[i].nodeName + '=' + dump.parse(val, 'attribute');
1122
}
1123
}
1124
}
1125
ret += close;
1126
1127
// Show content of TextNode or CDATASection
1128
if (_node.nodeType === 3 || _node.nodeType === 4) {
1129
ret += _node.nodeValue;
1130
}
1131
return ret + open + '/' + tag + close;
1132
},
1133
// Function calls it internally, it's the arguments part of the function
1134
functionArgs: function functionArgs(fn) {
1135
var l = fn.length;
1136
if (!l) {
1137
return '';
1138
}
1139
var args = new Array(l);
1140
while (l--) {
1141
// 97 is 'a'
1142
args[l] = String.fromCharCode(97 + l);
1143
}
1144
return ' ' + args.join(', ') + ' ';
1145
},
1146
// Object calls it internally, the key part of an item in a map
1147
key: quote,
1148
// Function calls it internally, it's the content of the function
1149
functionCode: '[code]',
1150
// Node calls it internally, it's a html attribute value
1151
attribute: quote,
1152
string: quote,
1153
date: quote,
1154
regexp: literal,
1155
number: literal,
1156
boolean: literal,
1157
symbol: function symbol(sym) {
1158
return sym.toString();
1159
}
1160
},
1161
// If true, entities are escaped ( <, >, \t, space and \n )
1162
HTML: false,
1163
// Indentation unit
1164
indentChar: ' ',
1165
// If true, items in a collection, are separated by a \n, else just a space.
1166
multiline: true
1167
};
1168
return dump;
1169
})();
1170
1171
// Support: IE 9
1172
// Detect if the console object exists and no-op otherwise.
1173
// This allows support for IE 9, which doesn't have a console
1174
// object if the developer tools are not open.
1175
1176
// Support: IE 9
1177
// Function#bind is supported, but no console.log.bind().
1178
1179
// Support: SpiderMonkey (mozjs 68+)
1180
// The console object has a log method, but no warn method.
1181
1182
var Logger = {
1183
warn: console$1 ? Function.prototype.bind.call(console$1.warn || console$1.log, console$1) : function () {}
1184
};
1185
1186
var SuiteReport = /*#__PURE__*/function () {
1187
function SuiteReport(name, parentSuite) {
1188
_classCallCheck(this, SuiteReport);
1189
this.name = name;
1190
this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
1191
1192
// When an "error" event is emitted from onUncaughtException(), the
1193
// "runEnd" event should report the status as failed. The "runEnd" event data
1194
// is tracked through this property (via the "runSuite" instance).
1195
this.globalFailureCount = 0;
1196
this.tests = [];
1197
this.childSuites = [];
1198
if (parentSuite) {
1199
parentSuite.pushChildSuite(this);
1200
}
1201
}
1202
return _createClass(SuiteReport, [{
1203
key: "start",
1204
value: function start(recordTime) {
1205
if (recordTime) {
1206
this._startTime = performance.now();
1207
}
1208
return {
1209
name: this.name,
1210
fullName: this.fullName.slice(),
1211
tests: this.tests.map(function (test) {
1212
return test.start();
1213
}),
1214
childSuites: this.childSuites.map(function (suite) {
1215
return suite.start();
1216
}),
1217
testCounts: {
1218
total: this.getTestCounts().total
1219
}
1220
};
1221
}
1222
}, {
1223
key: "end",
1224
value: function end(recordTime) {
1225
if (recordTime) {
1226
this._endTime = performance.now();
1227
}
1228
return {
1229
name: this.name,
1230
fullName: this.fullName.slice(),
1231
tests: this.tests.map(function (test) {
1232
return test.end();
1233
}),
1234
childSuites: this.childSuites.map(function (suite) {
1235
return suite.end();
1236
}),
1237
testCounts: this.getTestCounts(),
1238
runtime: this.getRuntime(),
1239
status: this.getStatus()
1240
};
1241
}
1242
}, {
1243
key: "pushChildSuite",
1244
value: function pushChildSuite(suite) {
1245
this.childSuites.push(suite);
1246
}
1247
}, {
1248
key: "pushTest",
1249
value: function pushTest(test) {
1250
this.tests.push(test);
1251
}
1252
}, {
1253
key: "getRuntime",
1254
value: function getRuntime() {
1255
return Math.round(this._endTime - this._startTime);
1256
}
1257
}, {
1258
key: "getTestCounts",
1259
value: function getTestCounts() {
1260
var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
1261
passed: 0,
1262
failed: 0,
1263
skipped: 0,
1264
todo: 0,
1265
total: 0
1266
};
1267
counts.failed += this.globalFailureCount;
1268
counts.total += this.globalFailureCount;
1269
counts = this.tests.reduce(function (counts, test) {
1270
if (test.valid) {
1271
counts[test.getStatus()]++;
1272
counts.total++;
1273
}
1274
return counts;
1275
}, counts);
1276
return this.childSuites.reduce(function (counts, suite) {
1277
return suite.getTestCounts(counts);
1278
}, counts);
1279
}
1280
}, {
1281
key: "getStatus",
1282
value: function getStatus() {
1283
var _this$getTestCounts = this.getTestCounts(),
1284
total = _this$getTestCounts.total,
1285
failed = _this$getTestCounts.failed,
1286
skipped = _this$getTestCounts.skipped,
1287
todo = _this$getTestCounts.todo;
1288
if (failed) {
1289
return 'failed';
1290
} else {
1291
if (skipped === total) {
1292
return 'skipped';
1293
} else if (todo === total) {
1294
return 'todo';
1295
} else {
1296
return 'passed';
1297
}
1298
}
1299
}
1300
}]);
1301
}();
1302
1303
var moduleStack = [];
1304
var runSuite = new SuiteReport();
1305
function isParentModuleInQueue() {
1306
var modulesInQueue = config.modules.filter(function (module) {
1307
return !module.ignored;
1308
}).map(function (module) {
1309
return module.moduleId;
1310
});
1311
return moduleStack.some(function (module) {
1312
return modulesInQueue.includes(module.moduleId);
1313
});
1314
}
1315
function createModule(name, testEnvironment, modifiers) {
1316
var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
1317
var moduleName = parentModule !== null ? [parentModule.name, name].join(' > ') : name;
1318
var parentSuite = parentModule ? parentModule.suiteReport : runSuite;
1319
var skip = parentModule !== null && parentModule.skip || modifiers.skip;
1320
var todo = parentModule !== null && parentModule.todo || modifiers.todo;
1321
var env = {};
1322
if (parentModule) {
1323
extend(env, parentModule.testEnvironment);
1324
}
1325
extend(env, testEnvironment);
1326
var module = {
1327
name: moduleName,
1328
parentModule: parentModule,
1329
hooks: {
1330
before: [],
1331
beforeEach: [],
1332
afterEach: [],
1333
after: []
1334
},
1335
testEnvironment: env,
1336
tests: [],
1337
moduleId: generateHash(moduleName),
1338
testsRun: 0,
1339
testsIgnored: 0,
1340
childModules: [],
1341
suiteReport: new SuiteReport(name, parentSuite),
1342
// Initialised by test.js when the module start executing,
1343
// i.e. before the first test in this module (or a child).
1344
stats: null,
1345
// Pass along `skip` and `todo` properties from parent module, in case
1346
// there is one, to childs. And use own otherwise.
1347
// This property will be used to mark own tests and tests of child suites
1348
// as either `skipped` or `todo`.
1349
skip: skip,
1350
todo: skip ? false : todo,
1351
ignored: modifiers.ignored || false
1352
};
1353
if (parentModule) {
1354
parentModule.childModules.push(module);
1355
}
1356
config.modules.push(module);
1357
return module;
1358
}
1359
function setHookFromEnvironment(hooks, environment, name) {
1360
var potentialHook = environment[name];
1361
if (typeof potentialHook === 'function') {
1362
hooks[name].push(potentialHook);
1363
}
1364
delete environment[name];
1365
}
1366
function makeSetHook(module, hookName) {
1367
return function setHook(callback) {
1368
if (config.currentModule !== module) {
1369
Logger.warn('The `' + hookName + '` hook was called inside the wrong module (`' + config.currentModule.name + '`). ' + 'Instead, use hooks provided by the callback to the containing module (`' + module.name + '`). ' + 'This will become an error in QUnit 3.0.');
1370
}
1371
module.hooks[hookName].push(callback);
1372
};
1373
}
1374
function processModule(name, options, scope) {
1375
var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
1376
if (typeof options === 'function') {
1377
scope = options;
1378
options = undefined;
1379
}
1380
var module = createModule(name, options, modifiers);
1381
1382
// Transfer any initial hooks from the options object to the 'hooks' object
1383
var testEnvironment = module.testEnvironment;
1384
var hooks = module.hooks;
1385
setHookFromEnvironment(hooks, testEnvironment, 'before');
1386
setHookFromEnvironment(hooks, testEnvironment, 'beforeEach');
1387
setHookFromEnvironment(hooks, testEnvironment, 'afterEach');
1388
setHookFromEnvironment(hooks, testEnvironment, 'after');
1389
var moduleFns = {
1390
before: makeSetHook(module, 'before'),
1391
beforeEach: makeSetHook(module, 'beforeEach'),
1392
afterEach: makeSetHook(module, 'afterEach'),
1393
after: makeSetHook(module, 'after')
1394
};
1395
var prevModule = config.currentModule;
1396
config.currentModule = module;
1397
if (typeof scope === 'function') {
1398
moduleStack.push(module);
1399
try {
1400
var cbReturnValue = scope.call(module.testEnvironment, moduleFns);
1401
if (cbReturnValue && typeof cbReturnValue.then === 'function') {
1402
Logger.warn('Returning a promise from a module callback is not supported. ' + 'Instead, use hooks for async behavior. ' + 'This will become an error in QUnit 3.0.');
1403
}
1404
} finally {
1405
// If the module closure threw an uncaught error during the load phase,
1406
// we let this bubble up to global error handlers. But, not until after
1407
// we teardown internal state to ensure correct module nesting.
1408
// Ref https://github.com/qunitjs/qunit/issues/1478.
1409
moduleStack.pop();
1410
config.currentModule = module.parentModule || prevModule;
1411
}
1412
}
1413
}
1414
var focused$1 = false; // indicates that the "only" filter was used
1415
1416
function module$1(name, options, scope) {
1417
var ignored = focused$1 && !isParentModuleInQueue();
1418
processModule(name, options, scope, {
1419
ignored: ignored
1420
});
1421
}
1422
module$1.only = function () {
1423
if (!focused$1) {
1424
// Upon the first module.only() call,
1425
// delete any and all previously registered modules and tests.
1426
config.modules.length = 0;
1427
config.queue.length = 0;
1428
1429
// Ignore any tests declared after this block within the same
1430
// module parent. https://github.com/qunitjs/qunit/issues/1645
1431
config.currentModule.ignored = true;
1432
}
1433
focused$1 = true;
1434
processModule.apply(void 0, arguments);
1435
};
1436
module$1.skip = function (name, options, scope) {
1437
if (focused$1) {
1438
return;
1439
}
1440
processModule(name, options, scope, {
1441
skip: true
1442
});
1443
};
1444
module$1.if = function (name, condition, options, scope) {
1445
if (focused$1) {
1446
return;
1447
}
1448
processModule(name, options, scope, {
1449
skip: !condition
1450
});
1451
};
1452
module$1.todo = function (name, options, scope) {
1453
if (focused$1) {
1454
return;
1455
}
1456
processModule(name, options, scope, {
1457
todo: true
1458
});
1459
};
1460
1461
// Stacktrace cleaner to focus on the path from error source to test suite.
1462
//
1463
// This should reduce a raw stack trace like this:
1464
//
1465
// > foo.broken()@/src/foo.js
1466
// > Bar@/src/bar.js
1467
// > @/test/bar.test.js
1468
// > @/lib/qunit.js:500:12
1469
// > @/lib/qunit.js:100:28
1470
// > @/lib/qunit.js:200:56
1471
// > setTimeout@
1472
// > @/dist/vendor.js
1473
//
1474
// and shorten it to show up until the end of the user's bar.test.js code.
1475
//
1476
// > foo.broken()@/src/foo.js
1477
// > Bar@/src/bar.js
1478
// > @/test/bar.test.js
1479
//
1480
// QUnit will obtain one example trace (once per process/pageload suffices),
1481
// strip off any :<line> and :<line>:<column>, and use that as match needle,
1482
// to the first QUnit-internal frames, and then stop at that point.
1483
// Any later frames, including those that are outside QUnit again, will be ommitted
1484
// as being uninteresting to the test, since QUnit will have either started or
1485
// resumed the test. This we also clean away browser built-ins, or other
1486
// vendor/bundler that may be higher up the stack.
1487
//
1488
// Stripping :<line>:<column> is not for prettyness, it is essential for the
1489
// match needle to work, since this sample trace will by definitin not be the
1490
// same line as e.g. the QUnit.test() call we're trying to identify.
1491
//
1492
// See also:
1493
// - https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1494
//
1495
var fileName = (sourceFromStacktrace(0) || ''
1496
// Global replace, because a frame like localhost:4000/lib/qunit.js:1234:50,
1497
// would otherwise (harmlessly, but uselessly) remove only the port (first match).
1498
// https://github.com/qunitjs/qunit/issues/1769
1499
).replace(/(:\d+)+\)?/g, '')
1500
// Remove anything prior to the last slash (Unix/Windows) from the last frame,
1501
// leaving only "qunit.js".
1502
.replace(/.+[/\\]/, '');
1503
function extractStacktrace(e, offset) {
1504
offset = offset === undefined ? 4 : offset;
1505
1506
// Support: IE9, e.stack is not supported, we will return undefined
1507
if (e && e.stack) {
1508
var stack = e.stack.split('\n');
1509
if (/^error$/i.test(stack[0])) {
1510
stack.shift();
1511
}
1512
if (fileName) {
1513
var include = [];
1514
for (var i = offset; i < stack.length; i++) {
1515
if (stack[i].indexOf(fileName) !== -1) {
1516
break;
1517
}
1518
include.push(stack[i]);
1519
}
1520
if (include.length) {
1521
return include.join('\n');
1522
}
1523
}
1524
return stack[offset];
1525
}
1526
}
1527
function sourceFromStacktrace(offset) {
1528
var error = new Error();
1529
1530
// Support: Safari <=7 only, IE <=10 - 11 only
1531
// Not all browsers generate the `stack` property for `new Error()`, see also #636
1532
if (!error.stack) {
1533
try {
1534
throw error;
1535
} catch (err) {
1536
error = err;
1537
}
1538
}
1539
return extractStacktrace(error, offset);
1540
}
1541
1542
var Assert = /*#__PURE__*/function () {
1543
function Assert(testContext) {
1544
_classCallCheck(this, Assert);
1545
this.test = testContext;
1546
}
1547
return _createClass(Assert, [{
1548
key: "timeout",
1549
value: function timeout(duration) {
1550
if (typeof duration !== 'number') {
1551
throw new Error('You must pass a number as the duration to assert.timeout');
1552
}
1553
this.test.timeout = duration;
1554
1555
// If a timeout has been set, clear it and reset with the new duration
1556
if (config.timeout) {
1557
clearTimeout(config.timeout);
1558
config.timeout = null;
1559
if (config.timeoutHandler && this.test.timeout > 0) {
1560
this.test.internalResetTimeout(this.test.timeout);
1561
}
1562
}
1563
}
1564
1565
// Documents a "step", which is a string value, in a test as a passing assertion
1566
}, {
1567
key: "step",
1568
value: function step(message) {
1569
var assertionMessage = message;
1570
var result = !!message;
1571
this.test.steps.push(message);
1572
if (typeof message === 'undefined' || message === '') {
1573
assertionMessage = 'You must provide a message to assert.step';
1574
} else if (typeof message !== 'string') {
1575
assertionMessage = 'You must provide a string value to assert.step';
1576
result = false;
1577
}
1578
this.pushResult({
1579
result: result,
1580
message: assertionMessage
1581
});
1582
}
1583
1584
// Verifies the steps in a test match a given array of string values
1585
}, {
1586
key: "verifySteps",
1587
value: function verifySteps(steps, message) {
1588
// Since the steps array is just string values, we can clone with slice
1589
var actualStepsClone = this.test.steps.slice();
1590
this.deepEqual(actualStepsClone, steps, message);
1591
this.test.stepsCount += this.test.steps.length;
1592
this.test.steps.length = 0;
1593
}
1594
}, {
1595
key: "expect",
1596
value: function expect(asserts) {
1597
if (arguments.length === 1) {
1598
this.test.expected = asserts;
1599
} else {
1600
return this.test.expected;
1601
}
1602
}
1603
1604
// Create a new async pause and return a new function that can release the pause.
1605
}, {
1606
key: "async",
1607
value: function async(count) {
1608
if (count === undefined) {
1609
count = 1;
1610
} else if (typeof count !== 'number') {
1611
throw new TypeError('async takes number as an input');
1612
}
1613
var requiredCalls = count;
1614
return this.test.internalStop(requiredCalls);
1615
}
1616
}, {
1617
key: "closeTo",
1618
value: function closeTo(actual, expected, delta, message) {
1619
if (typeof delta !== 'number') {
1620
throw new TypeError('closeTo() requires a delta argument');
1621
}
1622
this.pushResult({
1623
result: Math.abs(actual - expected) <= delta,
1624
actual: actual,
1625
expected: expected,
1626
message: message || "value should be within ".concat(delta, " inclusive")
1627
});
1628
}
1629
1630
// Alias of pushResult.
1631
}, {
1632
key: "push",
1633
value: function push(result, actual, expected, message, negative) {
1634
var currentAssert = this instanceof Assert ? this : config.current.assert;
1635
return currentAssert.pushResult({
1636
result: result,
1637
actual: actual,
1638
expected: expected,
1639
message: message,
1640
negative: negative
1641
});
1642
}
1643
1644
// Public API to internal test.pushResult()
1645
}, {
1646
key: "pushResult",
1647
value: function pushResult(resultInfo) {
1648
// Destructure of resultInfo = { result, actual, expected, message, negative }
1649
var assert = this;
1650
var currentTest = assert instanceof Assert && assert.test || config.current;
1651
1652
// Backwards compatibility fix.
1653
// Allows the direct use of global exported assertions and QUnit.assert.*
1654
// Although, it's use is not recommended as it can leak assertions
1655
// to other tests from async tests, because we only get a reference to the current test,
1656
// not exactly the test where assertion were intended to be called.
1657
if (!currentTest) {
1658
throw new Error('assertion outside test context, in ' + sourceFromStacktrace(2));
1659
}
1660
if (!(assert instanceof Assert)) {
1661
assert = currentTest.assert;
1662
}
1663
return assert.test.pushResult(resultInfo);
1664
}
1665
}, {
1666
key: "ok",
1667
value: function ok(result, message) {
1668
if (!message) {
1669
message = result ? 'okay' : "failed, expected argument to be truthy, was: ".concat(dump.parse(result));
1670
}
1671
this.pushResult({
1672
result: !!result,
1673
actual: result,
1674
expected: true,
1675
message: message
1676
});
1677
}
1678
}, {
1679
key: "notOk",
1680
value: function notOk(result, message) {
1681
if (!message) {
1682
message = !result ? 'okay' : "failed, expected argument to be falsy, was: ".concat(dump.parse(result));
1683
}
1684
this.pushResult({
1685
result: !result,
1686
actual: result,
1687
expected: false,
1688
message: message
1689
});
1690
}
1691
}, {
1692
key: "true",
1693
value: function _true(result, message) {
1694
this.pushResult({
1695
result: result === true,
1696
actual: result,
1697
expected: true,
1698
message: message
1699
});
1700
}
1701
}, {
1702
key: "false",
1703
value: function _false(result, message) {
1704
this.pushResult({
1705
result: result === false,
1706
actual: result,
1707
expected: false,
1708
message: message
1709
});
1710
}
1711
}, {
1712
key: "equal",
1713
value: function equal(actual, expected, message) {
1714
this.pushResult({
1715
// eslint-disable-next-line eqeqeq
1716
result: expected == actual,
1717
actual: actual,
1718
expected: expected,
1719
message: message
1720
});
1721
}
1722
}, {
1723
key: "notEqual",
1724
value: function notEqual(actual, expected, message) {
1725
this.pushResult({
1726
// eslint-disable-next-line eqeqeq
1727
result: expected != actual,
1728
actual: actual,
1729
expected: expected,
1730
message: message,
1731
negative: true
1732
});
1733
}
1734
}, {
1735
key: "propEqual",
1736
value: function propEqual(actual, expected, message) {
1737
actual = objectValues(actual);
1738
expected = objectValues(expected);
1739
this.pushResult({
1740
result: equiv(actual, expected),
1741
actual: actual,
1742
expected: expected,
1743
message: message
1744
});
1745
}
1746
}, {
1747
key: "notPropEqual",
1748
value: function notPropEqual(actual, expected, message) {
1749
actual = objectValues(actual);
1750
expected = objectValues(expected);
1751
this.pushResult({
1752
result: !equiv(actual, expected),
1753
actual: actual,
1754
expected: expected,
1755
message: message,
1756
negative: true
1757
});
1758
}
1759
}, {
1760
key: "propContains",
1761
value: function propContains(actual, expected, message) {
1762
actual = objectValuesSubset(actual, expected);
1763
1764
// The expected parameter is usually a plain object, but clone it for
1765
// consistency with propEqual(), and to make it easy to explain that
1766
// inheritence is not considered (on either side), and to support
1767
// recursively checking subsets of nested objects.
1768
expected = objectValues(expected, false);
1769
this.pushResult({
1770
result: equiv(actual, expected),
1771
actual: actual,
1772
expected: expected,
1773
message: message
1774
});
1775
}
1776
}, {
1777
key: "notPropContains",
1778
value: function notPropContains(actual, expected, message) {
1779
actual = objectValuesSubset(actual, expected);
1780
expected = objectValues(expected);
1781
this.pushResult({
1782
result: !equiv(actual, expected),
1783
actual: actual,
1784
expected: expected,
1785
message: message,
1786
negative: true
1787
});
1788
}
1789
}, {
1790
key: "deepEqual",
1791
value: function deepEqual(actual, expected, message) {
1792
this.pushResult({
1793
result: equiv(actual, expected),
1794
actual: actual,
1795
expected: expected,
1796
message: message
1797
});
1798
}
1799
}, {
1800
key: "notDeepEqual",
1801
value: function notDeepEqual(actual, expected, message) {
1802
this.pushResult({
1803
result: !equiv(actual, expected),
1804
actual: actual,
1805
expected: expected,
1806
message: message,
1807
negative: true
1808
});
1809
}
1810
}, {
1811
key: "strictEqual",
1812
value: function strictEqual(actual, expected, message) {
1813
this.pushResult({
1814
result: expected === actual,
1815
actual: actual,
1816
expected: expected,
1817
message: message
1818
});
1819
}
1820
}, {
1821
key: "notStrictEqual",
1822
value: function notStrictEqual(actual, expected, message) {
1823
this.pushResult({
1824
result: expected !== actual,
1825
actual: actual,
1826
expected: expected,
1827
message: message,
1828
negative: true
1829
});
1830
}
1831
}, {
1832
key: 'throws',
1833
value: function throws(block, expected, message) {
1834
var _validateExpectedExce = validateExpectedExceptionArgs(expected, message, 'throws');
1835
var _validateExpectedExce2 = _slicedToArray(_validateExpectedExce, 2);
1836
expected = _validateExpectedExce2[0];
1837
message = _validateExpectedExce2[1];
1838
var currentTest = this instanceof Assert && this.test || config.current;
1839
if (typeof block !== 'function') {
1840
currentTest.assert.pushResult({
1841
result: false,
1842
actual: block,
1843
message: 'The value provided to `assert.throws` in ' + '"' + currentTest.testName + '" was not a function.'
1844
});
1845
return;
1846
}
1847
var actual;
1848
var result = false;
1849
currentTest.ignoreGlobalErrors = true;
1850
try {
1851
block.call(currentTest.testEnvironment);
1852
} catch (e) {
1853
actual = e;
1854
}
1855
currentTest.ignoreGlobalErrors = false;
1856
if (actual) {
1857
var _validateException = validateException(actual, expected, message);
1858
var _validateException2 = _slicedToArray(_validateException, 3);
1859
result = _validateException2[0];
1860
expected = _validateException2[1];
1861
message = _validateException2[2];
1862
}
1863
currentTest.assert.pushResult({
1864
result: result,
1865
// undefined if it didn't throw
1866
actual: actual && errorString(actual),
1867
expected: expected,
1868
message: message
1869
});
1870
}
1871
}, {
1872
key: "rejects",
1873
value: function rejects(promise, expected, message) {
1874
var _validateExpectedExce3 = validateExpectedExceptionArgs(expected, message, 'rejects');
1875
var _validateExpectedExce4 = _slicedToArray(_validateExpectedExce3, 2);
1876
expected = _validateExpectedExce4[0];
1877
message = _validateExpectedExce4[1];
1878
var currentTest = this instanceof Assert && this.test || config.current;
1879
var then = promise && promise.then;
1880
if (typeof then !== 'function') {
1881
currentTest.assert.pushResult({
1882
result: false,
1883
message: 'The value provided to `assert.rejects` in ' + '"' + currentTest.testName + '" was not a promise.',
1884
actual: promise
1885
});
1886
return;
1887
}
1888
var done = this.async();
1889
return then.call(promise, function handleFulfillment() {
1890
currentTest.assert.pushResult({
1891
result: false,
1892
message: 'The promise returned by the `assert.rejects` callback in ' + '"' + currentTest.testName + '" did not reject.',
1893
actual: promise
1894
});
1895
done();
1896
}, function handleRejection(actual) {
1897
var result;
1898
var _validateException3 = validateException(actual, expected, message);
1899
var _validateException4 = _slicedToArray(_validateException3, 3);
1900
result = _validateException4[0];
1901
expected = _validateException4[1];
1902
message = _validateException4[2];
1903
currentTest.assert.pushResult({
1904
result: result,
1905
// leave rejection value of undefined as-is
1906
actual: actual && errorString(actual),
1907
expected: expected,
1908
message: message
1909
});
1910
done();
1911
});
1912
}
1913
}]);
1914
}();
1915
function validateExpectedExceptionArgs(expected, message, assertionMethod) {
1916
var expectedType = objectType(expected);
1917
1918
// 'expected' is optional unless doing string comparison
1919
if (expectedType === 'string') {
1920
if (message === undefined) {
1921
message = expected;
1922
expected = undefined;
1923
return [expected, message];
1924
} else {
1925
throw new Error('assert.' + assertionMethod + ' does not accept a string value for the expected argument.\n' + 'Use a non-string object value (e.g. RegExp or validator function) ' + 'instead if necessary.');
1926
}
1927
}
1928
var valid = !expected ||
1929
// TODO: be more explicit here
1930
expectedType === 'regexp' || expectedType === 'function' || expectedType === 'object';
1931
if (!valid) {
1932
throw new Error('Invalid expected value type (' + expectedType + ') ' + 'provided to assert.' + assertionMethod + '.');
1933
}
1934
return [expected, message];
1935
}
1936
function validateException(actual, expected, message) {
1937
var result = false;
1938
var expectedType = objectType(expected);
1939
1940
// These branches should be exhaustive, based on validation done in validateExpectedException
1941
1942
// We don't want to validate
1943
if (!expected) {
1944
result = true;
1945
1946
// Expected is a regexp
1947
} else if (expectedType === 'regexp') {
1948
result = expected.test(errorString(actual));
1949
1950
// Log the string form of the regexp
1951
expected = String(expected);
1952
1953
// Expected is a constructor, maybe an Error constructor.
1954
// Note the extra check on its prototype - this is an implicit
1955
// requirement of "instanceof", else it will throw a TypeError.
1956
} else if (expectedType === 'function' && expected.prototype !== undefined && actual instanceof expected) {
1957
result = true;
1958
1959
// Expected is an Error object
1960
} else if (expectedType === 'object') {
1961
result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
1962
1963
// Log the string form of the Error object
1964
expected = errorString(expected);
1965
1966
// Expected is a validation function which returns true if validation passed
1967
} else if (expectedType === 'function') {
1968
// protect against accidental semantics which could hard error in the test
1969
try {
1970
result = expected.call({}, actual) === true;
1971
expected = null;
1972
} catch (e) {
1973
// assign the "expected" to a nice error string to communicate the local failure to the user
1974
expected = errorString(e);
1975
}
1976
}
1977
return [result, expected, message];
1978
}
1979
1980
// Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1981
// Known to us are: Closure Compiler, Narwhal
1982
// eslint-disable-next-line dot-notation
1983
Assert.prototype.raises = Assert.prototype['throws'];
1984
1985
var LISTENERS = Object.create(null);
1986
var SUPPORTED_EVENTS = ['error', 'runStart', 'suiteStart', 'testStart', 'assertion', 'testEnd', 'suiteEnd', 'runEnd'];
1987
1988
/**
1989
* Emits an event with the specified data to all currently registered listeners.
1990
* Callbacks will fire in the order in which they are registered (FIFO). This
1991
* function is not exposed publicly; it is used by QUnit internals to emit
1992
* logging events.
1993
*
1994
* @private
1995
* @method emit
1996
* @param {string} eventName
1997
* @param {Object} data
1998
* @return {void}
1999
*/
2000
function emit(eventName, data) {
2001
if (typeof eventName !== 'string') {
2002
throw new TypeError('eventName must be a string when emitting an event');
2003
}
2004
2005
// Clone the callbacks in case one of them registers a new callback
2006
var originalCallbacks = LISTENERS[eventName];
2007
var callbacks = originalCallbacks ? _toConsumableArray(originalCallbacks) : [];
2008
for (var i = 0; i < callbacks.length; i++) {
2009
callbacks[i](data);
2010
}
2011
}
2012
2013
/**
2014
* Registers a callback as a listener to the specified event.
2015
*
2016
* @public
2017
* @method on
2018
* @param {string} eventName
2019
* @param {Function} callback
2020
* @return {void}
2021
*/
2022
function on(eventName, callback) {
2023
if (typeof eventName !== 'string') {
2024
throw new TypeError('eventName must be a string when registering a listener');
2025
} else if (!inArray(eventName, SUPPORTED_EVENTS)) {
2026
var events = SUPPORTED_EVENTS.join(', ');
2027
throw new Error("\"".concat(eventName, "\" is not a valid event; must be one of: ").concat(events, "."));
2028
} else if (typeof callback !== 'function') {
2029
throw new TypeError('callback must be a function when registering a listener');
2030
}
2031
if (!LISTENERS[eventName]) {
2032
LISTENERS[eventName] = [];
2033
}
2034
2035
// Don't register the same callback more than once
2036
if (!inArray(callback, LISTENERS[eventName])) {
2037
LISTENERS[eventName].push(callback);
2038
}
2039
}
2040
2041
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2042
2043
function commonjsRequire (path) {
2044
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
2045
}
2046
2047
var promisePolyfill = {exports: {}};
2048
2049
(function () {
2050
2051
/** @suppress {undefinedVars} */
2052
var globalNS = function () {
2053
// the only reliable means to get the global object is
2054
// `Function('return this')()`
2055
// However, this causes CSP violations in Chrome apps.
2056
if (typeof globalThis !== 'undefined') {
2057
return globalThis;
2058
}
2059
if (typeof self !== 'undefined') {
2060
return self;
2061
}
2062
if (typeof window !== 'undefined') {
2063
return window;
2064
}
2065
if (typeof commonjsGlobal !== 'undefined') {
2066
return commonjsGlobal;
2067
}
2068
throw new Error('unable to locate global object');
2069
}();
2070
2071
// Expose the polyfill if Promise is undefined or set to a
2072
// non-function value. The latter can be due to a named HTMLElement
2073
// being exposed by browsers for legacy reasons.
2074
// https://github.com/taylorhakes/promise-polyfill/issues/114
2075
if (typeof globalNS['Promise'] === 'function') {
2076
promisePolyfill.exports = globalNS['Promise'];
2077
return;
2078
}
2079
2080
/**
2081
* @this {Promise}
2082
*/
2083
function finallyConstructor(callback) {
2084
var constructor = this.constructor;
2085
return this.then(function (value) {
2086
// @ts-ignore
2087
return constructor.resolve(callback()).then(function () {
2088
return value;
2089
});
2090
}, function (reason) {
2091
// @ts-ignore
2092
return constructor.resolve(callback()).then(function () {
2093
// @ts-ignore
2094
return constructor.reject(reason);
2095
});
2096
});
2097
}
2098
function allSettled(arr) {
2099
var P = this;
2100
return new P(function (resolve, reject) {
2101
if (!(arr && typeof arr.length !== 'undefined')) {
2102
return reject(new TypeError(_typeof(arr) + ' ' + arr + ' is not iterable(cannot read property Symbol(Symbol.iterator))'));
2103
}
2104
var args = Array.prototype.slice.call(arr);
2105
if (args.length === 0) return resolve([]);
2106
var remaining = args.length;
2107
function res(i, val) {
2108
if (val && (_typeof(val) === 'object' || typeof val === 'function')) {
2109
var then = val.then;
2110
if (typeof then === 'function') {
2111
then.call(val, function (val) {
2112
res(i, val);
2113
}, function (e) {
2114
args[i] = {
2115
status: 'rejected',
2116
reason: e
2117
};
2118
if (--remaining === 0) {
2119
resolve(args);
2120
}
2121
});
2122
return;
2123
}
2124
}
2125
args[i] = {
2126
status: 'fulfilled',
2127
value: val
2128
};
2129
if (--remaining === 0) {
2130
resolve(args);
2131
}
2132
}
2133
for (var i = 0; i < args.length; i++) {
2134
res(i, args[i]);
2135
}
2136
});
2137
}
2138
2139
// Store setTimeout reference so promise-polyfill will be unaffected by
2140
// other code modifying setTimeout (like sinon.useFakeTimers())
2141
var setTimeoutFunc = setTimeout;
2142
function isArray(x) {
2143
return Boolean(x && typeof x.length !== 'undefined');
2144
}
2145
function noop() {}
2146
2147
// Polyfill for Function.prototype.bind
2148
function bind(fn, thisArg) {
2149
return function () {
2150
fn.apply(thisArg, arguments);
2151
};
2152
}
2153
2154
/**
2155
* @constructor
2156
* @param {Function} fn
2157
*/
2158
function Promise(fn) {
2159
if (!(this instanceof Promise)) throw new TypeError('Promises must be constructed via new');
2160
if (typeof fn !== 'function') throw new TypeError('not a function');
2161
/** @type {!number} */
2162
this._state = 0;
2163
/** @type {!boolean} */
2164
this._handled = false;
2165
/** @type {Promise|undefined} */
2166
this._value = undefined;
2167
/** @type {!Array<!Function>} */
2168
this._deferreds = [];
2169
doResolve(fn, this);
2170
}
2171
function handle(self, deferred) {
2172
while (self._state === 3) {
2173
self = self._value;
2174
}
2175
if (self._state === 0) {
2176
self._deferreds.push(deferred);
2177
return;
2178
}
2179
self._handled = true;
2180
Promise._immediateFn(function () {
2181
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
2182
if (cb === null) {
2183
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
2184
return;
2185
}
2186
var ret;
2187
try {
2188
ret = cb(self._value);
2189
} catch (e) {
2190
reject(deferred.promise, e);
2191
return;
2192
}
2193
resolve(deferred.promise, ret);
2194
});
2195
}
2196
function resolve(self, newValue) {
2197
try {
2198
// Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
2199
if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
2200
if (newValue && (_typeof(newValue) === 'object' || typeof newValue === 'function')) {
2201
var then = newValue.then;
2202
if (newValue instanceof Promise) {
2203
self._state = 3;
2204
self._value = newValue;
2205
finale(self);
2206
return;
2207
} else if (typeof then === 'function') {
2208
doResolve(bind(then, newValue), self);
2209
return;
2210
}
2211
}
2212
self._state = 1;
2213
self._value = newValue;
2214
finale(self);
2215
} catch (e) {
2216
reject(self, e);
2217
}
2218
}
2219
function reject(self, newValue) {
2220
self._state = 2;
2221
self._value = newValue;
2222
finale(self);
2223
}
2224
function finale(self) {
2225
if (self._state === 2 && self._deferreds.length === 0) {
2226
Promise._immediateFn(function () {
2227
if (!self._handled) {
2228
Promise._unhandledRejectionFn(self._value);
2229
}
2230
});
2231
}
2232
for (var i = 0, len = self._deferreds.length; i < len; i++) {
2233
handle(self, self._deferreds[i]);
2234
}
2235
self._deferreds = null;
2236
}
2237
2238
/**
2239
* @constructor
2240
*/
2241
function Handler(onFulfilled, onRejected, promise) {
2242
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
2243
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
2244
this.promise = promise;
2245
}
2246
2247
/**
2248
* Take a potentially misbehaving resolver function and make sure
2249
* onFulfilled and onRejected are only called once.
2250
*
2251
* Makes no guarantees about asynchrony.
2252
*/
2253
function doResolve(fn, self) {
2254
var done = false;
2255
try {
2256
fn(function (value) {
2257
if (done) return;
2258
done = true;
2259
resolve(self, value);
2260
}, function (reason) {
2261
if (done) return;
2262
done = true;
2263
reject(self, reason);
2264
});
2265
} catch (ex) {
2266
if (done) return;
2267
done = true;
2268
reject(self, ex);
2269
}
2270
}
2271
Promise.prototype['catch'] = function (onRejected) {
2272
return this.then(null, onRejected);
2273
};
2274
Promise.prototype.then = function (onFulfilled, onRejected) {
2275
// @ts-ignore
2276
var prom = new this.constructor(noop);
2277
handle(this, new Handler(onFulfilled, onRejected, prom));
2278
return prom;
2279
};
2280
Promise.prototype['finally'] = finallyConstructor;
2281
Promise.all = function (arr) {
2282
return new Promise(function (resolve, reject) {
2283
if (!isArray(arr)) {
2284
return reject(new TypeError('Promise.all accepts an array'));
2285
}
2286
var args = Array.prototype.slice.call(arr);
2287
if (args.length === 0) return resolve([]);
2288
var remaining = args.length;
2289
function res(i, val) {
2290
try {
2291
if (val && (_typeof(val) === 'object' || typeof val === 'function')) {
2292
var then = val.then;
2293
if (typeof then === 'function') {
2294
then.call(val, function (val) {
2295
res(i, val);
2296
}, reject);
2297
return;
2298
}
2299
}
2300
args[i] = val;
2301
if (--remaining === 0) {
2302
resolve(args);
2303
}
2304
} catch (ex) {
2305
reject(ex);
2306
}
2307
}
2308
for (var i = 0; i < args.length; i++) {
2309
res(i, args[i]);
2310
}
2311
});
2312
};
2313
Promise.allSettled = allSettled;
2314
Promise.resolve = function (value) {
2315
if (value && _typeof(value) === 'object' && value.constructor === Promise) {
2316
return value;
2317
}
2318
return new Promise(function (resolve) {
2319
resolve(value);
2320
});
2321
};
2322
Promise.reject = function (value) {
2323
return new Promise(function (resolve, reject) {
2324
reject(value);
2325
});
2326
};
2327
Promise.race = function (arr) {
2328
return new Promise(function (resolve, reject) {
2329
if (!isArray(arr)) {
2330
return reject(new TypeError('Promise.race accepts an array'));
2331
}
2332
for (var i = 0, len = arr.length; i < len; i++) {
2333
Promise.resolve(arr[i]).then(resolve, reject);
2334
}
2335
});
2336
};
2337
2338
// Use polyfill for setImmediate for performance gains
2339
// @ts-ignore
2340
if (typeof setImmediate === 'function') {
2341
// @ts-ignore
2342
var setImmediateFunc = setImmediate;
2343
Promise._immediateFn = function (fn) {
2344
setImmediateFunc(fn);
2345
};
2346
} else {
2347
Promise._immediateFn = function (fn) {
2348
setTimeoutFunc(fn, 0);
2349
};
2350
}
2351
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
2352
if (typeof console !== 'undefined' && console) {
2353
console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
2354
}
2355
};
2356
promisePolyfill.exports = Promise;
2357
})();
2358
var _Promise = promisePolyfill.exports;
2359
2360
// Register logging callbacks
2361
function registerLoggingCallbacks(obj) {
2362
var callbackNames = ['begin', 'done', 'log', 'testStart', 'testDone', 'moduleStart', 'moduleDone'];
2363
function registerLoggingCallback(key) {
2364
return function loggingCallback(callback) {
2365
if (typeof callback !== 'function') {
2366
throw new Error('Callback parameter must be a function');
2367
}
2368
config.callbacks[key].push(callback);
2369
};
2370
}
2371
for (var i = 0; i < callbackNames.length; i++) {
2372
var key = callbackNames[i];
2373
2374
// Initialize key collection of logging callback
2375
if (typeof config.callbacks[key] === 'undefined') {
2376
config.callbacks[key] = [];
2377
}
2378
obj[key] = registerLoggingCallback(key);
2379
}
2380
}
2381
function runLoggingCallbacks(key, args) {
2382
var callbacks = config.callbacks[key];
2383
2384
// Handling 'log' callbacks separately. Unlike the other callbacks,
2385
// the log callback is not controlled by the processing queue,
2386
// but rather used by asserts. Hence to promisfy the 'log' callback
2387
// would mean promisfying each step of a test
2388
if (key === 'log') {
2389
callbacks.map(function (callback) {
2390
return callback(args);
2391
});
2392
return;
2393
}
2394
2395
// ensure that each callback is executed serially
2396
var promiseChain = _Promise.resolve();
2397
callbacks.forEach(function (callback) {
2398
promiseChain = promiseChain.then(function () {
2399
return _Promise.resolve(callback(args));
2400
});
2401
});
2402
return promiseChain;
2403
}
2404
2405
var TestReport = /*#__PURE__*/function () {
2406
function TestReport(name, suite, options) {
2407
_classCallCheck(this, TestReport);
2408
this.name = name;
2409
this.suiteName = suite.name;
2410
this.fullName = suite.fullName.concat(name);
2411
this.runtime = 0;
2412
this.assertions = [];
2413
this.skipped = !!options.skip;
2414
this.todo = !!options.todo;
2415
this.valid = options.valid;
2416
this._startTime = 0;
2417
this._endTime = 0;
2418
suite.pushTest(this);
2419
}
2420
return _createClass(TestReport, [{
2421
key: "start",
2422
value: function start(recordTime) {
2423
if (recordTime) {
2424
this._startTime = performance.now();
2425
}
2426
return {
2427
name: this.name,
2428
suiteName: this.suiteName,
2429
fullName: this.fullName.slice()
2430
};
2431
}
2432
}, {
2433
key: "end",
2434
value: function end(recordTime) {
2435
if (recordTime) {
2436
this._endTime = performance.now();
2437
}
2438
return extend(this.start(), {
2439
runtime: this.getRuntime(),
2440
status: this.getStatus(),
2441
errors: this.getFailedAssertions(),
2442
assertions: this.getAssertions()
2443
});
2444
}
2445
}, {
2446
key: "pushAssertion",
2447
value: function pushAssertion(assertion) {
2448
this.assertions.push(assertion);
2449
}
2450
}, {
2451
key: "getRuntime",
2452
value: function getRuntime() {
2453
return Math.round(this._endTime - this._startTime);
2454
}
2455
}, {
2456
key: "getStatus",
2457
value: function getStatus() {
2458
if (this.skipped) {
2459
return 'skipped';
2460
}
2461
var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
2462
if (!testPassed) {
2463
return 'failed';
2464
} else if (this.todo) {
2465
return 'todo';
2466
} else {
2467
return 'passed';
2468
}
2469
}
2470
}, {
2471
key: "getFailedAssertions",
2472
value: function getFailedAssertions() {
2473
return this.assertions.filter(function (assertion) {
2474
return !assertion.passed;
2475
});
2476
}
2477
}, {
2478
key: "getAssertions",
2479
value: function getAssertions() {
2480
return this.assertions.slice();
2481
}
2482
2483
// Remove actual and expected values from assertions. This is to prevent
2484
// leaking memory throughout a test suite.
2485
}, {
2486
key: "slimAssertions",
2487
value: function slimAssertions() {
2488
this.assertions = this.assertions.map(function (assertion) {
2489
delete assertion.actual;
2490
delete assertion.expected;
2491
return assertion;
2492
});
2493
}
2494
}]);
2495
}();
2496
2497
function Test(settings) {
2498
this.expected = null;
2499
this.assertions = [];
2500
this.module = config.currentModule;
2501
this.steps = [];
2502
// This powers the QUnit.config.countStepsAsOne feature.
2503
// https://github.com/qunitjs/qunit/pull/1775
2504
this.stepsCount = 0;
2505
this.timeout = undefined;
2506
this.data = undefined;
2507
this.withData = false;
2508
this.pauses = new StringMap();
2509
this.nextPauseId = 1;
2510
2511
// For the most common case, we have:
2512
// - 0: new Test
2513
// - 1: addTest
2514
// - 2: QUnit.test
2515
// - 3: user file
2516
//
2517
// This needs is customised by test.each()
2518
this.stackOffset = 3;
2519
extend(this, settings);
2520
2521
// If a module is skipped, all its tests and the tests of the child suites
2522
// should be treated as skipped even if they are defined as `only` or `todo`.
2523
// As for `todo` module, all its tests will be treated as `todo` except for
2524
// tests defined as `skip` which will be left intact.
2525
//
2526
// So, if a test is defined as `todo` and is inside a skipped module, we should
2527
// then treat that test as if was defined as `skip`.
2528
if (this.module.skip) {
2529
this.skip = true;
2530
this.todo = false;
2531
2532
// Skipped tests should be left intact
2533
} else if (this.module.todo && !this.skip) {
2534
this.todo = true;
2535
}
2536
2537
// Queuing a late test after the run has ended is not allowed.
2538
// This was once supported for internal use by QUnit.onError().
2539
// Ref https://github.com/qunitjs/qunit/issues/1377
2540
if (config.pq.finished) {
2541
// Using this for anything other than onError(), such as testing in QUnit.done(),
2542
// is unstable and will likely result in the added tests being ignored by CI.
2543
// (Meaning the CI passes irregardless of the added tests).
2544
//
2545
// TODO: Make this an error in QUnit 3.0
2546
// throw new Error( "Unexpected test after runEnd" );
2547
Logger.warn('Unexpected test after runEnd. This is unstable and will fail in QUnit 3.0.');
2548
return;
2549
}
2550
if (!this.skip && typeof this.callback !== 'function') {
2551
var method = this.todo ? 'QUnit.todo' : 'QUnit.test';
2552
throw new TypeError("You must provide a callback to ".concat(method, "(\"").concat(this.testName, "\")"));
2553
}
2554
2555
// Register unique strings
2556
for (var i = 0, l = this.module.tests; i < l.length; i++) {
2557
if (this.module.tests[i].name === this.testName) {
2558
this.testName += ' ';
2559
}
2560
}
2561
this.testId = generateHash(this.module.name, this.testName);
2562
2563
// No validation after this. Beyond this point, failures must be recorded as
2564
// a completed test with errors, instead of early bail out.
2565
// Otherwise, internals may be left in an inconsistent state.
2566
// Ref https://github.com/qunitjs/qunit/issues/1514
2567
2568
++Test.count;
2569
this.errorForStack = new Error();
2570
if (this.callback && this.callback.validTest) {
2571
// Omit the test-level trace for the internal "No tests" test failure,
2572
// There is already an assertion-level trace, and that's noisy enough
2573
// as it is.
2574
this.errorForStack.stack = undefined;
2575
}
2576
this.testReport = new TestReport(this.testName, this.module.suiteReport, {
2577
todo: this.todo,
2578
skip: this.skip,
2579
valid: this.valid()
2580
});
2581
this.module.tests.push({
2582
name: this.testName,
2583
testId: this.testId,
2584
skip: !!this.skip
2585
});
2586
if (this.skip) {
2587
// Skipped tests will fully ignore (and dereference for garbage collect) any sent callback
2588
this.callback = function () {};
2589
this.async = false;
2590
this.expected = 0;
2591
} else {
2592
this.assert = new Assert(this);
2593
}
2594
}
2595
Test.count = 0;
2596
function getNotStartedModules(startModule) {
2597
var module = startModule;
2598
var modules = [];
2599
while (module && module.testsRun === 0) {
2600
modules.push(module);
2601
module = module.parentModule;
2602
}
2603
2604
// The above push modules from the child to the parent
2605
// return a reversed order with the top being the top most parent module
2606
return modules.reverse();
2607
}
2608
Test.prototype = {
2609
// Use a getter to avoid computing a stack trace (which can be expensive),
2610
// This is displayed by the HTML Reporter, but most other integrations do
2611
// not access it.
2612
get stack() {
2613
return extractStacktrace(this.errorForStack, this.stackOffset);
2614
},
2615
before: function before() {
2616
var _this = this;
2617
var module = this.module;
2618
var notStartedModules = getNotStartedModules(module);
2619
2620
// ensure the callbacks are executed serially for each module
2621
var moduleStartChain = _Promise.resolve();
2622
notStartedModules.forEach(function (startModule) {
2623
moduleStartChain = moduleStartChain.then(function () {
2624
startModule.stats = {
2625
all: 0,
2626
bad: 0,
2627
started: performance.now()
2628
};
2629
emit('suiteStart', startModule.suiteReport.start(true));
2630
return runLoggingCallbacks('moduleStart', {
2631
name: startModule.name,
2632
tests: startModule.tests
2633
});
2634
});
2635
});
2636
return moduleStartChain.then(function () {
2637
config.current = _this;
2638
_this.testEnvironment = extend({}, module.testEnvironment);
2639
_this.started = performance.now();
2640
emit('testStart', _this.testReport.start(true));
2641
return runLoggingCallbacks('testStart', {
2642
name: _this.testName,
2643
module: module.name,
2644
testId: _this.testId,
2645
previousFailure: _this.previousFailure
2646
}).then(function () {
2647
if (!config.pollution) {
2648
saveGlobal();
2649
}
2650
});
2651
});
2652
},
2653
run: function run() {
2654
config.current = this;
2655
if (config.notrycatch) {
2656
runTest(this);
2657
return;
2658
}
2659
try {
2660
runTest(this);
2661
} catch (e) {
2662
this.pushFailure('Died on test #' + (this.assertions.length + 1) + ': ' + (e.message || e) + '\n' + this.stack, extractStacktrace(e, 0));
2663
2664
// Else next test will carry the responsibility
2665
saveGlobal();
2666
2667
// Restart the tests if they're blocking
2668
if (config.blocking) {
2669
internalRecover(this);
2670
}
2671
}
2672
function runTest(test) {
2673
var promise;
2674
if (test.withData) {
2675
promise = test.callback.call(test.testEnvironment, test.assert, test.data);
2676
} else {
2677
promise = test.callback.call(test.testEnvironment, test.assert);
2678
}
2679
test.resolvePromise(promise);
2680
2681
// If the test has an async "pause" on it, but the timeout is 0, then we push a
2682
// failure as the test should be synchronous.
2683
if (test.timeout === 0 && test.pauses.size > 0) {
2684
pushFailure('Test did not finish synchronously even though assert.timeout( 0 ) was used.', sourceFromStacktrace(2));
2685
}
2686
}
2687
},
2688
after: function after() {
2689
checkPollution();
2690
},
2691
queueGlobalHook: function queueGlobalHook(hook, hookName) {
2692
var _this2 = this;
2693
var runHook = function runHook() {
2694
config.current = _this2;
2695
var promise;
2696
if (config.notrycatch) {
2697
promise = hook.call(_this2.testEnvironment, _this2.assert);
2698
} else {
2699
try {
2700
promise = hook.call(_this2.testEnvironment, _this2.assert);
2701
} catch (error) {
2702
_this2.pushFailure('Global ' + hookName + ' failed on ' + _this2.testName + ': ' + errorString(error), extractStacktrace(error, 0));
2703
return;
2704
}
2705
}
2706
_this2.resolvePromise(promise, hookName);
2707
};
2708
return runHook;
2709
},
2710
queueHook: function queueHook(hook, hookName, hookOwner) {
2711
var _this3 = this;
2712
var callHook = function callHook() {
2713
var promise = hook.call(_this3.testEnvironment, _this3.assert);
2714
_this3.resolvePromise(promise, hookName);
2715
};
2716
var runHook = function runHook() {
2717
if (hookName === 'before') {
2718
if (hookOwner.testsRun !== 0) {
2719
return;
2720
}
2721
_this3.preserveEnvironment = true;
2722
}
2723
2724
// The 'after' hook should only execute when there are not tests left and
2725
// when the 'after' and 'finish' tasks are the only tasks left to process
2726
if (hookName === 'after' && !lastTestWithinModuleExecuted(hookOwner) && (config.queue.length > 0 || config.pq.taskCount() > 2)) {
2727
return;
2728
}
2729
config.current = _this3;
2730
if (config.notrycatch) {
2731
callHook();
2732
return;
2733
}
2734
try {
2735
// This try-block includes the indirect call to resolvePromise, which shouldn't
2736
// have to be inside try-catch. But, since we support any user-provided thenable
2737
// object, the thenable might throw in some unexpected way.
2738
// This subtle behaviour is undocumented. To avoid new failures in minor releases
2739
// we will not change this until QUnit 3.
2740
// TODO: In QUnit 3, reduce this try-block to just hook.call(), matching
2741
// the simplicity of queueGlobalHook.
2742
callHook();
2743
} catch (error) {
2744
_this3.pushFailure(hookName + ' failed on ' + _this3.testName + ': ' + (error.message || error), extractStacktrace(error, 0));
2745
}
2746
};
2747
return runHook;
2748
},
2749
// Currently only used for module level hooks, can be used to add global level ones
2750
hooks: function hooks(handler) {
2751
var hooks = [];
2752
function processGlobalhooks(test) {
2753
if ((handler === 'beforeEach' || handler === 'afterEach') && config.globalHooks[handler]) {
2754
for (var i = 0; i < config.globalHooks[handler].length; i++) {
2755
hooks.push(test.queueGlobalHook(config.globalHooks[handler][i], handler));
2756
}
2757
}
2758
}
2759
function processHooks(test, module) {
2760
if (module.parentModule) {
2761
processHooks(test, module.parentModule);
2762
}
2763
if (module.hooks[handler].length) {
2764
for (var i = 0; i < module.hooks[handler].length; i++) {
2765
hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
2766
}
2767
}
2768
}
2769
2770
// Hooks are ignored on skipped tests
2771
if (!this.skip) {
2772
processGlobalhooks(this);
2773
processHooks(this, this.module);
2774
}
2775
return hooks;
2776
},
2777
finish: function finish() {
2778
config.current = this;
2779
2780
// Release the timeout and timeout callback references to be garbage collected.
2781
// https://github.com/qunitjs/qunit/pull/1708
2782
if (setTimeout$1) {
2783
clearTimeout(this.timeout);
2784
config.timeoutHandler = null;
2785
}
2786
2787
// Release the test callback to ensure that anything referenced has been
2788
// released to be garbage collected.
2789
this.callback = undefined;
2790
if (this.steps.length) {
2791
var stepsList = this.steps.join(', ');
2792
this.pushFailure('Expected assert.verifySteps() to be called before end of test ' + "after using assert.step(). Unverified steps: ".concat(stepsList), this.stack);
2793
}
2794
if (!config._deprecated_countEachStep_shown && !config.countStepsAsOne && this.expected !== null && this.stepsCount) {
2795
config._deprecated_countEachStep_shown = true;
2796
if (config.requireExpects) {
2797
Logger.warn('Counting each assert.step() for assert.expect() is changing in QUnit 3.0. You can enable QUnit.config.countStepsAsOne to prepare for the upgrade. https://qunitjs.com/api/assert/expect/');
2798
} else {
2799
Logger.warn('Counting each assert.step() for assert.expect() is changing in QUnit 3.0. Omit assert.expect() from tests that use assert.step(), or enable QUnit.config.countStepsAsOne to prepare for the upgrade. https://qunitjs.com/api/assert/expect/');
2800
}
2801
}
2802
var actualCountForExpect = config.countStepsAsOne ? this.assertions.length - this.stepsCount : this.assertions.length;
2803
if (config.requireExpects && this.expected === null) {
2804
this.pushFailure('Expected number of assertions to be defined, but expect() was ' + 'not called.', this.stack);
2805
} else if (this.expected !== null && this.expected !== actualCountForExpect && this.stepsCount && this.expected === this.assertions.length - this.stepsCount && !config.countStepsAsOne) {
2806
this.pushFailure('Expected ' + this.expected + ' assertions, but ' + actualCountForExpect + ' were run\nIt looks like you might prefer to enable QUnit.config.countStepsAsOne, which will become the default in QUnit 3.0. https://qunitjs.com/api/assert/expect/', this.stack);
2807
} else if (this.expected !== null && this.expected !== actualCountForExpect && this.stepsCount && this.expected === this.assertions.length && config.countStepsAsOne) {
2808
this.pushFailure('Expected ' + this.expected + ' assertions, but ' + actualCountForExpect + ' were run\nRemember that with QUnit.config.countStepsAsOne and in QUnit 3.0, steps no longer count as separate assertions. https://qunitjs.com/api/assert/expect/', this.stack);
2809
} else if (this.expected !== null && this.expected !== actualCountForExpect) {
2810
this.pushFailure('Expected ' + this.expected + ' assertions, but ' + actualCountForExpect + ' were run', this.stack);
2811
} else if (this.expected === null && !actualCountForExpect) {
2812
this.pushFailure('Expected at least one assertion, but none were run - call ' + 'expect(0) to accept zero assertions.', this.stack);
2813
}
2814
var module = this.module;
2815
var moduleName = module.name;
2816
var testName = this.testName;
2817
var skipped = !!this.skip;
2818
var todo = !!this.todo;
2819
var bad = 0;
2820
var storage = config.storage;
2821
this.runtime = Math.round(performance.now() - this.started);
2822
config.stats.all += this.assertions.length;
2823
config.stats.testCount += 1;
2824
module.stats.all += this.assertions.length;
2825
for (var i = 0; i < this.assertions.length; i++) {
2826
// A failing assertion will counts toward the HTML Reporter's
2827
// "X assertions, Y failed" line even if it was inside a todo.
2828
// Inverting this would be similarly confusing since all but the last
2829
// passing assertion inside a todo test should be considered as good.
2830
// These stats don't decide the outcome of anything, so counting them
2831
// as failing seems the most intuitive.
2832
if (!this.assertions[i].result) {
2833
bad++;
2834
config.stats.bad++;
2835
module.stats.bad++;
2836
}
2837
}
2838
if (skipped) {
2839
incrementTestsIgnored(module);
2840
} else {
2841
incrementTestsRun(module);
2842
}
2843
2844
// Store result when possible.
2845
// Note that this also marks todo tests as bad, thus they get hoisted,
2846
// and always run first on refresh.
2847
if (storage) {
2848
if (bad) {
2849
storage.setItem('qunit-test-' + moduleName + '-' + testName, bad);
2850
} else {
2851
storage.removeItem('qunit-test-' + moduleName + '-' + testName);
2852
}
2853
}
2854
2855
// After emitting the js-reporters event we cleanup the assertion data to
2856
// avoid leaking it. It is not used by the legacy testDone callbacks.
2857
emit('testEnd', this.testReport.end(true));
2858
this.testReport.slimAssertions();
2859
var test = this;
2860
return runLoggingCallbacks('testDone', {
2861
name: testName,
2862
module: moduleName,
2863
skipped: skipped,
2864
todo: todo,
2865
failed: bad,
2866
passed: this.assertions.length - bad,
2867
total: this.assertions.length,
2868
runtime: skipped ? 0 : this.runtime,
2869
// HTML Reporter use
2870
assertions: this.assertions,
2871
testId: this.testId,
2872
// Source of Test
2873
// generating stack trace is expensive, so using a getter will help defer this until we need it
2874
get source() {
2875
return test.stack;
2876
}
2877
}).then(function () {
2878
if (allTestsExecuted(module)) {
2879
var completedModules = [module];
2880
2881
// Check if the parent modules, iteratively, are done. If that the case,
2882
// we emit the `suiteEnd` event and trigger `moduleDone` callback.
2883
var parent = module.parentModule;
2884
while (parent && allTestsExecuted(parent)) {
2885
completedModules.push(parent);
2886
parent = parent.parentModule;
2887
}
2888
var moduleDoneChain = _Promise.resolve();
2889
completedModules.forEach(function (completedModule) {
2890
moduleDoneChain = moduleDoneChain.then(function () {
2891
return logSuiteEnd(completedModule);
2892
});
2893
});
2894
return moduleDoneChain;
2895
}
2896
}).then(function () {
2897
config.current = undefined;
2898
});
2899
function logSuiteEnd(module) {
2900
// Reset `module.hooks` to ensure that anything referenced in these hooks
2901
// has been released to be garbage collected. Descendant modules that were
2902
// entirely skipped, e.g. due to filtering, will never have this method
2903
// called for them, but might have hooks with references pinning data in
2904
// memory (even if the hooks weren't actually executed), so we reset the
2905
// hooks on all descendant modules here as well. This is safe because we
2906
// will never call this as long as any descendant modules still have tests
2907
// to run. This also means that in multi-tiered nesting scenarios we might
2908
// reset the hooks multiple times on some modules, but that's harmless.
2909
var modules = [module];
2910
while (modules.length) {
2911
var nextModule = modules.shift();
2912
nextModule.hooks = {};
2913
modules.push.apply(modules, _toConsumableArray(nextModule.childModules));
2914
}
2915
emit('suiteEnd', module.suiteReport.end(true));
2916
return runLoggingCallbacks('moduleDone', {
2917
name: module.name,
2918
tests: module.tests,
2919
failed: module.stats.bad,
2920
passed: module.stats.all - module.stats.bad,
2921
total: module.stats.all,
2922
runtime: Math.round(performance.now() - module.stats.started)
2923
});
2924
}
2925
},
2926
preserveTestEnvironment: function preserveTestEnvironment() {
2927
if (this.preserveEnvironment) {
2928
this.module.testEnvironment = this.testEnvironment;
2929
this.testEnvironment = extend({}, this.module.testEnvironment);
2930
}
2931
},
2932
queue: function queue() {
2933
var test = this;
2934
if (!this.valid()) {
2935
incrementTestsIgnored(this.module);
2936
return;
2937
}
2938
function runTest() {
2939
return [function () {
2940
return test.before();
2941
}].concat(_toConsumableArray(test.hooks('before')), [function () {
2942
test.preserveTestEnvironment();
2943
}], _toConsumableArray(test.hooks('beforeEach')), [function () {
2944
test.run();
2945
}], _toConsumableArray(test.hooks('afterEach').reverse()), _toConsumableArray(test.hooks('after').reverse()), [function () {
2946
test.after();
2947
}, function () {
2948
return test.finish();
2949
}]);
2950
}
2951
var previousFailCount = config.storage && +config.storage.getItem('qunit-test-' + this.module.name + '-' + this.testName);
2952
2953
// Prioritize previously failed tests, detected from storage
2954
var prioritize = config.reorder && !!previousFailCount;
2955
this.previousFailure = !!previousFailCount;
2956
config.pq.add(runTest, prioritize);
2957
},
2958
pushResult: function pushResult(resultInfo) {
2959
if (this !== config.current) {
2960
var message = resultInfo && resultInfo.message || '';
2961
var testName = this && this.testName || '';
2962
var error = 'Assertion occurred after test finished.\n' + '> Test: ' + testName + '\n' + '> Message: ' + message + '\n';
2963
throw new Error(error);
2964
}
2965
2966
// Destructure of resultInfo = { result, actual, expected, message, negative }
2967
var details = {
2968
module: this.module.name,
2969
name: this.testName,
2970
result: resultInfo.result,
2971
message: resultInfo.message,
2972
actual: resultInfo.actual,
2973
testId: this.testId,
2974
negative: resultInfo.negative || false,
2975
runtime: Math.round(performance.now() - this.started),
2976
todo: !!this.todo
2977
};
2978
if (hasOwn$1.call(resultInfo, 'expected')) {
2979
details.expected = resultInfo.expected;
2980
}
2981
if (!resultInfo.result) {
2982
var source = resultInfo.source || sourceFromStacktrace();
2983
if (source) {
2984
details.source = source;
2985
}
2986
}
2987
this.logAssertion(details);
2988
this.assertions.push({
2989
result: !!resultInfo.result,
2990
message: resultInfo.message
2991
});
2992
},
2993
pushFailure: function pushFailure(message, source) {
2994
if (!(this instanceof Test)) {
2995
throw new Error('pushFailure() assertion outside test context, was ' + sourceFromStacktrace(2));
2996
}
2997
this.pushResult({
2998
result: false,
2999
message: message || 'error',
3000
source: source
3001
});
3002
},
3003
/**
3004
* Log assertion details using both the old QUnit.log interface and
3005
* QUnit.on( "assertion" ) interface.
3006
*
3007
* @private
3008
*/
3009
logAssertion: function logAssertion(details) {
3010
runLoggingCallbacks('log', details);
3011
var assertion = {
3012
passed: details.result,
3013
actual: details.actual,
3014
expected: details.expected,
3015
message: details.message,
3016
stack: details.source,
3017
todo: details.todo
3018
};
3019
this.testReport.pushAssertion(assertion);
3020
emit('assertion', assertion);
3021
},
3022
/**
3023
* Reset config.timeout with a new timeout duration.
3024
*
3025
* @param {number} timeoutDuration
3026
*/
3027
internalResetTimeout: function internalResetTimeout(timeoutDuration) {
3028
clearTimeout(config.timeout);
3029
config.timeout = setTimeout$1(config.timeoutHandler(timeoutDuration), timeoutDuration);
3030
},
3031
/**
3032
* Create a new async pause and return a new function that can release the pause.
3033
*
3034
* This mechanism is internally used by:
3035
*
3036
* - explicit async pauses, created by calling `assert.async()`,
3037
* - implicit async pauses, created when `QUnit.test()` or module hook callbacks
3038
* use async-await or otherwise return a Promise.
3039
*
3040
* Happy scenario:
3041
*
3042
* - Pause is created by calling internalStop().
3043
*
3044
* Pause is released normally by invoking release() during the same test.
3045
*
3046
* The release() callback lets internal processing resume.
3047
*
3048
* Failure scenarios:
3049
*
3050
* - The test fails due to an uncaught exception.
3051
*
3052
* In this case, Test.run() will call internalRecover() which empties the clears all
3053
* async pauses and sets the cancelled flag, which means we silently ignore any
3054
* late calls to the resume() callback, as we will have moved on to a different
3055
* test by then, and we don't want to cause an extra "release during a different test"
3056
* errors that the developer isn't really responsible for. This can happen when a test
3057
* correctly schedules a call to release(), but also causes an uncaught error. The
3058
* uncaught error means we will no longer wait for the release (as it might not arrive).
3059
*
3060
* - Pause is never released, or called an insufficient number of times.
3061
*
3062
* Our timeout handler will kill the pause and resume test processing, basically
3063
* like internalRecover(), but for one pause instead of any/all.
3064
*
3065
* Here, too, any late calls to resume() will be silently ignored to avoid
3066
* extra errors. We tolerate this since the original test will have already been
3067
* marked as failure.
3068
*
3069
* TODO: QUnit 3 will enable timeouts by default <https://github.com/qunitjs/qunit/issues/1483>,
3070
* but right now a test will hang indefinitely if async pauses are not released,
3071
* unless QUnit.config.testTimeout or assert.timeout() is used.
3072
*
3073
* - Pause is spontaneously released during a different test,
3074
* or when no test is currently running.
3075
*
3076
* This is close to impossible because this error only happens if the original test
3077
* succesfully finished first (since other failure scenarios kill pauses and ignore
3078
* late calls). It can happen if a test ended exactly as expected, but has some
3079
* external or shared state continuing to hold a reference to the release callback,
3080
* and either the same test scheduled another call to it in the future, or a later test
3081
* causes it to be called through some shared state.
3082
*
3083
* - Pause release() is called too often, during the same test.
3084
*
3085
* This simply throws an error, after which uncaught error handling picks it up
3086
* and processing resumes.
3087
*
3088
* @param {number} [requiredCalls=1]
3089
*/
3090
internalStop: function internalStop() {
3091
var requiredCalls = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
3092
config.blocking = true;
3093
var test = this;
3094
var pauseId = this.nextPauseId++;
3095
var pause = {
3096
cancelled: false,
3097
remaining: requiredCalls
3098
};
3099
test.pauses.set(pauseId, pause);
3100
function release() {
3101
if (pause.cancelled) {
3102
return;
3103
}
3104
if (config.current === undefined) {
3105
throw new Error('Unexpected release of async pause after tests finished.\n' + "> Test: ".concat(test.testName, " [async #").concat(pauseId, "]"));
3106
}
3107
if (config.current !== test) {
3108
throw new Error('Unexpected release of async pause during a different test.\n' + "> Test: ".concat(test.testName, " [async #").concat(pauseId, "]"));
3109
}
3110
if (pause.remaining <= 0) {
3111
throw new Error('Tried to release async pause that was already released.\n' + "> Test: ".concat(test.testName, " [async #").concat(pauseId, "]"));
3112
}
3113
3114
// The `requiredCalls` parameter exists to support `assert.async(count)`
3115
pause.remaining--;
3116
if (pause.remaining === 0) {
3117
test.pauses.delete(pauseId);
3118
}
3119
internalStart(test);
3120
}
3121
3122
// Set a recovery timeout, if so configured.
3123
if (setTimeout$1) {
3124
var timeoutDuration;
3125
if (typeof test.timeout === 'number') {
3126
timeoutDuration = test.timeout;
3127
} else if (typeof config.testTimeout === 'number') {
3128
timeoutDuration = config.testTimeout;
3129
}
3130
if (typeof timeoutDuration === 'number' && timeoutDuration > 0) {
3131
config.timeoutHandler = function (timeout) {
3132
return function () {
3133
config.timeout = null;
3134
pause.cancelled = true;
3135
test.pauses.delete(pauseId);
3136
test.pushFailure("Test took longer than ".concat(timeout, "ms; test timed out."), sourceFromStacktrace(2));
3137
internalRecover(test);
3138
};
3139
};
3140
clearTimeout(config.timeout);
3141
config.timeout = setTimeout$1(config.timeoutHandler(timeoutDuration), timeoutDuration);
3142
} else {
3143
clearTimeout(config.timeout);
3144
config.timeout = setTimeout$1(function () {
3145
config.timeout = null;
3146
if (!config._deprecated_timeout_shown) {
3147
config._deprecated_timeout_shown = true;
3148
Logger.warn("Test \"".concat(test.testName, "\" took longer than 3000ms, but no timeout was set. Set QUnit.config.testTimeout or call assert.timeout() to avoid a timeout in QUnit 3. https://qunitjs.com/api/config/testTimeout/"));
3149
}
3150
}, 3000);
3151
}
3152
}
3153
return release;
3154
},
3155
resolvePromise: function resolvePromise(promise, phase) {
3156
if (promise != null) {
3157
var _test = this;
3158
var then = promise.then;
3159
if (typeof then === 'function') {
3160
var resume = _test.internalStop();
3161
var resolve = function resolve() {
3162
resume();
3163
};
3164
if (config.notrycatch) {
3165
then.call(promise, resolve);
3166
} else {
3167
var reject = function reject(error) {
3168
var message = 'Promise rejected ' + (!phase ? 'during' : phase.replace(/Each$/, '')) + ' "' + _test.testName + '": ' + (error && error.message || error);
3169
_test.pushFailure(message, extractStacktrace(error, 0));
3170
3171
// Else next test will carry the responsibility
3172
saveGlobal();
3173
3174
// Unblock
3175
internalRecover(_test);
3176
};
3177
then.call(promise, resolve, reject);
3178
}
3179
}
3180
}
3181
},
3182
valid: function valid() {
3183
// Internally-generated tests are always valid
3184
if (this.callback && this.callback.validTest) {
3185
return true;
3186
}
3187
function moduleChainIdMatch(testModule, selectedId) {
3188
return (
3189
// undefined or empty array
3190
!selectedId || !selectedId.length || inArray(testModule.moduleId, selectedId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule, selectedId)
3191
);
3192
}
3193
if (!moduleChainIdMatch(this.module, config.moduleId)) {
3194
return false;
3195
}
3196
if (config.testId && config.testId.length && !inArray(this.testId, config.testId)) {
3197
return false;
3198
}
3199
function moduleChainNameMatch(testModule, selectedModule) {
3200
if (!selectedModule) {
3201
// undefined or empty string
3202
return true;
3203
}
3204
var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
3205
if (testModuleName === selectedModule) {
3206
return true;
3207
} else if (testModule.parentModule) {
3208
return moduleChainNameMatch(testModule.parentModule, selectedModule);
3209
} else {
3210
return false;
3211
}
3212
}
3213
var selectedModule = config.module && config.module.toLowerCase();
3214
if (!moduleChainNameMatch(this.module, selectedModule)) {
3215
return false;
3216
}
3217
var filter = config.filter;
3218
if (!filter) {
3219
return true;
3220
}
3221
var regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter);
3222
var fullName = this.module.name + ': ' + this.testName;
3223
return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
3224
},
3225
regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
3226
var regex = new RegExp(pattern, flags);
3227
var match = regex.test(fullName);
3228
return match !== exclude;
3229
},
3230
stringFilter: function stringFilter(filter, fullName) {
3231
filter = filter.toLowerCase();
3232
fullName = fullName.toLowerCase();
3233
var include = filter.charAt(0) !== '!';
3234
if (!include) {
3235
filter = filter.slice(1);
3236
}
3237
3238
// If the filter matches, we need to honour include
3239
if (fullName.indexOf(filter) !== -1) {
3240
return include;
3241
}
3242
3243
// Otherwise, do the opposite
3244
return !include;
3245
}
3246
};
3247
function pushFailure() {
3248
if (!config.current) {
3249
throw new Error('pushFailure() assertion outside test context, in ' + sourceFromStacktrace(2));
3250
}
3251
3252
// Gets current test obj
3253
var currentTest = config.current;
3254
return currentTest.pushFailure.apply(currentTest, arguments);
3255
}
3256
function saveGlobal() {
3257
config.pollution = [];
3258
if (config.noglobals) {
3259
for (var key in g) {
3260
if (hasOwn$1.call(g, key)) {
3261
// In Opera sometimes DOM element ids show up here, ignore them
3262
if (/^qunit-test-output/.test(key)) {
3263
continue;
3264
}
3265
config.pollution.push(key);
3266
}
3267
}
3268
}
3269
}
3270
function checkPollution() {
3271
var old = config.pollution;
3272
saveGlobal();
3273
var newGlobals = diff$1(config.pollution, old);
3274
if (newGlobals.length > 0) {
3275
pushFailure('Introduced global variable(s): ' + newGlobals.join(', '));
3276
}
3277
var deletedGlobals = diff$1(old, config.pollution);
3278
if (deletedGlobals.length > 0) {
3279
pushFailure('Deleted global variable(s): ' + deletedGlobals.join(', '));
3280
}
3281
}
3282
var focused = false; // indicates that the "only" filter was used
3283
3284
function addTest(settings) {
3285
if (focused || config.currentModule.ignored) {
3286
return;
3287
}
3288
var newTest = new Test(settings);
3289
newTest.queue();
3290
}
3291
function addOnlyTest(settings) {
3292
if (config.currentModule.ignored) {
3293
return;
3294
}
3295
if (!focused) {
3296
config.queue.length = 0;
3297
focused = true;
3298
}
3299
var newTest = new Test(settings);
3300
newTest.queue();
3301
}
3302
3303
// Will be exposed as QUnit.test
3304
function test(testName, callback) {
3305
addTest({
3306
testName: testName,
3307
callback: callback
3308
});
3309
}
3310
function makeEachTestName(testName, argument) {
3311
return "".concat(testName, " [").concat(argument, "]");
3312
}
3313
3314
// Characters to avoid in test names especially CLI/AP output:
3315
// * x00-1F: e.g. NULL, backspace (\b), line breaks (\r\n), ESC.
3316
// * x74: DEL.
3317
// * xA0: non-breaking space.
3318
//
3319
// See https://en.wikipedia.org/wiki/ASCII#Character_order
3320
//
3321
// eslint-disable-next-line no-control-regex
3322
var rNonObviousStr = /[\x00-\x1F\x7F\xA0]/;
3323
function runEach(data, eachFn) {
3324
if (Array.isArray(data)) {
3325
for (var i = 0; i < data.length; i++) {
3326
var value = data[i];
3327
3328
// Create automatic labels for primitive data in arrays passed to test.each().
3329
// We want to avoid the default "example [0], example [1]" where possible since
3330
// these are not self-explanatory in results, and are also tedious to locate
3331
// the source of since the numerical key of an array isn't literally in the
3332
// code (you have to count).
3333
//
3334
// Design requirements:
3335
// * Unique. Each label must be unique and correspond 1:1 with a data value.
3336
// This way each test name will hash to a unique testId with Rerun link,
3337
// without having to rely on Test class enforcing uniqueness with invisible
3338
// space hack.
3339
// * Unambigious. While technical uniqueness is a hard requirement above,
3340
// we also want the labels to be obvious and unambiguous to humans.
3341
// For example, abbrebating "foobar" and "foobaz" to "f" and "fo" is
3342
// technically unique, but ambigious to humans which one is which.
3343
// * Short and readable. Where possible we omit the array index numbers
3344
// so that in most cases, the value is simply shown as-is.
3345
// We prefer "example [foo], example [bar]"
3346
// over "example [0: foo], example [2: bar]".
3347
// This also has the benefit of being stable and robust against e.g.
3348
// re-ordering data or adding new items during development, without
3349
// invalidating a previous filter or rerun link immediately.
3350
var valueType = _typeof(value);
3351
var testKey = i;
3352
if (valueType === 'string' && value.length <= 40 && !rNonObviousStr.test(value) && !/\s*\d+: /.test(value)) {
3353
testKey = value;
3354
} else if (valueType === 'string' || valueType === 'number' || valueType === 'boolean' || valueType === 'undefined' || value === null) {
3355
var valueForName = String(value);
3356
if (!rNonObviousStr.test(valueForName)) {
3357
testKey = i + ': ' + (valueForName.length <= 30 ? valueForName : valueForName.slice(0, 29) + '…');
3358
}
3359
}
3360
eachFn(value, testKey);
3361
}
3362
} else if (_typeof(data) === 'object' && data !== null) {
3363
for (var key in data) {
3364
eachFn(data[key], key);
3365
}
3366
} else {
3367
throw new Error("test.each() expects an array or object as input, but\nfound ".concat(_typeof(data), " instead."));
3368
}
3369
}
3370
extend(test, {
3371
todo: function todo(testName, callback) {
3372
addTest({
3373
testName: testName,
3374
callback: callback,
3375
todo: true
3376
});
3377
},
3378
skip: function skip(testName) {
3379
addTest({
3380
testName: testName,
3381
skip: true
3382
});
3383
},
3384
if: function _if(testName, condition, callback) {
3385
addTest({
3386
testName: testName,
3387
callback: callback,
3388
skip: !condition
3389
});
3390
},
3391
only: function only(testName, callback) {
3392
addOnlyTest({
3393
testName: testName,
3394
callback: callback
3395
});
3396
},
3397
each: function each(testName, dataset, callback) {
3398
runEach(dataset, function (data, testKey) {
3399
addTest({
3400
testName: makeEachTestName(testName, testKey),
3401
callback: callback,
3402
withData: true,
3403
stackOffset: 5,
3404
data: data
3405
});
3406
});
3407
}
3408
});
3409
test.todo.each = function (testName, dataset, callback) {
3410
runEach(dataset, function (data, testKey) {
3411
addTest({
3412
testName: makeEachTestName(testName, testKey),
3413
callback: callback,
3414
todo: true,
3415
withData: true,
3416
stackOffset: 5,
3417
data: data
3418
});
3419
});
3420
};
3421
test.skip.each = function (testName, dataset) {
3422
runEach(dataset, function (_, testKey) {
3423
addTest({
3424
testName: makeEachTestName(testName, testKey),
3425
stackOffset: 5,
3426
skip: true
3427
});
3428
});
3429
};
3430
test.if.each = function (testName, condition, dataset, callback) {
3431
runEach(dataset, function (data, testKey) {
3432
addTest({
3433
testName: makeEachTestName(testName, testKey),
3434
callback: callback,
3435
withData: true,
3436
stackOffset: 5,
3437
skip: !condition,
3438
data: condition ? data : undefined
3439
});
3440
});
3441
};
3442
test.only.each = function (testName, dataset, callback) {
3443
runEach(dataset, function (data, testKey) {
3444
addOnlyTest({
3445
testName: makeEachTestName(testName, testKey),
3446
callback: callback,
3447
withData: true,
3448
stackOffset: 5,
3449
data: data
3450
});
3451
});
3452
};
3453
3454
// Forcefully release all processing holds.
3455
function internalRecover(test) {
3456
test.pauses.forEach(function (pause) {
3457
pause.cancelled = true;
3458
});
3459
test.pauses.clear();
3460
internalStart(test);
3461
}
3462
3463
// Release a processing hold, scheduling a resumption attempt if no holds remain.
3464
function internalStart(test) {
3465
// Ignore if other async pauses still exist.
3466
if (test.pauses.size > 0) {
3467
return;
3468
}
3469
3470
// Add a slight delay to allow more assertions etc.
3471
if (setTimeout$1) {
3472
clearTimeout(config.timeout);
3473
config.timeout = setTimeout$1(function () {
3474
if (test.pauses.size > 0) {
3475
return;
3476
}
3477
clearTimeout(config.timeout);
3478
config.timeout = null;
3479
config.blocking = false;
3480
config.pq.advance();
3481
});
3482
} else {
3483
config.blocking = false;
3484
config.pq.advance();
3485
}
3486
}
3487
function collectTests(module) {
3488
var tests = [].concat(module.tests);
3489
var modules = _toConsumableArray(module.childModules);
3490
3491
// Do a breadth-first traversal of the child modules
3492
while (modules.length) {
3493
var nextModule = modules.shift();
3494
tests.push.apply(tests, nextModule.tests);
3495
modules.push.apply(modules, _toConsumableArray(nextModule.childModules));
3496
}
3497
return tests;
3498
}
3499
3500
// This returns true after all executable and skippable tests
3501
// in a module have been proccessed, and informs 'suiteEnd'
3502
// and moduleDone().
3503
function allTestsExecuted(module) {
3504
return module.testsRun + module.testsIgnored === collectTests(module).length;
3505
}
3506
3507
// This returns true during the last executable non-skipped test
3508
// within a module, and informs the running of the 'after' hook
3509
// for a given module. This runs only once for a given module,
3510
// but must run during the last non-skipped test. When it runs,
3511
// there may be non-zero skipped tests left.
3512
function lastTestWithinModuleExecuted(module) {
3513
return module.testsRun === collectTests(module).filter(function (test) {
3514
return !test.skip;
3515
}).length - 1;
3516
}
3517
function incrementTestsRun(module) {
3518
module.testsRun++;
3519
while (module = module.parentModule) {
3520
module.testsRun++;
3521
}
3522
}
3523
function incrementTestsIgnored(module) {
3524
module.testsIgnored++;
3525
while (module = module.parentModule) {
3526
module.testsIgnored++;
3527
}
3528
}
3529
3530
/* global module, exports, define */
3531
function exportQUnit(QUnit) {
3532
var exportedModule = false;
3533
if (window$1 && document) {
3534
// QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
3535
if (window$1.QUnit && window$1.QUnit.version) {
3536
throw new Error('QUnit has already been defined.');
3537
}
3538
window$1.QUnit = QUnit;
3539
exportedModule = true;
3540
}
3541
3542
// For Node.js
3543
if (typeof module !== 'undefined' && module && module.exports) {
3544
module.exports = QUnit;
3545
3546
// For consistency with CommonJS environments' exports
3547
module.exports.QUnit = QUnit;
3548
exportedModule = true;
3549
}
3550
3551
// For CommonJS with exports, but without module.exports, like Rhino
3552
if (typeof exports !== 'undefined' && exports) {
3553
exports.QUnit = QUnit;
3554
exportedModule = true;
3555
}
3556
3557
// For AMD
3558
if (typeof define === 'function' && define.amd) {
3559
define(function () {
3560
return QUnit;
3561
});
3562
QUnit.config.autostart = false;
3563
exportedModule = true;
3564
}
3565
3566
// For other environments, including Web Workers (globalThis === self),
3567
// SpiderMonkey (mozjs), and other embedded JavaScript engines
3568
if (!exportedModule) {
3569
g.QUnit = QUnit;
3570
}
3571
}
3572
3573
var ConsoleReporter = /*#__PURE__*/function () {
3574
function ConsoleReporter(runner) {
3575
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3576
_classCallCheck(this, ConsoleReporter);
3577
// Cache references to console methods to ensure we can report failures
3578
// from tests tests that mock the console object itself.
3579
// https://github.com/qunitjs/qunit/issues/1340
3580
// Support IE 9: Function#bind is supported, but no console.log.bind().
3581
this.log = options.log || Function.prototype.bind.call(console$1.log, console$1);
3582
runner.on('error', this.onError.bind(this));
3583
runner.on('runStart', this.onRunStart.bind(this));
3584
runner.on('testStart', this.onTestStart.bind(this));
3585
runner.on('testEnd', this.onTestEnd.bind(this));
3586
runner.on('runEnd', this.onRunEnd.bind(this));
3587
}
3588
return _createClass(ConsoleReporter, [{
3589
key: "onError",
3590
value: function onError(error) {
3591
this.log('error', error);
3592
}
3593
}, {
3594
key: "onRunStart",
3595
value: function onRunStart(runStart) {
3596
this.log('runStart', runStart);
3597
}
3598
}, {
3599
key: "onTestStart",
3600
value: function onTestStart(test) {
3601
this.log('testStart', test);
3602
}
3603
}, {
3604
key: "onTestEnd",
3605
value: function onTestEnd(test) {
3606
this.log('testEnd', test);
3607
}
3608
}, {
3609
key: "onRunEnd",
3610
value: function onRunEnd(runEnd) {
3611
this.log('runEnd', runEnd);
3612
}
3613
}], [{
3614
key: "init",
3615
value: function init(runner, options) {
3616
return new ConsoleReporter(runner, options);
3617
}
3618
}]);
3619
}();
3620
3621
// TODO: Consider using globalThis instead of window, so that the reporter
3622
// works for Node.js as well. As this can add overhead, we should make
3623
// this opt-in before we enable it for CLI.
3624
//
3625
// QUnit 3 will switch from `window` to `globalThis` and then make it
3626
// no longer an implicit feature of the HTML Reporter, but rather let
3627
// it be opt-in via `QUnit.config.reporters = ['perf']` or something
3628
// like that.
3629
var nativePerf = window$1 && typeof window$1.performance !== 'undefined' &&
3630
// eslint-disable-next-line compat/compat -- Checked
3631
typeof window$1.performance.mark === 'function' &&
3632
// eslint-disable-next-line compat/compat -- Checked
3633
typeof window$1.performance.measure === 'function' ? window$1.performance : undefined;
3634
var perf = {
3635
measure: nativePerf ? function (comment, startMark, endMark) {
3636
// `performance.measure` may fail if the mark could not be found.
3637
// reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()`
3638
try {
3639
nativePerf.measure(comment, startMark, endMark);
3640
} catch (ex) {
3641
Logger.warn('performance.measure could not be executed because of ', ex.message);
3642
}
3643
} : function () {},
3644
mark: nativePerf ? nativePerf.mark.bind(nativePerf) : function () {}
3645
};
3646
var PerfReporter = /*#__PURE__*/function () {
3647
function PerfReporter(runner) {
3648
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3649
_classCallCheck(this, PerfReporter);
3650
this.perf = options.perf || perf;
3651
runner.on('runStart', this.onRunStart.bind(this));
3652
runner.on('runEnd', this.onRunEnd.bind(this));
3653
runner.on('suiteStart', this.onSuiteStart.bind(this));
3654
runner.on('suiteEnd', this.onSuiteEnd.bind(this));
3655
runner.on('testStart', this.onTestStart.bind(this));
3656
runner.on('testEnd', this.onTestEnd.bind(this));
3657
}
3658
return _createClass(PerfReporter, [{
3659
key: "onRunStart",
3660
value: function onRunStart() {
3661
this.perf.mark('qunit_suite_0_start');
3662
}
3663
}, {
3664
key: "onSuiteStart",
3665
value: function onSuiteStart(suiteStart) {
3666
var suiteLevel = suiteStart.fullName.length;
3667
this.perf.mark("qunit_suite_".concat(suiteLevel, "_start"));
3668
}
3669
}, {
3670
key: "onSuiteEnd",
3671
value: function onSuiteEnd(suiteEnd) {
3672
var suiteLevel = suiteEnd.fullName.length;
3673
var suiteName = suiteEnd.fullName.join(' – ');
3674
this.perf.mark("qunit_suite_".concat(suiteLevel, "_end"));
3675
this.perf.measure("QUnit Test Suite: ".concat(suiteName), "qunit_suite_".concat(suiteLevel, "_start"), "qunit_suite_".concat(suiteLevel, "_end"));
3676
}
3677
}, {
3678
key: "onTestStart",
3679
value: function onTestStart() {
3680
this.perf.mark('qunit_test_start');
3681
}
3682
}, {
3683
key: "onTestEnd",
3684
value: function onTestEnd(testEnd) {
3685
this.perf.mark('qunit_test_end');
3686
var testName = testEnd.fullName.join(' – ');
3687
this.perf.measure("QUnit Test: ".concat(testName), 'qunit_test_start', 'qunit_test_end');
3688
}
3689
}, {
3690
key: "onRunEnd",
3691
value: function onRunEnd() {
3692
this.perf.mark('qunit_suite_0_end');
3693
this.perf.measure('QUnit Test Run', 'qunit_suite_0_start', 'qunit_suite_0_end');
3694
}
3695
}], [{
3696
key: "init",
3697
value: function init(runner, options) {
3698
return new PerfReporter(runner, options);
3699
}
3700
}]);
3701
}();
3702
3703
var FORCE_COLOR,
3704
NODE_DISABLE_COLORS,
3705
NO_COLOR,
3706
TERM,
3707
isTTY = true;
3708
if (typeof process !== 'undefined') {
3709
var _ref = process.env || {};
3710
FORCE_COLOR = _ref.FORCE_COLOR;
3711
NODE_DISABLE_COLORS = _ref.NODE_DISABLE_COLORS;
3712
NO_COLOR = _ref.NO_COLOR;
3713
TERM = _ref.TERM;
3714
isTTY = process.stdout && process.stdout.isTTY;
3715
}
3716
var $ = {
3717
enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && (FORCE_COLOR != null && FORCE_COLOR !== '0' || isTTY),
3718
// modifiers
3719
reset: init(0, 0),
3720
bold: init(1, 22),
3721
dim: init(2, 22),
3722
italic: init(3, 23),
3723
underline: init(4, 24),
3724
inverse: init(7, 27),
3725
hidden: init(8, 28),
3726
strikethrough: init(9, 29),
3727
// colors
3728
black: init(30, 39),
3729
red: init(31, 39),
3730
green: init(32, 39),
3731
yellow: init(33, 39),
3732
blue: init(34, 39),
3733
magenta: init(35, 39),
3734
cyan: init(36, 39),
3735
white: init(37, 39),
3736
gray: init(90, 39),
3737
grey: init(90, 39),
3738
// background colors
3739
bgBlack: init(40, 49),
3740
bgRed: init(41, 49),
3741
bgGreen: init(42, 49),
3742
bgYellow: init(43, 49),
3743
bgBlue: init(44, 49),
3744
bgMagenta: init(45, 49),
3745
bgCyan: init(46, 49),
3746
bgWhite: init(47, 49)
3747
};
3748
function run(arr, str) {
3749
var i = 0,
3750
tmp,
3751
beg = '',
3752
end = '';
3753
for (; i < arr.length; i++) {
3754
tmp = arr[i];
3755
beg += tmp.open;
3756
end += tmp.close;
3757
if (!!~str.indexOf(tmp.close)) {
3758
str = str.replace(tmp.rgx, tmp.close + tmp.open);
3759
}
3760
}
3761
return beg + str + end;
3762
}
3763
function chain(has, keys) {
3764
var ctx = {
3765
has: has,
3766
keys: keys
3767
};
3768
ctx.reset = $.reset.bind(ctx);
3769
ctx.bold = $.bold.bind(ctx);
3770
ctx.dim = $.dim.bind(ctx);
3771
ctx.italic = $.italic.bind(ctx);
3772
ctx.underline = $.underline.bind(ctx);
3773
ctx.inverse = $.inverse.bind(ctx);
3774
ctx.hidden = $.hidden.bind(ctx);
3775
ctx.strikethrough = $.strikethrough.bind(ctx);
3776
ctx.black = $.black.bind(ctx);
3777
ctx.red = $.red.bind(ctx);
3778
ctx.green = $.green.bind(ctx);
3779
ctx.yellow = $.yellow.bind(ctx);
3780
ctx.blue = $.blue.bind(ctx);
3781
ctx.magenta = $.magenta.bind(ctx);
3782
ctx.cyan = $.cyan.bind(ctx);
3783
ctx.white = $.white.bind(ctx);
3784
ctx.gray = $.gray.bind(ctx);
3785
ctx.grey = $.grey.bind(ctx);
3786
ctx.bgBlack = $.bgBlack.bind(ctx);
3787
ctx.bgRed = $.bgRed.bind(ctx);
3788
ctx.bgGreen = $.bgGreen.bind(ctx);
3789
ctx.bgYellow = $.bgYellow.bind(ctx);
3790
ctx.bgBlue = $.bgBlue.bind(ctx);
3791
ctx.bgMagenta = $.bgMagenta.bind(ctx);
3792
ctx.bgCyan = $.bgCyan.bind(ctx);
3793
ctx.bgWhite = $.bgWhite.bind(ctx);
3794
return ctx;
3795
}
3796
function init(open, close) {
3797
var blk = {
3798
open: "\x1B[".concat(open, "m"),
3799
close: "\x1B[".concat(close, "m"),
3800
rgx: new RegExp("\\x1b\\[".concat(close, "m"), 'g')
3801
};
3802
return function (txt) {
3803
if (this !== void 0 && this.has !== void 0) {
3804
!!~this.has.indexOf(open) || (this.has.push(open), this.keys.push(blk));
3805
return txt === void 0 ? this : $.enabled ? run(this.keys, txt + '') : txt + '';
3806
}
3807
return txt === void 0 ? chain([open], [blk]) : $.enabled ? run([blk], txt + '') : txt + '';
3808
};
3809
}
3810
3811
/**
3812
* Format a given value into YAML.
3813
*
3814
* YAML is a superset of JSON that supports all the same data
3815
* types and syntax, and more. As such, it is always possible
3816
* to fallback to JSON.stringfify, but we generally avoid
3817
* that to make output easier to read for humans.
3818
*
3819
* Supported data types:
3820
*
3821
* - null
3822
* - boolean
3823
* - number
3824
* - string
3825
* - array
3826
* - object
3827
*
3828
* Anything else (including NaN, Infinity, and undefined)
3829
* must be described in strings, for display purposes.
3830
*
3831
* Note that quotes are optional in YAML strings if the
3832
* strings are "simple", and as such we generally prefer
3833
* that for improved readability. We output strings in
3834
* one of three ways:
3835
*
3836
* - bare unquoted text, for simple one-line strings.
3837
* - JSON (quoted text), for complex one-line strings.
3838
* - YAML Block, for complex multi-line strings.
3839
*
3840
* Objects with cyclical references will be stringifed as
3841
* "[Circular]" as they cannot otherwise be represented.
3842
*/
3843
function prettyYamlValue(value) {
3844
var indent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 4;
3845
if (value === undefined) {
3846
// Not supported in JSON/YAML, turn into string
3847
// and let the below output it as bare string.
3848
value = String(value);
3849
}
3850
3851
// Support IE 9-11: Use isFinite instead of ES6 Number.isFinite
3852
if (typeof value === 'number' && !isFinite(value)) {
3853
// Turn NaN and Infinity into simple strings.
3854
// Paranoia: Don't return directly just in case there's
3855
// a way to add special characters here.
3856
value = String(value);
3857
}
3858
if (typeof value === 'number') {
3859
// Simple numbers
3860
return JSON.stringify(value);
3861
}
3862
if (typeof value === 'string') {
3863
// If any of these match, then we can't output it
3864
// as bare unquoted text, because that would either
3865
// cause data loss or invalid YAML syntax.
3866
//
3867
// - Quotes, escapes, line breaks, or JSON-like stuff.
3868
var rSpecialJson = /['"\\/[{}\]\r\n]/;
3869
3870
// - Characters that are special at the start of a YAML value
3871
var rSpecialYaml = /[-?:,[\]{}#&*!|=>'"%@`]/;
3872
3873
// - Leading or trailing whitespace.
3874
var rUntrimmed = /(^\s|\s$)/;
3875
3876
// - Ambiguous as YAML number, e.g. '2', '-1.2', '.2', or '2_000'
3877
var rNumerical = /^[\d._-]+$/;
3878
3879
// - Ambiguous as YAML bool.
3880
// Use case-insensitive match, although technically only
3881
// fully-lower, fully-upper, or uppercase-first would be ambiguous.
3882
// e.g. true/True/TRUE, but not tRUe.
3883
var rBool = /^(true|false|y|n|yes|no|on|off)$/i;
3884
3885
// Is this a complex string?
3886
if (value === '' || rSpecialJson.test(value) || rSpecialYaml.test(value[0]) || rUntrimmed.test(value) || rNumerical.test(value) || rBool.test(value)) {
3887
if (!/\n/.test(value)) {
3888
// Complex one-line string, use JSON (quoted string)
3889
return JSON.stringify(value);
3890
}
3891
3892
// See also <https://yaml-multiline.info/>
3893
// Support IE 9-11: Avoid ES6 String#repeat
3894
var prefix = new Array(indent + 1).join(' ');
3895
var trailingLinebreakMatch = value.match(/\n+$/);
3896
var trailingLinebreaks = trailingLinebreakMatch ? trailingLinebreakMatch[0].length : 0;
3897
if (trailingLinebreaks === 1) {
3898
// Use the most straight-forward "Block" string in YAML
3899
// without any "Chomping" indicators.
3900
var lines = value
3901
3902
// Ignore the last new line, since we'll get that one for free
3903
// with the straight-forward Block syntax.
3904
.replace(/\n$/, '').split('\n').map(function (line) {
3905
return prefix + line;
3906
});
3907
return '|\n' + lines.join('\n');
3908
} else {
3909
// This has either no trailing new lines, or more than 1.
3910
// Use |+ so that YAML parsers will preserve it exactly.
3911
var _lines = value.split('\n').map(function (line) {
3912
return prefix + line;
3913
});
3914
return '|+\n' + _lines.join('\n');
3915
}
3916
} else {
3917
// Simple string, use bare unquoted text
3918
return value;
3919
}
3920
}
3921
3922
// Handle null, boolean, array, and object
3923
return JSON.stringify(decycledShallowClone(value), null, 2);
3924
}
3925
3926
/**
3927
* Creates a shallow clone of an object where cycles have
3928
* been replaced with "[Circular]".
3929
*/
3930
function decycledShallowClone(object) {
3931
var ancestors = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
3932
if (ancestors.indexOf(object) !== -1) {
3933
return '[Circular]';
3934
}
3935
var type = Object.prototype.toString.call(object).replace(/^\[.+\s(.+?)]$/, '$1').toLowerCase();
3936
var clone;
3937
switch (type) {
3938
case 'array':
3939
ancestors.push(object);
3940
clone = object.map(function (element) {
3941
return decycledShallowClone(element, ancestors);
3942
});
3943
ancestors.pop();
3944
break;
3945
case 'object':
3946
ancestors.push(object);
3947
clone = {};
3948
Object.keys(object).forEach(function (key) {
3949
clone[key] = decycledShallowClone(object[key], ancestors);
3950
});
3951
ancestors.pop();
3952
break;
3953
default:
3954
clone = object;
3955
}
3956
return clone;
3957
}
3958
var TapReporter = /*#__PURE__*/function () {
3959
function TapReporter(runner) {
3960
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3961
_classCallCheck(this, TapReporter);
3962
// Cache references to console methods to ensure we can report failures
3963
// from tests tests that mock the console object itself.
3964
// https://github.com/qunitjs/qunit/issues/1340
3965
// Support IE 9: Function#bind is supported, but no console.log.bind().
3966
this.log = options.log || Function.prototype.bind.call(console$1.log, console$1);
3967
this.testCount = 0;
3968
this.ended = false;
3969
this.bailed = false;
3970
runner.on('error', this.onError.bind(this));
3971
runner.on('runStart', this.onRunStart.bind(this));
3972
runner.on('testEnd', this.onTestEnd.bind(this));
3973
runner.on('runEnd', this.onRunEnd.bind(this));
3974
}
3975
return _createClass(TapReporter, [{
3976
key: "onRunStart",
3977
value: function onRunStart(_runSuite) {
3978
this.log('TAP version 13');
3979
}
3980
}, {
3981
key: "onError",
3982
value: function onError(error) {
3983
if (this.bailed) {
3984
return;
3985
}
3986
this.bailed = true;
3987
3988
// Imitate onTestEnd
3989
// Skip this if we're past "runEnd" as it would look odd
3990
if (!this.ended) {
3991
this.testCount = this.testCount + 1;
3992
this.log("not ok ".concat(this.testCount, " ").concat($.red('global failure')));
3993
this.logError(error);
3994
}
3995
this.log('Bail out! ' + errorString(error).split('\n')[0]);
3996
if (this.ended) {
3997
this.logError(error);
3998
}
3999
}
4000
}, {
4001
key: "onTestEnd",
4002
value: function onTestEnd(test) {
4003
var _this = this;
4004
this.testCount = this.testCount + 1;
4005
if (test.status === 'passed') {
4006
this.log("ok ".concat(this.testCount, " ").concat(test.fullName.join(' > ')));
4007
} else if (test.status === 'skipped') {
4008
this.log("ok ".concat(this.testCount, " ").concat($.yellow("# SKIP ".concat(test.fullName.join(' > ')))));
4009
} else if (test.status === 'todo') {
4010
this.log("not ok ".concat(this.testCount, " ").concat($.cyan("# TODO ".concat(test.fullName.join(' > ')))));
4011
test.errors.forEach(function (error) {
4012
return _this.logAssertion(error, 'todo');
4013
});
4014
} else {
4015
this.log("not ok ".concat(this.testCount, " ").concat($.red(test.fullName.join(' > '))));
4016
test.errors.forEach(function (error) {
4017
return _this.logAssertion(error);
4018
});
4019
}
4020
}
4021
}, {
4022
key: "onRunEnd",
4023
value: function onRunEnd(runEnd) {
4024
this.ended = true;
4025
this.log("1..".concat(runEnd.testCounts.total));
4026
this.log("# pass ".concat(runEnd.testCounts.passed));
4027
this.log("# ".concat($.yellow("skip ".concat(runEnd.testCounts.skipped))));
4028
this.log("# ".concat($.cyan("todo ".concat(runEnd.testCounts.todo))));
4029
this.log("# ".concat($.red("fail ".concat(runEnd.testCounts.failed))));
4030
}
4031
}, {
4032
key: "logAssertion",
4033
value: function logAssertion(error, severity) {
4034
var out = ' ---';
4035
out += "\n message: ".concat(prettyYamlValue(error.message || 'failed'));
4036
out += "\n severity: ".concat(prettyYamlValue(severity || 'failed'));
4037
4038
// When pushFailure() is used, actual/expected are initially unset but
4039
// eventually in Test#logAssertion, for testReport#pushAssertion, these are
4040
// forged into existence as undefined.
4041
var hasAny = error.expected !== undefined || error.actual !== undefined;
4042
if (hasAny) {
4043
out += "\n actual : ".concat(prettyYamlValue(error.actual));
4044
out += "\n expected: ".concat(prettyYamlValue(error.expected));
4045
}
4046
if (error.stack) {
4047
// Since stacks aren't user generated, take a bit of liberty by
4048
// adding a trailing new line to allow a straight-forward YAML Blocks.
4049
out += "\n stack: ".concat(prettyYamlValue(error.stack + '\n'));
4050
}
4051
out += '\n ...';
4052
this.log(out);
4053
}
4054
}, {
4055
key: "logError",
4056
value: function logError(error) {
4057
var out = ' ---';
4058
out += "\n message: ".concat(prettyYamlValue(errorString(error)));
4059
out += "\n severity: ".concat(prettyYamlValue('failed'));
4060
if (error && error.stack) {
4061
out += "\n stack: ".concat(prettyYamlValue(error.stack + '\n'));
4062
}
4063
out += '\n ...';
4064
this.log(out);
4065
}
4066
}], [{
4067
key: "init",
4068
value: function init(runner, options) {
4069
return new TapReporter(runner, options);
4070
}
4071
}]);
4072
}();
4073
4074
var reporters = {
4075
console: ConsoleReporter,
4076
perf: PerfReporter,
4077
tap: TapReporter
4078
};
4079
4080
function makeAddGlobalHook(hookName) {
4081
return function addGlobalHook(callback) {
4082
if (!config.globalHooks[hookName]) {
4083
config.globalHooks[hookName] = [];
4084
}
4085
config.globalHooks[hookName].push(callback);
4086
};
4087
}
4088
var hooks = {
4089
beforeEach: makeAddGlobalHook('beforeEach'),
4090
afterEach: makeAddGlobalHook('afterEach')
4091
};
4092
4093
/**
4094
* Creates a seeded "sample" generator which is used for randomizing tests.
4095
*/
4096
function unitSamplerGenerator(seed) {
4097
// 32-bit xorshift, requires only a nonzero seed
4098
// https://excamera.com/sphinx/article-xorshift.html
4099
var sample = parseInt(generateHash(seed), 16) || -1;
4100
return function () {
4101
sample ^= sample << 13;
4102
sample ^= sample >>> 17;
4103
sample ^= sample << 5;
4104
4105
// ECMAScript has no unsigned number type
4106
if (sample < 0) {
4107
sample += 0x100000000;
4108
}
4109
return sample / 0x100000000;
4110
};
4111
}
4112
var ProcessingQueue = /*#__PURE__*/function () {
4113
/**
4114
* @param {Function} test Reference to the QUnit.test() method
4115
*/
4116
function ProcessingQueue(test) {
4117
_classCallCheck(this, ProcessingQueue);
4118
this.test = test;
4119
this.priorityCount = 0;
4120
this.unitSampler = null;
4121
4122
// This is a queue of functions that are tasks within a single test.
4123
// After tests are dequeued from config.queue they are expanded into
4124
// a set of tasks in this queue.
4125
this.taskQueue = [];
4126
this.finished = false;
4127
}
4128
4129
/**
4130
* Advances the taskQueue to the next task. If the taskQueue is empty,
4131
* process the testQueue
4132
*/
4133
return _createClass(ProcessingQueue, [{
4134
key: "advance",
4135
value: function advance() {
4136
this.advanceTaskQueue();
4137
if (!this.taskQueue.length && !config.blocking && !config.current) {
4138
this.advanceTestQueue();
4139
}
4140
}
4141
4142
/**
4143
* Advances the taskQueue with an increased depth
4144
*/
4145
}, {
4146
key: "advanceTaskQueue",
4147
value: function advanceTaskQueue() {
4148
var start = performance.now();
4149
config.depth = (config.depth || 0) + 1;
4150
this.processTaskQueue(start);
4151
config.depth--;
4152
}
4153
4154
/**
4155
* Process the first task on the taskQueue as a promise.
4156
* Each task is a function added by Test#queue() in /src/test.js
4157
*/
4158
}, {
4159
key: "processTaskQueue",
4160
value: function processTaskQueue(start) {
4161
var _this = this;
4162
if (this.taskQueue.length && !config.blocking) {
4163
var elapsedTime = performance.now() - start;
4164
if (!setTimeout$1 || config.updateRate <= 0 || elapsedTime < config.updateRate) {
4165
var task = this.taskQueue.shift();
4166
_Promise.resolve(task()).then(function () {
4167
if (!_this.taskQueue.length) {
4168
_this.advance();
4169
} else {
4170
_this.processTaskQueue(start);
4171
}
4172
});
4173
} else {
4174
setTimeout$1(function () {
4175
_this.advance();
4176
});
4177
}
4178
}
4179
}
4180
4181
/**
4182
* Advance the testQueue to the next test to process. Call done() if testQueue completes.
4183
*/
4184
}, {
4185
key: "advanceTestQueue",
4186
value: function advanceTestQueue() {
4187
if (!config.blocking && !config.queue.length && config.depth === 0) {
4188
this.done();
4189
return;
4190
}
4191
var testTasks = config.queue.shift();
4192
this.addToTaskQueue(testTasks());
4193
if (this.priorityCount > 0) {
4194
this.priorityCount--;
4195
}
4196
this.advance();
4197
}
4198
4199
/**
4200
* Enqueue the tasks for a test into the task queue.
4201
* @param {Array} tasksArray
4202
*/
4203
}, {
4204
key: "addToTaskQueue",
4205
value: function addToTaskQueue(tasksArray) {
4206
var _this$taskQueue;
4207
(_this$taskQueue = this.taskQueue).push.apply(_this$taskQueue, _toConsumableArray(tasksArray));
4208
}
4209
4210
/**
4211
* Return the number of tasks remaining in the task queue to be processed.
4212
* @return {number}
4213
*/
4214
}, {
4215
key: "taskCount",
4216
value: function taskCount() {
4217
return this.taskQueue.length;
4218
}
4219
4220
/**
4221
* Adds a test to the TestQueue for execution.
4222
* @param {Function} testTasksFunc
4223
* @param {boolean} prioritize
4224
*/
4225
}, {
4226
key: "add",
4227
value: function add(testTasksFunc, prioritize) {
4228
if (prioritize) {
4229
config.queue.splice(this.priorityCount++, 0, testTasksFunc);
4230
} else if (config.seed) {
4231
if (!this.unitSampler) {
4232
this.unitSampler = unitSamplerGenerator(config.seed);
4233
}
4234
4235
// Insert into a random position after all prioritized items
4236
var index = Math.floor(this.unitSampler() * (config.queue.length - this.priorityCount + 1));
4237
config.queue.splice(this.priorityCount + index, 0, testTasksFunc);
4238
} else {
4239
config.queue.push(testTasksFunc);
4240
}
4241
}
4242
4243
/**
4244
* This function is called when the ProcessingQueue is done processing all
4245
* items. It handles emitting the final run events.
4246
*/
4247
}, {
4248
key: "done",
4249
value: function done() {
4250
// We have reached the end of the processing queue and are about to emit the
4251
// "runEnd" event after which reporters typically stop listening and exit
4252
// the process. First, check if we need to emit one final test.
4253
if (config.stats.testCount === 0 && config.failOnZeroTests === true) {
4254
var error;
4255
if (config.filter && config.filter.length) {
4256
error = new Error("No tests matched the filter \"".concat(config.filter, "\"."));
4257
} else if (config.module && config.module.length) {
4258
error = new Error("No tests matched the module \"".concat(config.module, "\"."));
4259
} else if (config.moduleId && config.moduleId.length) {
4260
error = new Error("No tests matched the moduleId \"".concat(config.moduleId, "\"."));
4261
} else if (config.testId && config.testId.length) {
4262
error = new Error("No tests matched the testId \"".concat(config.testId, "\"."));
4263
} else {
4264
error = new Error('No tests were run.');
4265
}
4266
this.test('global failure', extend(function (assert) {
4267
assert.pushResult({
4268
result: false,
4269
message: error.message,
4270
source: error.stack
4271
});
4272
}, {
4273
validTest: true
4274
}));
4275
4276
// We do need to call `advance()` in order to resume the processing queue.
4277
// Once this new test is finished processing, we'll reach `done` again, and
4278
// that time the above condition will evaluate to false.
4279
this.advance();
4280
return;
4281
}
4282
var storage = config.storage;
4283
var runtime = Math.round(performance.now() - config.started);
4284
var passed = config.stats.all - config.stats.bad;
4285
this.finished = true;
4286
emit('runEnd', runSuite.end(true));
4287
runLoggingCallbacks('done', {
4288
// @deprecated since 2.19.0 Use done() without `details` parameter,
4289
// or use `QUnit.on('runEnd')` instead. Parameter to be replaced in
4290
// QUnit 3.0 with test counts.
4291
passed: passed,
4292
failed: config.stats.bad,
4293
total: config.stats.all,
4294
runtime: runtime
4295
}).then(function () {
4296
// Clear own storage items if all tests passed
4297
if (storage && config.stats.bad === 0) {
4298
for (var i = storage.length - 1; i >= 0; i--) {
4299
var key = storage.key(i);
4300
if (key.indexOf('qunit-test-') === 0) {
4301
storage.removeItem(key);
4302
}
4303
}
4304
}
4305
});
4306
}
4307
}]);
4308
}();
4309
4310
/**
4311
* Handle a global error that should result in a failed test run.
4312
*
4313
* Summary:
4314
*
4315
* - If we're strictly inside a test (or one if its module hooks), the exception
4316
* becomes a failed assertion.
4317
*
4318
* This has the important side-effect that uncaught exceptions (such as
4319
* calling an undefined function) during a "todo" test do NOT result in
4320
* a failed test run.
4321
*
4322
* - If we're anywhere outside a test (be it in early event callbacks, or
4323
* internally between tests, or somewhere after "runEnd" if the process is
4324
* still alive for some reason), then send an "error" event to the reporters.
4325
*
4326
* @since 2.17.0
4327
* @param {Error|any} error
4328
*/
4329
function onUncaughtException(error) {
4330
if (config.current) {
4331
// This omits 'actual' and 'expected' (undefined)
4332
config.current.assert.pushResult({
4333
result: false,
4334
message: "global failure: ".concat(errorString(error)),
4335
// We could let callers specify an offset to subtract a number of frames via
4336
// sourceFromStacktrace, in case they are a wrapper further away from the error
4337
// handler, and thus reduce some noise in the stack trace. However, we're not
4338
// doing this right now because it would almost never be used in practice given
4339
// the vast majority of error values will be Error objects, and thus have their
4340
// own stack trace already.
4341
source: error && error.stack || sourceFromStacktrace(2)
4342
});
4343
} else {
4344
// The "error" event was added in QUnit 2.17.
4345
// Increase "bad assertion" stats despite no longer pushing an assertion in this case.
4346
// This ensures "runEnd" and "QUnit.done()" handlers behave as expected, since the "bad"
4347
// count is typically how reporters decide on the boolean outcome of the test run.
4348
runSuite.globalFailureCount++;
4349
config.stats.bad++;
4350
config.stats.all++;
4351
emit('error', error);
4352
}
4353
}
4354
4355
/**
4356
* Handle a window.onerror error.
4357
*
4358
* If there is a current test that sets the internal `ignoreGlobalErrors` field
4359
* (such as during `assert.throws()`), then the error is ignored and native
4360
* error reporting is suppressed as well. This is because in browsers, an error
4361
* can sometimes end up in `window.onerror` instead of in the local try/catch.
4362
* This ignoring of errors does not apply to our general onUncaughtException
4363
* method, nor to our `unhandledRejection` handlers, as those are not meant
4364
* to receive an "expected" error during `assert.throws()`.
4365
*
4366
* @see <https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror>
4367
* @deprecated since 2.17.0 Use QUnit.onUncaughtException instead.
4368
* @param {Object} details
4369
* @param {string} details.message
4370
* @param {string} details.fileName
4371
* @param {number} details.lineNumber
4372
* @param {string|undefined} [details.stacktrace]
4373
* @return {bool} True if native error reporting should be suppressed.
4374
*/
4375
function onWindowError(details) {
4376
Logger.warn('QUnit.onError is deprecated and will be removed in QUnit 3.0.' + ' Please use QUnit.onUncaughtException instead.');
4377
if (config.current && config.current.ignoreGlobalErrors) {
4378
return true;
4379
}
4380
var err = new Error(details.message);
4381
err.stack = details.stacktrace || details.fileName + ':' + details.lineNumber;
4382
onUncaughtException(err);
4383
return false;
4384
}
4385
4386
/* eslint-disable indent */
4387
4388
/*
4389
* This file is a modified version of google-diff-match-patch's JavaScript implementation
4390
* (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
4391
* modifications are licensed as more fully set forth in LICENSE.txt.
4392
*
4393
* The original source of google-diff-match-patch is attributable and licensed as follows:
4394
*
4395
* Copyright 2006 Google Inc.
4396
* https://code.google.com/p/google-diff-match-patch/
4397
*
4398
* Licensed under the Apache License, Version 2.0 (the "License");
4399
* you may not use this file except in compliance with the License.
4400
* You may obtain a copy of the License at
4401
*
4402
* https://www.apache.org/licenses/LICENSE-2.0
4403
*
4404
* Unless required by applicable law or agreed to in writing, software
4405
* distributed under the License is distributed on an "AS IS" BASIS,
4406
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4407
* See the License for the specific language governing permissions and
4408
* limitations under the License.
4409
*
4410
* More Info:
4411
* https://code.google.com/p/google-diff-match-patch/
4412
*
4413
* Usage: QUnit.diff(expected, actual)
4414
*
4415
*/
4416
function DiffMatchPatch() {}
4417
4418
// DIFF FUNCTIONS
4419
4420
/**
4421
* The data structure representing a diff is an array of tuples:
4422
* [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
4423
* which means: delete 'Hello', add 'Goodbye' and keep ' world.'
4424
*/
4425
var DIFF_DELETE = -1;
4426
var DIFF_INSERT = 1;
4427
var DIFF_EQUAL = 0;
4428
var hasOwn = Object.prototype.hasOwnProperty;
4429
4430
/**
4431
* Find the differences between two texts. Simplifies the problem by stripping
4432
* any common prefix or suffix off the texts before diffing.
4433
* @param {string} text1 Old string to be diffed.
4434
* @param {string} text2 New string to be diffed.
4435
* @param {boolean=} optChecklines Optional speedup flag. If present and false,
4436
* then don't run a line-level diff first to identify the changed areas.
4437
* Defaults to true, which does a faster, slightly less optimal diff.
4438
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4439
*/
4440
DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
4441
// The diff must be complete in up to 1 second.
4442
var deadline = Date.now() + 1000;
4443
4444
// Check for null inputs.
4445
if (text1 === null || text2 === null) {
4446
throw new Error('Cannot diff null input.');
4447
}
4448
4449
// Check for equality (speedup).
4450
if (text1 === text2) {
4451
if (text1) {
4452
return [[DIFF_EQUAL, text1]];
4453
}
4454
return [];
4455
}
4456
if (typeof optChecklines === 'undefined') {
4457
optChecklines = true;
4458
}
4459
4460
// Trim off common prefix (speedup).
4461
var commonlength = this.diffCommonPrefix(text1, text2);
4462
var commonprefix = text1.substring(0, commonlength);
4463
text1 = text1.substring(commonlength);
4464
text2 = text2.substring(commonlength);
4465
4466
// Trim off common suffix (speedup).
4467
commonlength = this.diffCommonSuffix(text1, text2);
4468
var commonsuffix = text1.substring(text1.length - commonlength);
4469
text1 = text1.substring(0, text1.length - commonlength);
4470
text2 = text2.substring(0, text2.length - commonlength);
4471
4472
// Compute the diff on the middle block.
4473
var diffs = this.diffCompute(text1, text2, optChecklines, deadline);
4474
4475
// Restore the prefix and suffix.
4476
if (commonprefix) {
4477
diffs.unshift([DIFF_EQUAL, commonprefix]);
4478
}
4479
if (commonsuffix) {
4480
diffs.push([DIFF_EQUAL, commonsuffix]);
4481
}
4482
this.diffCleanupMerge(diffs);
4483
return diffs;
4484
};
4485
4486
/**
4487
* Reduce the number of edits by eliminating operationally trivial equalities.
4488
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4489
*/
4490
DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
4491
var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
4492
changes = false;
4493
equalities = []; // Stack of indices where equalities are found.
4494
equalitiesLength = 0; // Keeping our own length var is faster in JS.
4495
/** @type {?string} */
4496
lastequality = null;
4497
4498
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
4499
pointer = 0; // Index of current position.
4500
4501
// Is there an insertion operation before the last equality.
4502
preIns = false;
4503
4504
// Is there a deletion operation before the last equality.
4505
preDel = false;
4506
4507
// Is there an insertion operation after the last equality.
4508
postIns = false;
4509
4510
// Is there a deletion operation after the last equality.
4511
postDel = false;
4512
while (pointer < diffs.length) {
4513
// Equality found.
4514
if (diffs[pointer][0] === DIFF_EQUAL) {
4515
if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
4516
// Candidate found.
4517
equalities[equalitiesLength++] = pointer;
4518
preIns = postIns;
4519
preDel = postDel;
4520
lastequality = diffs[pointer][1];
4521
} else {
4522
// Not a candidate, and can never become one.
4523
equalitiesLength = 0;
4524
lastequality = null;
4525
}
4526
postIns = postDel = false;
4527
4528
// An insertion or deletion.
4529
} else {
4530
if (diffs[pointer][0] === DIFF_DELETE) {
4531
postDel = true;
4532
} else {
4533
postIns = true;
4534
}
4535
4536
/*
4537
* Five types to be split:
4538
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
4539
* <ins>A</ins>X<ins>C</ins><del>D</del>
4540
* <ins>A</ins><del>B</del>X<ins>C</ins>
4541
* <ins>A</del>X<ins>C</ins><del>D</del>
4542
* <ins>A</ins><del>B</del>X<del>C</del>
4543
*/
4544
if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
4545
// Duplicate record.
4546
diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
4547
4548
// Change second copy to insert.
4549
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
4550
equalitiesLength--; // Throw away the equality we just deleted;
4551
lastequality = null;
4552
if (preIns && preDel) {
4553
// No changes made which could affect previous entry, keep going.
4554
postIns = postDel = true;
4555
equalitiesLength = 0;
4556
} else {
4557
equalitiesLength--; // Throw away the previous equality.
4558
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
4559
postIns = postDel = false;
4560
}
4561
changes = true;
4562
}
4563
}
4564
pointer++;
4565
}
4566
if (changes) {
4567
this.diffCleanupMerge(diffs);
4568
}
4569
};
4570
4571
/**
4572
* Convert a diff array into a pretty HTML report.
4573
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
4574
* @param {integer} string to be beautified.
4575
* @return {string} HTML representation.
4576
*/
4577
DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
4578
var html = [];
4579
for (var x = 0; x < diffs.length; x++) {
4580
var op = diffs[x][0]; // Operation (insert, delete, equal)
4581
var data = diffs[x][1]; // Text of change.
4582
switch (op) {
4583
case DIFF_INSERT:
4584
html[x] = '<ins>' + escapeText(data) + '</ins>';
4585
break;
4586
case DIFF_DELETE:
4587
html[x] = '<del>' + escapeText(data) + '</del>';
4588
break;
4589
case DIFF_EQUAL:
4590
html[x] = '<span>' + escapeText(data) + '</span>';
4591
break;
4592
}
4593
}
4594
return html.join('');
4595
};
4596
4597
/**
4598
* Determine the common prefix of two strings.
4599
* @param {string} text1 First string.
4600
* @param {string} text2 Second string.
4601
* @return {number} The number of characters common to the start of each
4602
* string.
4603
*/
4604
DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
4605
var pointermid, pointermax, pointermin, pointerstart;
4606
4607
// Quick check for common null cases.
4608
if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
4609
return 0;
4610
}
4611
4612
// Binary search.
4613
// Performance analysis: https://neil.fraser.name/news/2007/10/09/
4614
pointermin = 0;
4615
pointermax = Math.min(text1.length, text2.length);
4616
pointermid = pointermax;
4617
pointerstart = 0;
4618
while (pointermin < pointermid) {
4619
if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
4620
pointermin = pointermid;
4621
pointerstart = pointermin;
4622
} else {
4623
pointermax = pointermid;
4624
}
4625
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4626
}
4627
return pointermid;
4628
};
4629
4630
/**
4631
* Determine the common suffix of two strings.
4632
* @param {string} text1 First string.
4633
* @param {string} text2 Second string.
4634
* @return {number} The number of characters common to the end of each string.
4635
*/
4636
DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
4637
var pointermid, pointermax, pointermin, pointerend;
4638
4639
// Quick check for common null cases.
4640
if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
4641
return 0;
4642
}
4643
4644
// Binary search.
4645
// Performance analysis: https://neil.fraser.name/news/2007/10/09/
4646
pointermin = 0;
4647
pointermax = Math.min(text1.length, text2.length);
4648
pointermid = pointermax;
4649
pointerend = 0;
4650
while (pointermin < pointermid) {
4651
if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) {
4652
pointermin = pointermid;
4653
pointerend = pointermin;
4654
} else {
4655
pointermax = pointermid;
4656
}
4657
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
4658
}
4659
return pointermid;
4660
};
4661
4662
/**
4663
* Find the differences between two texts. Assumes that the texts do not
4664
* have any common prefix or suffix.
4665
* @param {string} text1 Old string to be diffed.
4666
* @param {string} text2 New string to be diffed.
4667
* @param {boolean} checklines Speedup flag. If false, then don't run a
4668
* line-level diff first to identify the changed areas.
4669
* If true, then run a faster, slightly less optimal diff.
4670
* @param {number} deadline Time when the diff should be complete by.
4671
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4672
* @private
4673
*/
4674
DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
4675
var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
4676
if (!text1) {
4677
// Just add some text (speedup).
4678
return [[DIFF_INSERT, text2]];
4679
}
4680
if (!text2) {
4681
// Just delete some text (speedup).
4682
return [[DIFF_DELETE, text1]];
4683
}
4684
longtext = text1.length > text2.length ? text1 : text2;
4685
shorttext = text1.length > text2.length ? text2 : text1;
4686
i = longtext.indexOf(shorttext);
4687
if (i !== -1) {
4688
// Shorter text is inside the longer text (speedup).
4689
diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
4690
4691
// Swap insertions for deletions if diff is reversed.
4692
if (text1.length > text2.length) {
4693
diffs[0][0] = diffs[2][0] = DIFF_DELETE;
4694
}
4695
return diffs;
4696
}
4697
if (shorttext.length === 1) {
4698
// Single character string.
4699
// After the previous speedup, the character can't be an equality.
4700
return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
4701
}
4702
4703
// Check to see if the problem can be split in two.
4704
hm = this.diffHalfMatch(text1, text2);
4705
if (hm) {
4706
// A half-match was found, sort out the return data.
4707
text1A = hm[0];
4708
text1B = hm[1];
4709
text2A = hm[2];
4710
text2B = hm[3];
4711
midCommon = hm[4];
4712
4713
// Send both pairs off for separate processing.
4714
diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
4715
diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
4716
4717
// Merge the results.
4718
return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
4719
}
4720
if (checklines && text1.length > 100 && text2.length > 100) {
4721
return this.diffLineMode(text1, text2, deadline);
4722
}
4723
return this.diffBisect(text1, text2, deadline);
4724
};
4725
4726
/**
4727
* Do the two texts share a substring which is at least half the length of the
4728
* longer text?
4729
* This speedup can produce non-minimal diffs.
4730
* @param {string} text1 First string.
4731
* @param {string} text2 Second string.
4732
* @return {Array.<string>} Five element Array, containing the prefix of
4733
* text1, the suffix of text1, the prefix of text2, the suffix of
4734
* text2 and the common middle. Or null if there was no match.
4735
* @private
4736
*/
4737
DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
4738
var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
4739
longtext = text1.length > text2.length ? text1 : text2;
4740
shorttext = text1.length > text2.length ? text2 : text1;
4741
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
4742
return null; // Pointless.
4743
}
4744
dmp = this; // 'this' becomes 'window' in a closure.
4745
4746
/**
4747
* Does a substring of shorttext exist within longtext such that the substring
4748
* is at least half the length of longtext?
4749
* Closure, but does not reference any external variables.
4750
* @param {string} longtext Longer string.
4751
* @param {string} shorttext Shorter string.
4752
* @param {number} i Start index of quarter length substring within longtext.
4753
* @return {Array.<string>} Five element Array, containing the prefix of
4754
* longtext, the suffix of longtext, the prefix of shorttext, the suffix
4755
* of shorttext and the common middle. Or null if there was no match.
4756
* @private
4757
*/
4758
function diffHalfMatchI(longtext, shorttext, i) {
4759
var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
4760
4761
// Start with a 1/4 length substring at position i as a seed.
4762
seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
4763
j = -1;
4764
bestCommon = '';
4765
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
4766
prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j));
4767
suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
4768
if (bestCommon.length < suffixLength + prefixLength) {
4769
bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
4770
bestLongtextA = longtext.substring(0, i - suffixLength);
4771
bestLongtextB = longtext.substring(i + prefixLength);
4772
bestShorttextA = shorttext.substring(0, j - suffixLength);
4773
bestShorttextB = shorttext.substring(j + prefixLength);
4774
}
4775
}
4776
if (bestCommon.length * 2 >= longtext.length) {
4777
return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
4778
} else {
4779
return null;
4780
}
4781
}
4782
4783
// First check if the second quarter is the seed for a half-match.
4784
hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
4785
4786
// Check again based on the third quarter.
4787
hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
4788
if (!hm1 && !hm2) {
4789
return null;
4790
} else if (!hm2) {
4791
hm = hm1;
4792
} else if (!hm1) {
4793
hm = hm2;
4794
} else {
4795
// Both matched. Select the longest.
4796
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
4797
}
4798
4799
// A half-match was found, sort out the return data.
4800
if (text1.length > text2.length) {
4801
text1A = hm[0];
4802
text1B = hm[1];
4803
text2A = hm[2];
4804
text2B = hm[3];
4805
} else {
4806
text2A = hm[0];
4807
text2B = hm[1];
4808
text1A = hm[2];
4809
text1B = hm[3];
4810
}
4811
midCommon = hm[4];
4812
return [text1A, text1B, text2A, text2B, midCommon];
4813
};
4814
4815
/**
4816
* Do a quick line-level diff on both strings, then rediff the parts for
4817
* greater accuracy.
4818
* This speedup can produce non-minimal diffs.
4819
* @param {string} text1 Old string to be diffed.
4820
* @param {string} text2 New string to be diffed.
4821
* @param {number} deadline Time when the diff should be complete by.
4822
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4823
* @private
4824
*/
4825
DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
4826
var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
4827
4828
// Scan the text on a line-by-line basis first.
4829
a = this.diffLinesToChars(text1, text2);
4830
text1 = a.chars1;
4831
text2 = a.chars2;
4832
linearray = a.lineArray;
4833
diffs = this.DiffMain(text1, text2, false, deadline);
4834
4835
// Convert the diff back to original text.
4836
this.diffCharsToLines(diffs, linearray);
4837
4838
// Eliminate freak matches (e.g. blank lines)
4839
this.diffCleanupSemantic(diffs);
4840
4841
// Rediff any replacement blocks, this time character-by-character.
4842
// Add a dummy entry at the end.
4843
diffs.push([DIFF_EQUAL, '']);
4844
pointer = 0;
4845
countDelete = 0;
4846
countInsert = 0;
4847
textDelete = '';
4848
textInsert = '';
4849
while (pointer < diffs.length) {
4850
switch (diffs[pointer][0]) {
4851
case DIFF_INSERT:
4852
countInsert++;
4853
textInsert += diffs[pointer][1];
4854
break;
4855
case DIFF_DELETE:
4856
countDelete++;
4857
textDelete += diffs[pointer][1];
4858
break;
4859
case DIFF_EQUAL:
4860
// Upon reaching an equality, check for prior redundancies.
4861
if (countDelete >= 1 && countInsert >= 1) {
4862
// Delete the offending records and add the merged ones.
4863
diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert);
4864
pointer = pointer - countDelete - countInsert;
4865
a = this.DiffMain(textDelete, textInsert, false, deadline);
4866
for (j = a.length - 1; j >= 0; j--) {
4867
diffs.splice(pointer, 0, a[j]);
4868
}
4869
pointer = pointer + a.length;
4870
}
4871
countInsert = 0;
4872
countDelete = 0;
4873
textDelete = '';
4874
textInsert = '';
4875
break;
4876
}
4877
pointer++;
4878
}
4879
diffs.pop(); // Remove the dummy entry at the end.
4880
4881
return diffs;
4882
};
4883
4884
/**
4885
* Find the 'middle snake' of a diff, split the problem in two
4886
* and return the recursively constructed diff.
4887
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
4888
* @param {string} text1 Old string to be diffed.
4889
* @param {string} text2 New string to be diffed.
4890
* @param {number} deadline Time at which to bail if not yet complete.
4891
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
4892
* @private
4893
*/
4894
DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) {
4895
var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
4896
4897
// Cache the text lengths to prevent multiple calls.
4898
text1Length = text1.length;
4899
text2Length = text2.length;
4900
maxD = Math.ceil((text1Length + text2Length) / 2);
4901
vOffset = maxD;
4902
vLength = 2 * maxD;
4903
v1 = new Array(vLength);
4904
v2 = new Array(vLength);
4905
4906
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
4907
// integers and undefined.
4908
for (x = 0; x < vLength; x++) {
4909
v1[x] = -1;
4910
v2[x] = -1;
4911
}
4912
v1[vOffset + 1] = 0;
4913
v2[vOffset + 1] = 0;
4914
delta = text1Length - text2Length;
4915
4916
// If the total number of characters is odd, then the front path will collide
4917
// with the reverse path.
4918
front = delta % 2 !== 0;
4919
4920
// Offsets for start and end of k loop.
4921
// Prevents mapping of space beyond the grid.
4922
k1start = 0;
4923
k1end = 0;
4924
k2start = 0;
4925
k2end = 0;
4926
for (d = 0; d < maxD; d++) {
4927
// Bail out if deadline is reached.
4928
if (Date.now() > deadline) {
4929
break;
4930
}
4931
4932
// Walk the front path one step.
4933
for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
4934
k1Offset = vOffset + k1;
4935
if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) {
4936
x1 = v1[k1Offset + 1];
4937
} else {
4938
x1 = v1[k1Offset - 1] + 1;
4939
}
4940
y1 = x1 - k1;
4941
while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
4942
x1++;
4943
y1++;
4944
}
4945
v1[k1Offset] = x1;
4946
if (x1 > text1Length) {
4947
// Ran off the right of the graph.
4948
k1end += 2;
4949
} else if (y1 > text2Length) {
4950
// Ran off the bottom of the graph.
4951
k1start += 2;
4952
} else if (front) {
4953
k2Offset = vOffset + delta - k1;
4954
if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
4955
// Mirror x2 onto top-left coordinate system.
4956
x2 = text1Length - v2[k2Offset];
4957
if (x1 >= x2) {
4958
// Overlap detected.
4959
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4960
}
4961
}
4962
}
4963
}
4964
4965
// Walk the reverse path one step.
4966
for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
4967
k2Offset = vOffset + k2;
4968
if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) {
4969
x2 = v2[k2Offset + 1];
4970
} else {
4971
x2 = v2[k2Offset - 1] + 1;
4972
}
4973
y2 = x2 - k2;
4974
while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
4975
x2++;
4976
y2++;
4977
}
4978
v2[k2Offset] = x2;
4979
if (x2 > text1Length) {
4980
// Ran off the left of the graph.
4981
k2end += 2;
4982
} else if (y2 > text2Length) {
4983
// Ran off the top of the graph.
4984
k2start += 2;
4985
} else if (!front) {
4986
k1Offset = vOffset + delta - k2;
4987
if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
4988
x1 = v1[k1Offset];
4989
y1 = vOffset + x1 - k1Offset;
4990
4991
// Mirror x2 onto top-left coordinate system.
4992
x2 = text1Length - x2;
4993
if (x1 >= x2) {
4994
// Overlap detected.
4995
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
4996
}
4997
}
4998
}
4999
}
5000
}
5001
5002
// Diff took too long and hit the deadline or
5003
// number of diffs equals number of characters, no commonality at all.
5004
return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
5005
};
5006
5007
/**
5008
* Given the location of the 'middle snake', split the diff in two parts
5009
* and recurse.
5010
* @param {string} text1 Old string to be diffed.
5011
* @param {string} text2 New string to be diffed.
5012
* @param {number} x Index of split point in text1.
5013
* @param {number} y Index of split point in text2.
5014
* @param {number} deadline Time at which to bail if not yet complete.
5015
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
5016
* @private
5017
*/
5018
DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) {
5019
var text1a, text1b, text2a, text2b, diffs, diffsb;
5020
text1a = text1.substring(0, x);
5021
text2a = text2.substring(0, y);
5022
text1b = text1.substring(x);
5023
text2b = text2.substring(y);
5024
5025
// Compute both diffs serially.
5026
diffs = this.DiffMain(text1a, text2a, false, deadline);
5027
diffsb = this.DiffMain(text1b, text2b, false, deadline);
5028
return diffs.concat(diffsb);
5029
};
5030
5031
/**
5032
* Reduce the number of edits by eliminating semantically trivial equalities.
5033
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5034
*/
5035
DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
5036
var changes = false;
5037
var equalities = []; // Stack of indices where equalities are found.
5038
var equalitiesLength = 0; // Keeping our own length var is faster in JS.
5039
/** @type {?string} */
5040
var lastequality = null;
5041
5042
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
5043
var pointer = 0; // Index of current position.
5044
5045
// Number of characters that changed prior to the equality.
5046
var lengthInsertions1 = 0;
5047
var lengthDeletions1 = 0;
5048
5049
// Number of characters that changed after the equality.
5050
var lengthInsertions2 = 0;
5051
var lengthDeletions2 = 0;
5052
while (pointer < diffs.length) {
5053
if (diffs[pointer][0] === DIFF_EQUAL) {
5054
// Equality found.
5055
equalities[equalitiesLength++] = pointer;
5056
lengthInsertions1 = lengthInsertions2;
5057
lengthDeletions1 = lengthDeletions2;
5058
lengthInsertions2 = 0;
5059
lengthDeletions2 = 0;
5060
lastequality = diffs[pointer][1];
5061
} else {
5062
// An insertion or deletion.
5063
if (diffs[pointer][0] === DIFF_INSERT) {
5064
lengthInsertions2 += diffs[pointer][1].length;
5065
} else {
5066
lengthDeletions2 += diffs[pointer][1].length;
5067
}
5068
5069
// Eliminate an equality that is smaller or equal to the edits on both
5070
// sides of it.
5071
if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
5072
// Duplicate record.
5073
diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
5074
5075
// Change second copy to insert.
5076
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
5077
5078
// Throw away the equality we just deleted.
5079
equalitiesLength--;
5080
5081
// Throw away the previous equality (it needs to be reevaluated).
5082
equalitiesLength--;
5083
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
5084
5085
// Reset the counters.
5086
lengthInsertions1 = 0;
5087
lengthDeletions1 = 0;
5088
lengthInsertions2 = 0;
5089
lengthDeletions2 = 0;
5090
lastequality = null;
5091
changes = true;
5092
}
5093
}
5094
pointer++;
5095
}
5096
5097
// Normalize the diff.
5098
if (changes) {
5099
this.diffCleanupMerge(diffs);
5100
}
5101
var deletion, insertion, overlapLength1, overlapLength2;
5102
5103
// Find any overlaps between deletions and insertions.
5104
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
5105
// -> <del>abc</del>xxx<ins>def</ins>
5106
// e.g: <del>xxxabc</del><ins>defxxx</ins>
5107
// -> <ins>def</ins>xxx<del>abc</del>
5108
// Only extract an overlap if it is as big as the edit ahead or behind it.
5109
pointer = 1;
5110
while (pointer < diffs.length) {
5111
if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
5112
deletion = diffs[pointer - 1][1];
5113
insertion = diffs[pointer][1];
5114
overlapLength1 = this.diffCommonOverlap(deletion, insertion);
5115
overlapLength2 = this.diffCommonOverlap(insertion, deletion);
5116
if (overlapLength1 >= overlapLength2) {
5117
if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) {
5118
// Overlap found. Insert an equality and trim the surrounding edits.
5119
diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]);
5120
diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1);
5121
diffs[pointer + 1][1] = insertion.substring(overlapLength1);
5122
pointer++;
5123
}
5124
} else {
5125
if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
5126
// Reverse overlap found.
5127
// Insert an equality and swap and trim the surrounding edits.
5128
diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]);
5129
diffs[pointer - 1][0] = DIFF_INSERT;
5130
diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2);
5131
diffs[pointer + 1][0] = DIFF_DELETE;
5132
diffs[pointer + 1][1] = deletion.substring(overlapLength2);
5133
pointer++;
5134
}
5135
}
5136
pointer++;
5137
}
5138
pointer++;
5139
}
5140
};
5141
5142
/**
5143
* Determine if the suffix of one string is the prefix of another.
5144
* @param {string} text1 First string.
5145
* @param {string} text2 Second string.
5146
* @return {number} The number of characters common to the end of the first
5147
* string and the start of the second string.
5148
* @private
5149
*/
5150
DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
5151
// Cache the text lengths to prevent multiple calls.
5152
var text1Length = text1.length;
5153
var text2Length = text2.length;
5154
5155
// Eliminate the null case.
5156
if (text1Length === 0 || text2Length === 0) {
5157
return 0;
5158
}
5159
5160
// Truncate the longer string.
5161
if (text1Length > text2Length) {
5162
text1 = text1.substring(text1Length - text2Length);
5163
} else if (text1Length < text2Length) {
5164
text2 = text2.substring(0, text1Length);
5165
}
5166
var textLength = Math.min(text1Length, text2Length);
5167
5168
// Quick check for the worst case.
5169
if (text1 === text2) {
5170
return textLength;
5171
}
5172
5173
// Start by looking for a single character match
5174
// and increase length until no match is found.
5175
// Performance analysis: https://neil.fraser.name/news/2010/11/04/
5176
var best = 0;
5177
var length = 1;
5178
while (true) {
5179
var pattern = text1.substring(textLength - length);
5180
var found = text2.indexOf(pattern);
5181
if (found === -1) {
5182
return best;
5183
}
5184
length += found;
5185
if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
5186
best = length;
5187
length++;
5188
}
5189
}
5190
};
5191
5192
/**
5193
* Split two texts into an array of strings. Reduce the texts to a string of
5194
* hashes where each Unicode character represents one line.
5195
* @param {string} text1 First string.
5196
* @param {string} text2 Second string.
5197
* @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
5198
* An object containing the encoded text1, the encoded text2 and
5199
* the array of unique strings.
5200
* The zeroth element of the array of unique strings is intentionally blank.
5201
* @private
5202
*/
5203
DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) {
5204
var lineArray = []; // E.g. lineArray[4] === 'Hello\n'
5205
var lineHash = {}; // E.g. lineHash['Hello\n'] === 4
5206
5207
// '\x00' is a valid character, but various debuggers don't like it.
5208
// So we'll insert a junk entry to avoid generating a null character.
5209
lineArray[0] = '';
5210
5211
/**
5212
* Split a text into an array of strings. Reduce the texts to a string of
5213
* hashes where each Unicode character represents one line.
5214
* Modifies linearray and linehash through being a closure.
5215
* @param {string} text String to encode.
5216
* @return {string} Encoded string.
5217
* @private
5218
*/
5219
function diffLinesToCharsMunge(text) {
5220
var chars = '';
5221
5222
// Walk the text, pulling out a substring for each line.
5223
// text.split('\n') would would temporarily double our memory footprint.
5224
// Modifying text would create many large strings to garbage collect.
5225
var lineStart = 0;
5226
var lineEnd = -1;
5227
5228
// Keeping our own length variable is faster than looking it up.
5229
var lineArrayLength = lineArray.length;
5230
while (lineEnd < text.length - 1) {
5231
lineEnd = text.indexOf('\n', lineStart);
5232
if (lineEnd === -1) {
5233
lineEnd = text.length - 1;
5234
}
5235
var line = text.substring(lineStart, lineEnd + 1);
5236
lineStart = lineEnd + 1;
5237
if (hasOwn.call(lineHash, line)) {
5238
chars += String.fromCharCode(lineHash[line]);
5239
} else {
5240
chars += String.fromCharCode(lineArrayLength);
5241
lineHash[line] = lineArrayLength;
5242
lineArray[lineArrayLength++] = line;
5243
}
5244
}
5245
return chars;
5246
}
5247
var chars1 = diffLinesToCharsMunge(text1);
5248
var chars2 = diffLinesToCharsMunge(text2);
5249
return {
5250
chars1: chars1,
5251
chars2: chars2,
5252
lineArray: lineArray
5253
};
5254
};
5255
5256
/**
5257
* Rehydrate the text in a diff from a string of line hashes to real lines of
5258
* text.
5259
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5260
* @param {!Array.<string>} lineArray Array of unique strings.
5261
* @private
5262
*/
5263
DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) {
5264
for (var x = 0; x < diffs.length; x++) {
5265
var chars = diffs[x][1];
5266
var text = [];
5267
for (var y = 0; y < chars.length; y++) {
5268
text[y] = lineArray[chars.charCodeAt(y)];
5269
}
5270
diffs[x][1] = text.join('');
5271
}
5272
};
5273
5274
/**
5275
* Reorder and merge like edit sections. Merge equalities.
5276
* Any edit section can move as long as it doesn't cross an equality.
5277
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
5278
*/
5279
DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) {
5280
diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end.
5281
var pointer = 0;
5282
var countDelete = 0;
5283
var countInsert = 0;
5284
var textDelete = '';
5285
var textInsert = '';
5286
while (pointer < diffs.length) {
5287
switch (diffs[pointer][0]) {
5288
case DIFF_INSERT:
5289
countInsert++;
5290
textInsert += diffs[pointer][1];
5291
pointer++;
5292
break;
5293
case DIFF_DELETE:
5294
countDelete++;
5295
textDelete += diffs[pointer][1];
5296
pointer++;
5297
break;
5298
case DIFF_EQUAL:
5299
// Upon reaching an equality, check for prior redundancies.
5300
if (countDelete + countInsert > 1) {
5301
if (countDelete !== 0 && countInsert !== 0) {
5302
// Factor out any common prefixes.
5303
var commonlength = this.diffCommonPrefix(textInsert, textDelete);
5304
if (commonlength !== 0) {
5305
if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) {
5306
diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength);
5307
} else {
5308
diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
5309
pointer++;
5310
}
5311
textInsert = textInsert.substring(commonlength);
5312
textDelete = textDelete.substring(commonlength);
5313
}
5314
5315
// Factor out any common suffixies.
5316
commonlength = this.diffCommonSuffix(textInsert, textDelete);
5317
if (commonlength !== 0) {
5318
diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1];
5319
textInsert = textInsert.substring(0, textInsert.length - commonlength);
5320
textDelete = textDelete.substring(0, textDelete.length - commonlength);
5321
}
5322
}
5323
5324
// Delete the offending records and add the merged ones.
5325
if (countDelete === 0) {
5326
diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]);
5327
} else if (countInsert === 0) {
5328
diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]);
5329
} else {
5330
diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
5331
}
5332
pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
5333
} else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
5334
// Merge this equality with the previous one.
5335
diffs[pointer - 1][1] += diffs[pointer][1];
5336
diffs.splice(pointer, 1);
5337
} else {
5338
pointer++;
5339
}
5340
countInsert = 0;
5341
countDelete = 0;
5342
textDelete = '';
5343
textInsert = '';
5344
break;
5345
}
5346
}
5347
if (diffs[diffs.length - 1][1] === '') {
5348
diffs.pop(); // Remove the dummy entry at the end.
5349
}
5350
5351
// Second pass: look for single edits surrounded on both sides by equalities
5352
// which can be shifted sideways to eliminate an equality.
5353
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
5354
var changes = false;
5355
pointer = 1;
5356
5357
// Intentionally ignore the first and last element (don't need checking).
5358
while (pointer < diffs.length - 1) {
5359
if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
5360
var diffPointer = diffs[pointer][1];
5361
var position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
5362
5363
// This is a single edit surrounded by equalities.
5364
if (position === diffs[pointer - 1][1]) {
5365
// Shift the edit over the previous equality.
5366
diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
5367
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
5368
diffs.splice(pointer - 1, 1);
5369
changes = true;
5370
} else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
5371
// Shift the edit over the next equality.
5372
diffs[pointer - 1][1] += diffs[pointer + 1][1];
5373
diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
5374
diffs.splice(pointer + 1, 1);
5375
changes = true;
5376
}
5377
}
5378
pointer++;
5379
}
5380
5381
// If shifts were made, the diff needs reordering and another shift sweep.
5382
if (changes) {
5383
this.diffCleanupMerge(diffs);
5384
}
5385
};
5386
function diff(o, n) {
5387
var diff, output, text;
5388
diff = new DiffMatchPatch();
5389
output = diff.DiffMain(o, n);
5390
diff.diffCleanupEfficiency(output);
5391
text = diff.diffPrettyHtml(output);
5392
return text;
5393
}
5394
5395
var QUnit = {};
5396
5397
// The "currentModule" object would ideally be defined using the createModule()
5398
// function. Since it isn't, add the missing suiteReport property to it now that
5399
// we have loaded all source code required to do so.
5400
//
5401
// TODO: Consider defining currentModule in core.js or module.js in its entirely
5402
// rather than partly in config.js and partly here.
5403
config.currentModule.suiteReport = runSuite;
5404
config.pq = new ProcessingQueue(test);
5405
var globalStartCalled = false;
5406
var runStarted = false;
5407
5408
// Figure out if we're running the tests from a server or not
5409
QUnit.isLocal = window$1 && window$1.location && window$1.location.protocol === 'file:';
5410
5411
// Expose the current QUnit version
5412
QUnit.version = '2.23.1';
5413
extend(QUnit, {
5414
config: config,
5415
diff: diff,
5416
dump: dump,
5417
equiv: equiv,
5418
reporters: reporters,
5419
hooks: hooks,
5420
is: is,
5421
objectType: objectType,
5422
on: on,
5423
onError: onWindowError,
5424
onUncaughtException: onUncaughtException,
5425
pushFailure: pushFailure,
5426
assert: Assert.prototype,
5427
module: module$1,
5428
test: test,
5429
// alias other test flavors for easy access
5430
todo: test.todo,
5431
skip: test.skip,
5432
only: test.only,
5433
start: function start(count) {
5434
if (config.current) {
5435
throw new Error('QUnit.start cannot be called inside a test context.');
5436
}
5437
var globalStartAlreadyCalled = globalStartCalled;
5438
globalStartCalled = true;
5439
if (runStarted) {
5440
throw new Error('Called start() while test already started running');
5441
}
5442
if (globalStartAlreadyCalled || count > 1) {
5443
throw new Error('Called start() outside of a test context too many times');
5444
}
5445
if (config.autostart) {
5446
throw new Error('Called start() outside of a test context when ' + 'QUnit.config.autostart was true');
5447
}
5448
5449
// Until we remove QUnit.load() in QUnit 3, we keep `pageLoaded`.
5450
// It no longer serves any purpose other than to support old test runners
5451
// that still call only QUnit.load(), or that call both it and QUnit.start().
5452
if (!config.pageLoaded) {
5453
// If the test runner used `autostart = false` and is calling QUnit.start()
5454
// to tell is their resources are ready, but the browser isn't ready yet,
5455
// then enable autostart now, and we'll let the tests really start after
5456
// the browser's "load" event handler calls autostart().
5457
config.autostart = true;
5458
5459
// If we're in Node or another non-browser environment, we start now as there
5460
// won't be any "load" event. We return early either way since autostart
5461
// is responsible for calling scheduleBegin (avoid "beginning" twice).
5462
if (!document) {
5463
QUnit.autostart();
5464
}
5465
return;
5466
}
5467
scheduleBegin();
5468
},
5469
onUnhandledRejection: function onUnhandledRejection(reason) {
5470
Logger.warn('QUnit.onUnhandledRejection is deprecated and will be removed in QUnit 3.0.' + ' Please use QUnit.onUncaughtException instead.');
5471
onUncaughtException(reason);
5472
},
5473
extend: function extend$1() {
5474
Logger.warn('QUnit.extend is deprecated and will be removed in QUnit 3.0.' + ' Please use Object.assign instead.');
5475
5476
// delegate to utility implementation, which does not warn and can be used elsewhere internally
5477
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
5478
args[_key] = arguments[_key];
5479
}
5480
return extend.apply(this, args);
5481
},
5482
load: function load() {
5483
Logger.warn('QUnit.load is deprecated and will be removed in QUnit 3.0.' + ' https://qunitjs.com/api/QUnit/load/');
5484
QUnit.autostart();
5485
},
5486
/**
5487
* @internal
5488
*/
5489
autostart: function autostart() {
5490
config.pageLoaded = true;
5491
5492
// Initialize the configuration options
5493
// TODO: Move this to config.js in QUnit 3.
5494
extend(config, {
5495
started: 0,
5496
updateRate: 1000,
5497
autostart: true,
5498
filter: ''
5499
}, true);
5500
if (!runStarted) {
5501
config.blocking = false;
5502
if (config.autostart) {
5503
scheduleBegin();
5504
}
5505
}
5506
},
5507
stack: function stack(offset) {
5508
offset = (offset || 0) + 2;
5509
// Support Safari: Use temp variable to avoid TCO for consistent cross-browser result
5510
// https://bugs.webkit.org/show_bug.cgi?id=276187
5511
var source = sourceFromStacktrace(offset);
5512
return source;
5513
}
5514
});
5515
registerLoggingCallbacks(QUnit);
5516
function scheduleBegin() {
5517
runStarted = true;
5518
5519
// Add a slight delay to allow definition of more modules and tests.
5520
if (setTimeout$1) {
5521
setTimeout$1(function () {
5522
begin();
5523
});
5524
} else {
5525
begin();
5526
}
5527
}
5528
function unblockAndAdvanceQueue() {
5529
config.blocking = false;
5530
config.pq.advance();
5531
}
5532
function begin() {
5533
if (config.started) {
5534
unblockAndAdvanceQueue();
5535
return;
5536
}
5537
5538
// The test run hasn't officially begun yet
5539
// Record the time of the test run's beginning
5540
config.started = performance.now();
5541
5542
// Delete the loose unnamed module if unused.
5543
if (config.modules[0].name === '' && config.modules[0].tests.length === 0) {
5544
config.modules.shift();
5545
}
5546
var modulesLog = [];
5547
for (var i = 0; i < config.modules.length; i++) {
5548
// Don't expose the unnamed global test module to plugins.
5549
if (config.modules[i].name !== '') {
5550
modulesLog.push({
5551
name: config.modules[i].name,
5552
moduleId: config.modules[i].moduleId,
5553
// Added in QUnit 1.16.0 for internal use by html-reporter,
5554
// but no longer used since QUnit 2.7.0.
5555
// @deprecated Kept unofficially to be removed in QUnit 3.0.
5556
tests: config.modules[i].tests
5557
});
5558
}
5559
}
5560
5561
// The test run is officially beginning now
5562
emit('runStart', runSuite.start(true));
5563
runLoggingCallbacks('begin', {
5564
totalTests: Test.count,
5565
modules: modulesLog
5566
}).then(unblockAndAdvanceQueue);
5567
}
5568
exportQUnit(QUnit);
5569
5570
(function () {
5571
if (!window$1 || !document) {
5572
return;
5573
}
5574
var config = QUnit.config;
5575
var hasOwn = Object.prototype.hasOwnProperty;
5576
5577
// Stores fixture HTML for resetting later
5578
function storeFixture() {
5579
// Avoid overwriting user-defined values
5580
// TODO: Change to negative null/undefined check once declared in /src/config.js
5581
if (hasOwn.call(config, 'fixture')) {
5582
return;
5583
}
5584
var fixture = document.getElementById('qunit-fixture');
5585
if (fixture) {
5586
config.fixture = fixture.cloneNode(true);
5587
}
5588
}
5589
QUnit.begin(storeFixture);
5590
5591
// Resets the fixture DOM element if available.
5592
function resetFixture() {
5593
if (config.fixture == null) {
5594
return;
5595
}
5596
var fixture = document.getElementById('qunit-fixture');
5597
var resetFixtureType = _typeof(config.fixture);
5598
if (resetFixtureType === 'string') {
5599
// support user defined values for `config.fixture`
5600
var newFixture = document.createElement('div');
5601
newFixture.setAttribute('id', 'qunit-fixture');
5602
newFixture.innerHTML = config.fixture;
5603
fixture.parentNode.replaceChild(newFixture, fixture);
5604
} else {
5605
var clonedFixture = config.fixture.cloneNode(true);
5606
fixture.parentNode.replaceChild(clonedFixture, fixture);
5607
}
5608
}
5609
QUnit.testStart(resetFixture);
5610
})();
5611
5612
(function () {
5613
// Only interact with URLs via window.location
5614
var location = typeof window$1 !== 'undefined' && window$1.location;
5615
if (!location) {
5616
return;
5617
}
5618
var urlParams = getUrlParams();
5619
5620
// TODO: Move to /src/core/ in QUnit 3
5621
// TODO: Document this as public API (read-only)
5622
QUnit.urlParams = urlParams;
5623
5624
// TODO: Move to /src/core/config.js in QUnit 3,
5625
// in accordance with /docs/api/config.index.md#order
5626
QUnit.config.filter = urlParams.filter;
5627
// NOTE: Based on readFlatPreconfigNumber from QUnit 3.
5628
if (/^[0-9]+$/.test(urlParams.maxDepth)) {
5629
QUnit.config.maxDepth = QUnit.dump.maxDepth = +urlParams.maxDepth;
5630
}
5631
QUnit.config.module = urlParams.module;
5632
QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
5633
QUnit.config.testId = [].concat(urlParams.testId || []);
5634
5635
// Test order randomization
5636
// Generate a random seed if `?seed` is specified without a value (boolean true),
5637
// or when set to the string "true".
5638
if (urlParams.seed === 'true' || urlParams.seed === true) {
5639
// NOTE: This duplicates logic from /src/core/config.js. Consolidated in QUnit 3.
5640
QUnit.config.seed = (Math.random().toString(36) + '0000000000').slice(2, 12);
5641
} else if (urlParams.seed) {
5642
QUnit.config.seed = urlParams.seed;
5643
}
5644
5645
// Add URL-parameter-mapped config values with UI form rendering data
5646
QUnit.config.urlConfig.push({
5647
id: 'hidepassed',
5648
label: 'Hide passed tests',
5649
tooltip: 'Only show tests and assertions that fail. Stored as query-strings.'
5650
}, {
5651
id: 'noglobals',
5652
label: 'Check for Globals',
5653
tooltip: 'Enabling this will test if any test introduces new properties on the ' + 'global object (`window` in Browsers). Stored as query-strings.'
5654
}, {
5655
id: 'notrycatch',
5656
label: 'No try-catch',
5657
tooltip: 'Enabling this will run tests outside of a try-catch block. Makes debugging ' + 'exceptions in IE reasonable. Stored as query-strings.'
5658
});
5659
QUnit.begin(function () {
5660
var urlConfig = QUnit.config.urlConfig;
5661
for (var i = 0; i < urlConfig.length; i++) {
5662
// Options can be either strings or objects with nonempty "id" properties
5663
var option = QUnit.config.urlConfig[i];
5664
if (typeof option !== 'string') {
5665
option = option.id;
5666
}
5667
if (QUnit.config[option] === undefined) {
5668
QUnit.config[option] = urlParams[option];
5669
}
5670
}
5671
});
5672
function getUrlParams() {
5673
var urlParams = Object.create(null);
5674
var params = location.search.slice(1).split('&');
5675
var length = params.length;
5676
for (var i = 0; i < length; i++) {
5677
if (params[i]) {
5678
var param = params[i].split('=');
5679
var name = decodeQueryParam(param[0]);
5680
5681
// Allow just a key to turn on a flag, e.g., test.html?noglobals
5682
var value = param.length === 1 || decodeQueryParam(param.slice(1).join('='));
5683
if (name in urlParams) {
5684
urlParams[name] = [].concat(urlParams[name], value);
5685
} else {
5686
urlParams[name] = value;
5687
}
5688
}
5689
}
5690
return urlParams;
5691
}
5692
function decodeQueryParam(param) {
5693
return decodeURIComponent(param.replace(/\+/g, '%20'));
5694
}
5695
})();
5696
5697
var fuzzysort$1 = {exports: {}};
5698
5699
(function (module) {
5700
(function (root, UMD) {
5701
if (module.exports) module.exports = UMD();else root.fuzzysort = UMD();
5702
})(commonjsGlobal, function UMD() {
5703
function fuzzysortNew(instanceOptions) {
5704
var fuzzysort = {
5705
single: function single(search, target, options) {
5706
if (search == 'farzher') return {
5707
target: "farzher was here (^-^*)/",
5708
score: 0,
5709
indexes: [0, 1, 2, 3, 4, 5, 6]
5710
};
5711
if (!search) return null;
5712
if (!isObj(search)) search = fuzzysort.getPreparedSearch(search);
5713
if (!target) return null;
5714
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5715
var allowTypo = options && options.allowTypo !== undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo !== undefined ? instanceOptions.allowTypo : true;
5716
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
5717
return algorithm(search, target, search[0]);
5718
},
5719
go: function go(search, targets, options) {
5720
if (search == 'farzher') return [{
5721
target: "farzher was here (^-^*)/",
5722
score: 0,
5723
indexes: [0, 1, 2, 3, 4, 5, 6],
5724
obj: targets ? targets[0] : null
5725
}];
5726
if (!search) return noResults;
5727
search = fuzzysort.prepareSearch(search);
5728
var searchLowerCode = search[0];
5729
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
5730
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
5731
var allowTypo = options && options.allowTypo !== undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo !== undefined ? instanceOptions.allowTypo : true;
5732
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
5733
var resultsLen = 0;
5734
var limitedCount = 0;
5735
var targetsLen = targets.length;
5736
5737
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
5738
5739
// options.keys
5740
if (options && options.keys) {
5741
var scoreFn = options.scoreFn || defaultScoreFn;
5742
var keys = options.keys;
5743
var keysLen = keys.length;
5744
for (var i = targetsLen - 1; i >= 0; --i) {
5745
var obj = targets[i];
5746
var objResults = new Array(keysLen);
5747
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
5748
var key = keys[keyI];
5749
var target = getValue(obj, key);
5750
if (!target) {
5751
objResults[keyI] = null;
5752
continue;
5753
}
5754
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5755
objResults[keyI] = algorithm(search, target, searchLowerCode);
5756
}
5757
objResults.obj = obj; // before scoreFn so scoreFn can use it
5758
var score = scoreFn(objResults);
5759
if (score === null) continue;
5760
if (score < threshold) continue;
5761
objResults.score = score;
5762
if (resultsLen < limit) {
5763
q.add(objResults);
5764
++resultsLen;
5765
} else {
5766
++limitedCount;
5767
if (score > q.peek().score) q.replaceTop(objResults);
5768
}
5769
}
5770
5771
// options.key
5772
} else if (options && options.key) {
5773
var key = options.key;
5774
for (var i = targetsLen - 1; i >= 0; --i) {
5775
var obj = targets[i];
5776
var target = getValue(obj, key);
5777
if (!target) continue;
5778
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5779
var result = algorithm(search, target, searchLowerCode);
5780
if (result === null) continue;
5781
if (result.score < threshold) continue;
5782
5783
// have to clone result so duplicate targets from different obj can each reference the correct obj
5784
result = {
5785
target: result.target,
5786
_targetLowerCodes: null,
5787
_nextBeginningIndexes: null,
5788
score: result.score,
5789
indexes: result.indexes,
5790
obj: obj
5791
}; // hidden
5792
5793
if (resultsLen < limit) {
5794
q.add(result);
5795
++resultsLen;
5796
} else {
5797
++limitedCount;
5798
if (result.score > q.peek().score) q.replaceTop(result);
5799
}
5800
}
5801
5802
// no keys
5803
} else {
5804
for (var i = targetsLen - 1; i >= 0; --i) {
5805
var target = targets[i];
5806
if (!target) continue;
5807
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5808
var result = algorithm(search, target, searchLowerCode);
5809
if (result === null) continue;
5810
if (result.score < threshold) continue;
5811
if (resultsLen < limit) {
5812
q.add(result);
5813
++resultsLen;
5814
} else {
5815
++limitedCount;
5816
if (result.score > q.peek().score) q.replaceTop(result);
5817
}
5818
}
5819
}
5820
if (resultsLen === 0) return noResults;
5821
var results = new Array(resultsLen);
5822
for (var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll();
5823
results.total = resultsLen + limitedCount;
5824
return results;
5825
},
5826
goAsync: function goAsync(search, targets, options) {
5827
var canceled = false;
5828
var p = new Promise(function (resolve, reject) {
5829
if (search == 'farzher') return resolve([{
5830
target: "farzher was here (^-^*)/",
5831
score: 0,
5832
indexes: [0, 1, 2, 3, 4, 5, 6],
5833
obj: targets ? targets[0] : null
5834
}]);
5835
if (!search) return resolve(noResults);
5836
search = fuzzysort.prepareSearch(search);
5837
var searchLowerCode = search[0];
5838
var q = fastpriorityqueue();
5839
var iCurrent = targets.length - 1;
5840
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
5841
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
5842
var allowTypo = options && options.allowTypo !== undefined ? options.allowTypo : instanceOptions && instanceOptions.allowTypo !== undefined ? instanceOptions.allowTypo : true;
5843
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
5844
var resultsLen = 0;
5845
var limitedCount = 0;
5846
function step() {
5847
if (canceled) return reject('canceled');
5848
var startMs = Date.now();
5849
5850
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
5851
5852
// options.keys
5853
if (options && options.keys) {
5854
var scoreFn = options.scoreFn || defaultScoreFn;
5855
var keys = options.keys;
5856
var keysLen = keys.length;
5857
for (; iCurrent >= 0; --iCurrent) {
5858
if (iCurrent % 1000 /*itemsPerCheck*/ === 0) {
5859
if (Date.now() - startMs >= 10 /*asyncInterval*/) {
5860
isNode ? setImmediate(step) : setTimeout(step);
5861
return;
5862
}
5863
}
5864
var obj = targets[iCurrent];
5865
var objResults = new Array(keysLen);
5866
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
5867
var key = keys[keyI];
5868
var target = getValue(obj, key);
5869
if (!target) {
5870
objResults[keyI] = null;
5871
continue;
5872
}
5873
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5874
objResults[keyI] = algorithm(search, target, searchLowerCode);
5875
}
5876
objResults.obj = obj; // before scoreFn so scoreFn can use it
5877
var score = scoreFn(objResults);
5878
if (score === null) continue;
5879
if (score < threshold) continue;
5880
objResults.score = score;
5881
if (resultsLen < limit) {
5882
q.add(objResults);
5883
++resultsLen;
5884
} else {
5885
++limitedCount;
5886
if (score > q.peek().score) q.replaceTop(objResults);
5887
}
5888
}
5889
5890
// options.key
5891
} else if (options && options.key) {
5892
var key = options.key;
5893
for (; iCurrent >= 0; --iCurrent) {
5894
if (iCurrent % 1000 /*itemsPerCheck*/ === 0) {
5895
if (Date.now() - startMs >= 10 /*asyncInterval*/) {
5896
isNode ? setImmediate(step) : setTimeout(step);
5897
return;
5898
}
5899
}
5900
var obj = targets[iCurrent];
5901
var target = getValue(obj, key);
5902
if (!target) continue;
5903
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5904
var result = algorithm(search, target, searchLowerCode);
5905
if (result === null) continue;
5906
if (result.score < threshold) continue;
5907
5908
// have to clone result so duplicate targets from different obj can each reference the correct obj
5909
result = {
5910
target: result.target,
5911
_targetLowerCodes: null,
5912
_nextBeginningIndexes: null,
5913
score: result.score,
5914
indexes: result.indexes,
5915
obj: obj
5916
}; // hidden
5917
5918
if (resultsLen < limit) {
5919
q.add(result);
5920
++resultsLen;
5921
} else {
5922
++limitedCount;
5923
if (result.score > q.peek().score) q.replaceTop(result);
5924
}
5925
}
5926
5927
// no keys
5928
} else {
5929
for (; iCurrent >= 0; --iCurrent) {
5930
if (iCurrent % 1000 /*itemsPerCheck*/ === 0) {
5931
if (Date.now() - startMs >= 10 /*asyncInterval*/) {
5932
isNode ? setImmediate(step) : setTimeout(step);
5933
return;
5934
}
5935
}
5936
var target = targets[iCurrent];
5937
if (!target) continue;
5938
if (!isObj(target)) target = fuzzysort.getPrepared(target);
5939
var result = algorithm(search, target, searchLowerCode);
5940
if (result === null) continue;
5941
if (result.score < threshold) continue;
5942
if (resultsLen < limit) {
5943
q.add(result);
5944
++resultsLen;
5945
} else {
5946
++limitedCount;
5947
if (result.score > q.peek().score) q.replaceTop(result);
5948
}
5949
}
5950
}
5951
if (resultsLen === 0) return resolve(noResults);
5952
var results = new Array(resultsLen);
5953
for (var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll();
5954
results.total = resultsLen + limitedCount;
5955
resolve(results);
5956
}
5957
isNode ? setImmediate(step) : step(); //setTimeout here is too slow
5958
});
5959
p.cancel = function () {
5960
canceled = true;
5961
};
5962
return p;
5963
},
5964
highlight: function highlight(result, hOpen, hClose) {
5965
if (typeof hOpen == 'function') return fuzzysort.highlightCallback(result, hOpen);
5966
if (result === null) return null;
5967
if (hOpen === undefined) hOpen = '<b>';
5968
if (hClose === undefined) hClose = '</b>';
5969
var highlighted = '';
5970
var matchesIndex = 0;
5971
var opened = false;
5972
var target = result.target;
5973
var targetLen = target.length;
5974
var matchesBest = result.indexes;
5975
for (var i = 0; i < targetLen; ++i) {
5976
var char = target[i];
5977
if (matchesBest[matchesIndex] === i) {
5978
++matchesIndex;
5979
if (!opened) {
5980
opened = true;
5981
highlighted += hOpen;
5982
}
5983
if (matchesIndex === matchesBest.length) {
5984
highlighted += char + hClose + target.substr(i + 1);
5985
break;
5986
}
5987
} else {
5988
if (opened) {
5989
opened = false;
5990
highlighted += hClose;
5991
}
5992
}
5993
highlighted += char;
5994
}
5995
return highlighted;
5996
},
5997
highlightCallback: function highlightCallback(result, cb) {
5998
if (result === null) return null;
5999
var target = result.target;
6000
var targetLen = target.length;
6001
var indexes = result.indexes;
6002
var highlighted = '';
6003
var matchI = 0;
6004
var indexesI = 0;
6005
var opened = false;
6006
var result = [];
6007
for (var i = 0; i < targetLen; ++i) {
6008
var char = target[i];
6009
if (indexes[indexesI] === i) {
6010
++indexesI;
6011
if (!opened) {
6012
opened = true;
6013
result.push(highlighted);
6014
highlighted = '';
6015
}
6016
if (indexesI === indexes.length) {
6017
highlighted += char;
6018
result.push(cb(highlighted, matchI++));
6019
highlighted = '';
6020
result.push(target.substr(i + 1));
6021
break;
6022
}
6023
} else {
6024
if (opened) {
6025
opened = false;
6026
result.push(cb(highlighted, matchI++));
6027
highlighted = '';
6028
}
6029
}
6030
highlighted += char;
6031
}
6032
return result;
6033
},
6034
prepare: function prepare(target) {
6035
if (!target) return {
6036
target: '',
6037
_targetLowerCodes: [0 /*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/],
6038
_nextBeginningIndexes: null,
6039
score: null,
6040
indexes: null,
6041
obj: null
6042
}; // hidden
6043
return {
6044
target: target,
6045
_targetLowerCodes: fuzzysort.prepareLowerCodes(target),
6046
_nextBeginningIndexes: null,
6047
score: null,
6048
indexes: null,
6049
obj: null
6050
}; // hidden
6051
},
6052
prepareSlow: function prepareSlow(target) {
6053
if (!target) return {
6054
target: '',
6055
_targetLowerCodes: [0 /*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/],
6056
_nextBeginningIndexes: null,
6057
score: null,
6058
indexes: null,
6059
obj: null
6060
}; // hidden
6061
return {
6062
target: target,
6063
_targetLowerCodes: fuzzysort.prepareLowerCodes(target),
6064
_nextBeginningIndexes: fuzzysort.prepareNextBeginningIndexes(target),
6065
score: null,
6066
indexes: null,
6067
obj: null
6068
}; // hidden
6069
},
6070
prepareSearch: function prepareSearch(search) {
6071
if (!search) search = '';
6072
return fuzzysort.prepareLowerCodes(search);
6073
},
6074
// Below this point is only internal code
6075
// Below this point is only internal code
6076
// Below this point is only internal code
6077
// Below this point is only internal code
6078
6079
getPrepared: function getPrepared(target) {
6080
if (target.length > 999) return fuzzysort.prepare(target); // don't cache huge targets
6081
var targetPrepared = preparedCache.get(target);
6082
if (targetPrepared !== undefined) return targetPrepared;
6083
targetPrepared = fuzzysort.prepare(target);
6084
preparedCache.set(target, targetPrepared);
6085
return targetPrepared;
6086
},
6087
getPreparedSearch: function getPreparedSearch(search) {
6088
if (search.length > 999) return fuzzysort.prepareSearch(search); // don't cache huge searches
6089
var searchPrepared = preparedSearchCache.get(search);
6090
if (searchPrepared !== undefined) return searchPrepared;
6091
searchPrepared = fuzzysort.prepareSearch(search);
6092
preparedSearchCache.set(search, searchPrepared);
6093
return searchPrepared;
6094
},
6095
algorithm: function algorithm(searchLowerCodes, prepared, searchLowerCode) {
6096
var targetLowerCodes = prepared._targetLowerCodes;
6097
var searchLen = searchLowerCodes.length;
6098
var targetLen = targetLowerCodes.length;
6099
var searchI = 0; // where we at
6100
var targetI = 0; // where you at
6101
var typoSimpleI = 0;
6102
var matchesSimpleLen = 0;
6103
6104
// very basic fuzzy match; to remove non-matching targets ASAP!
6105
// walk through target. find sequential matches.
6106
// if all chars aren't found then exit
6107
for (;;) {
6108
var isMatch = searchLowerCode === targetLowerCodes[targetI];
6109
if (isMatch) {
6110
matchesSimple[matchesSimpleLen++] = targetI;
6111
++searchI;
6112
if (searchI === searchLen) break;
6113
searchLowerCode = searchLowerCodes[typoSimpleI === 0 ? searchI : typoSimpleI === searchI ? searchI + 1 : typoSimpleI === searchI - 1 ? searchI - 1 : searchI];
6114
}
6115
++targetI;
6116
if (targetI >= targetLen) {
6117
// Failed to find searchI
6118
// Check for typo or exit
6119
// we go as far as possible before trying to transpose
6120
// then we transpose backwards until we reach the beginning
6121
for (;;) {
6122
if (searchI <= 1) return null; // not allowed to transpose first char
6123
if (typoSimpleI === 0) {
6124
// we haven't tried to transpose yet
6125
--searchI;
6126
var searchLowerCodeNew = searchLowerCodes[searchI];
6127
if (searchLowerCode === searchLowerCodeNew) continue; // doesn't make sense to transpose a repeat char
6128
typoSimpleI = searchI;
6129
} else {
6130
if (typoSimpleI === 1) return null; // reached the end of the line for transposing
6131
--typoSimpleI;
6132
searchI = typoSimpleI;
6133
searchLowerCode = searchLowerCodes[searchI + 1];
6134
var searchLowerCodeNew = searchLowerCodes[searchI];
6135
if (searchLowerCode === searchLowerCodeNew) continue; // doesn't make sense to transpose a repeat char
6136
}
6137
matchesSimpleLen = searchI;
6138
targetI = matchesSimple[matchesSimpleLen - 1] + 1;
6139
break;
6140
}
6141
}
6142
}
6143
var searchI = 0;
6144
var typoStrictI = 0;
6145
var successStrict = false;
6146
var matchesStrictLen = 0;
6147
var nextBeginningIndexes = prepared._nextBeginningIndexes;
6148
if (nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);
6149
var firstPossibleI = targetI = matchesSimple[0] === 0 ? 0 : nextBeginningIndexes[matchesSimple[0] - 1];
6150
6151
// Our target string successfully matched all characters in sequence!
6152
// Let's try a more advanced and strict test to improve the score
6153
// only count it as a match if it's consecutive or a beginning character!
6154
if (targetI !== targetLen) for (;;) {
6155
if (targetI >= targetLen) {
6156
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
6157
if (searchI <= 0) {
6158
// We failed to push chars forward for a better match
6159
// transpose, starting from the beginning
6160
++typoStrictI;
6161
if (typoStrictI > searchLen - 2) break;
6162
if (searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI + 1]) continue; // doesn't make sense to transpose a repeat char
6163
targetI = firstPossibleI;
6164
continue;
6165
}
6166
--searchI;
6167
var lastMatch = matchesStrict[--matchesStrictLen];
6168
targetI = nextBeginningIndexes[lastMatch];
6169
} else {
6170
var isMatch = searchLowerCodes[typoStrictI === 0 ? searchI : typoStrictI === searchI ? searchI + 1 : typoStrictI === searchI - 1 ? searchI - 1 : searchI] === targetLowerCodes[targetI];
6171
if (isMatch) {
6172
matchesStrict[matchesStrictLen++] = targetI;
6173
++searchI;
6174
if (searchI === searchLen) {
6175
successStrict = true;
6176
break;
6177
}
6178
++targetI;
6179
} else {
6180
targetI = nextBeginningIndexes[targetI];
6181
}
6182
}
6183
}
6184
{
6185
// tally up the score & keep track of matches for highlighting later
6186
if (successStrict) {
6187
var matchesBest = matchesStrict;
6188
var matchesBestLen = matchesStrictLen;
6189
} else {
6190
var matchesBest = matchesSimple;
6191
var matchesBestLen = matchesSimpleLen;
6192
}
6193
var score = 0;
6194
var lastTargetI = -1;
6195
for (var i = 0; i < searchLen; ++i) {
6196
var targetI = matchesBest[i];
6197
// score only goes down if they're not consecutive
6198
if (lastTargetI !== targetI - 1) score -= targetI;
6199
lastTargetI = targetI;
6200
}
6201
if (!successStrict) {
6202
score *= 1000;
6203
if (typoSimpleI !== 0) score += -20; /*typoPenalty*/
6204
} else {
6205
if (typoStrictI !== 0) score += -20; /*typoPenalty*/
6206
}
6207
score -= targetLen - searchLen;
6208
prepared.score = score;
6209
prepared.indexes = new Array(matchesBestLen);
6210
for (var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i];
6211
return prepared;
6212
}
6213
},
6214
algorithmNoTypo: function algorithmNoTypo(searchLowerCodes, prepared, searchLowerCode) {
6215
var targetLowerCodes = prepared._targetLowerCodes;
6216
var searchLen = searchLowerCodes.length;
6217
var targetLen = targetLowerCodes.length;
6218
var searchI = 0; // where we at
6219
var targetI = 0; // where you at
6220
var matchesSimpleLen = 0;
6221
6222
// very basic fuzzy match; to remove non-matching targets ASAP!
6223
// walk through target. find sequential matches.
6224
// if all chars aren't found then exit
6225
for (;;) {
6226
var isMatch = searchLowerCode === targetLowerCodes[targetI];
6227
if (isMatch) {
6228
matchesSimple[matchesSimpleLen++] = targetI;
6229
++searchI;
6230
if (searchI === searchLen) break;
6231
searchLowerCode = searchLowerCodes[searchI];
6232
}
6233
++targetI;
6234
if (targetI >= targetLen) return null; // Failed to find searchI
6235
}
6236
var searchI = 0;
6237
var successStrict = false;
6238
var matchesStrictLen = 0;
6239
var nextBeginningIndexes = prepared._nextBeginningIndexes;
6240
if (nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);
6241
targetI = matchesSimple[0] === 0 ? 0 : nextBeginningIndexes[matchesSimple[0] - 1];
6242
6243
// Our target string successfully matched all characters in sequence!
6244
// Let's try a more advanced and strict test to improve the score
6245
// only count it as a match if it's consecutive or a beginning character!
6246
if (targetI !== targetLen) for (;;) {
6247
if (targetI >= targetLen) {
6248
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
6249
if (searchI <= 0) break; // We failed to push chars forward for a better match
6250
6251
--searchI;
6252
var lastMatch = matchesStrict[--matchesStrictLen];
6253
targetI = nextBeginningIndexes[lastMatch];
6254
} else {
6255
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI];
6256
if (isMatch) {
6257
matchesStrict[matchesStrictLen++] = targetI;
6258
++searchI;
6259
if (searchI === searchLen) {
6260
successStrict = true;
6261
break;
6262
}
6263
++targetI;
6264
} else {
6265
targetI = nextBeginningIndexes[targetI];
6266
}
6267
}
6268
}
6269
{
6270
// tally up the score & keep track of matches for highlighting later
6271
if (successStrict) {
6272
var matchesBest = matchesStrict;
6273
var matchesBestLen = matchesStrictLen;
6274
} else {
6275
var matchesBest = matchesSimple;
6276
var matchesBestLen = matchesSimpleLen;
6277
}
6278
var score = 0;
6279
var lastTargetI = -1;
6280
for (var i = 0; i < searchLen; ++i) {
6281
var targetI = matchesBest[i];
6282
// score only goes down if they're not consecutive
6283
if (lastTargetI !== targetI - 1) score -= targetI;
6284
lastTargetI = targetI;
6285
}
6286
if (!successStrict) score *= 1000;
6287
score -= targetLen - searchLen;
6288
prepared.score = score;
6289
prepared.indexes = new Array(matchesBestLen);
6290
for (var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i];
6291
return prepared;
6292
}
6293
},
6294
prepareLowerCodes: function prepareLowerCodes(str) {
6295
var strLen = str.length;
6296
var lowerCodes = []; // new Array(strLen) sparse array is too slow
6297
var lower = str.toLowerCase();
6298
for (var i = 0; i < strLen; ++i) lowerCodes[i] = lower.charCodeAt(i);
6299
return lowerCodes;
6300
},
6301
prepareBeginningIndexes: function prepareBeginningIndexes(target) {
6302
var targetLen = target.length;
6303
var beginningIndexes = [];
6304
var beginningIndexesLen = 0;
6305
var wasUpper = false;
6306
var wasAlphanum = false;
6307
for (var i = 0; i < targetLen; ++i) {
6308
var targetCode = target.charCodeAt(i);
6309
var isUpper = targetCode >= 65 && targetCode <= 90;
6310
var isAlphanum = isUpper || targetCode >= 97 && targetCode <= 122 || targetCode >= 48 && targetCode <= 57;
6311
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum;
6312
wasUpper = isUpper;
6313
wasAlphanum = isAlphanum;
6314
if (isBeginning) beginningIndexes[beginningIndexesLen++] = i;
6315
}
6316
return beginningIndexes;
6317
},
6318
prepareNextBeginningIndexes: function prepareNextBeginningIndexes(target) {
6319
var targetLen = target.length;
6320
var beginningIndexes = fuzzysort.prepareBeginningIndexes(target);
6321
var nextBeginningIndexes = []; // new Array(targetLen) sparse array is too slow
6322
var lastIsBeginning = beginningIndexes[0];
6323
var lastIsBeginningI = 0;
6324
for (var i = 0; i < targetLen; ++i) {
6325
if (lastIsBeginning > i) {
6326
nextBeginningIndexes[i] = lastIsBeginning;
6327
} else {
6328
lastIsBeginning = beginningIndexes[++lastIsBeginningI];
6329
nextBeginningIndexes[i] = lastIsBeginning === undefined ? targetLen : lastIsBeginning;
6330
}
6331
}
6332
return nextBeginningIndexes;
6333
},
6334
cleanup: cleanup,
6335
new: fuzzysortNew
6336
};
6337
return fuzzysort;
6338
} // fuzzysortNew
6339
6340
// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
6341
var isNode = typeof commonjsRequire !== 'undefined' && typeof window === 'undefined';
6342
var MyMap = typeof Map === 'function' ? Map : function () {
6343
var s = Object.create(null);
6344
this.get = function (k) {
6345
return s[k];
6346
};
6347
this.set = function (k, val) {
6348
s[k] = val;
6349
return this;
6350
};
6351
this.clear = function () {
6352
s = Object.create(null);
6353
};
6354
};
6355
var preparedCache = new MyMap();
6356
var preparedSearchCache = new MyMap();
6357
var noResults = [];
6358
noResults.total = 0;
6359
var matchesSimple = [];
6360
var matchesStrict = [];
6361
function cleanup() {
6362
preparedCache.clear();
6363
preparedSearchCache.clear();
6364
matchesSimple = [];
6365
matchesStrict = [];
6366
}
6367
function defaultScoreFn(a) {
6368
var max = -9007199254740991;
6369
for (var i = a.length - 1; i >= 0; --i) {
6370
var result = a[i];
6371
if (result === null) continue;
6372
var score = result.score;
6373
if (score > max) max = score;
6374
}
6375
if (max === -9007199254740991) return null;
6376
return max;
6377
}
6378
6379
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
6380
// prop = 'key1.key2' 10ms
6381
// prop = ['key1', 'key2'] 27ms
6382
function getValue(obj, prop) {
6383
var tmp = obj[prop];
6384
if (tmp !== undefined) return tmp;
6385
var segs = prop;
6386
if (!Array.isArray(prop)) segs = prop.split('.');
6387
var len = segs.length;
6388
var i = -1;
6389
while (obj && ++i < len) obj = obj[segs[i]];
6390
return obj;
6391
}
6392
function isObj(x) {
6393
return _typeof(x) === 'object';
6394
} // faster as a function
6395
6396
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
6397
var fastpriorityqueue = function fastpriorityqueue() {
6398
var r = [],
6399
o = 0,
6400
e = {};
6401
function n() {
6402
for (var e = 0, n = r[e], c = 1; c < o;) {
6403
var f = c + 1;
6404
e = c, f < o && r[f].score < r[c].score && (e = f), r[e - 1 >> 1] = r[e], c = 1 + (e << 1);
6405
}
6406
for (var a = e - 1 >> 1; e > 0 && n.score < r[a].score; a = (e = a) - 1 >> 1) r[e] = r[a];
6407
r[e] = n;
6408
}
6409
return e.add = function (e) {
6410
var n = o;
6411
r[o++] = e;
6412
for (var c = n - 1 >> 1; n > 0 && e.score < r[c].score; c = (n = c) - 1 >> 1) r[n] = r[c];
6413
r[n] = e;
6414
}, e.poll = function () {
6415
if (0 !== o) {
6416
var e = r[0];
6417
return r[0] = r[--o], n(), e;
6418
}
6419
}, e.peek = function (e) {
6420
if (0 !== o) return r[0];
6421
}, e.replaceTop = function (o) {
6422
r[0] = o, n();
6423
}, e;
6424
};
6425
var q = fastpriorityqueue(); // reuse this, except for async, it needs to make its own
6426
6427
return fuzzysortNew();
6428
}); // UMD
6429
6430
// TODO: (performance) wasm version!?
6431
// TODO: (performance) threads?
6432
// TODO: (performance) avoid cache misses
6433
// TODO: (performance) preparedCache is a memory leak
6434
// TODO: (like sublime) backslash === forwardslash
6435
// TODO: (like sublime) spaces: "a b" should do 2 searches 1 for a and 1 for b
6436
// TODO: (scoring) garbage in targets that allows most searches to strict match need a penality
6437
// TODO: (performance) idk if allowTypo is optimized
6438
})(fuzzysort$1);
6439
var fuzzysort = fuzzysort$1.exports;
6440
6441
var stats = {
6442
failedTests: [],
6443
defined: 0,
6444
completed: 0
6445
};
6446
(function () {
6447
// Don't load the HTML Reporter on non-browser environments
6448
if (!window$1 || !document) {
6449
return;
6450
}
6451
QUnit.reporters.perf.init(QUnit);
6452
var config = QUnit.config;
6453
var hiddenTests = [];
6454
var collapseNext = false;
6455
var hasOwn = Object.prototype.hasOwnProperty;
6456
var unfilteredUrl = setUrl({
6457
filter: undefined,
6458
module: undefined,
6459
moduleId: undefined,
6460
testId: undefined
6461
});
6462
var dropdownData = null;
6463
function trim(string) {
6464
if (typeof string.trim === 'function') {
6465
return string.trim();
6466
} else {
6467
return string.replace(/^\s+|\s+$/g, '');
6468
}
6469
}
6470
function addEvent(elem, type, fn) {
6471
elem.addEventListener(type, fn, false);
6472
}
6473
function removeEvent(elem, type, fn) {
6474
elem.removeEventListener(type, fn, false);
6475
}
6476
function addEvents(elems, type, fn) {
6477
var i = elems.length;
6478
while (i--) {
6479
addEvent(elems[i], type, fn);
6480
}
6481
}
6482
function hasClass(elem, name) {
6483
return (' ' + elem.className + ' ').indexOf(' ' + name + ' ') >= 0;
6484
}
6485
function addClass(elem, name) {
6486
if (!hasClass(elem, name)) {
6487
elem.className += (elem.className ? ' ' : '') + name;
6488
}
6489
}
6490
function toggleClass(elem, name, force) {
6491
if (force || typeof force === 'undefined' && !hasClass(elem, name)) {
6492
addClass(elem, name);
6493
} else {
6494
removeClass(elem, name);
6495
}
6496
}
6497
function removeClass(elem, name) {
6498
var set = ' ' + elem.className + ' ';
6499
6500
// Class name may appear multiple times
6501
while (set.indexOf(' ' + name + ' ') >= 0) {
6502
set = set.replace(' ' + name + ' ', ' ');
6503
}
6504
6505
// Trim for prettiness
6506
elem.className = trim(set);
6507
}
6508
function id(name) {
6509
return document.getElementById && document.getElementById(name);
6510
}
6511
function abortTests() {
6512
var abortButton = id('qunit-abort-tests-button');
6513
if (abortButton) {
6514
abortButton.disabled = true;
6515
abortButton.innerHTML = 'Aborting...';
6516
}
6517
QUnit.config.queue.length = 0;
6518
return false;
6519
}
6520
function interceptNavigation(ev) {
6521
// Trim potential accidental whitespace so that QUnit doesn't throw an error about no tests matching the filter.
6522
var filterInputElem = id('qunit-filter-input');
6523
filterInputElem.value = trim(filterInputElem.value);
6524
applyUrlParams();
6525
if (ev && ev.preventDefault) {
6526
ev.preventDefault();
6527
}
6528
return false;
6529
}
6530
function getUrlConfigHtml() {
6531
var selection = false;
6532
var urlConfig = config.urlConfig;
6533
var urlConfigHtml = '';
6534
for (var i = 0; i < urlConfig.length; i++) {
6535
// Options can be either strings or objects with nonempty "id" properties
6536
var val = config.urlConfig[i];
6537
if (typeof val === 'string') {
6538
val = {
6539
id: val,
6540
label: val
6541
};
6542
}
6543
var escaped = escapeText(val.id);
6544
var escapedTooltip = escapeText(val.tooltip);
6545
if (!val.value || typeof val.value === 'string') {
6546
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>';
6547
} else {
6548
urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + escapeText(val.label) + ": <select id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
6549
if (Array.isArray(val.value)) {
6550
for (var j = 0; j < val.value.length; j++) {
6551
escaped = escapeText(val.value[j]);
6552
urlConfigHtml += "<option value='" + escaped + "'" + (config[val.id] === val.value[j] ? (selection = true) && " selected='selected'" : '') + '>' + escaped + '</option>';
6553
}
6554
} else {
6555
for (var _j in val.value) {
6556
if (hasOwn.call(val.value, _j)) {
6557
urlConfigHtml += "<option value='" + escapeText(_j) + "'" + (config[val.id] === _j ? (selection = true) && " selected='selected'" : '') + '>' + escapeText(val.value[_j]) + '</option>';
6558
}
6559
}
6560
}
6561
if (config[val.id] && !selection) {
6562
escaped = escapeText(config[val.id]);
6563
urlConfigHtml += "<option value='" + escaped + "' selected='selected' disabled='disabled'>" + escaped + '</option>';
6564
}
6565
urlConfigHtml += '</select></label>';
6566
}
6567
}
6568
return urlConfigHtml;
6569
}
6570
6571
// Handle "click" events on toolbar checkboxes and "change" for select menus.
6572
// Updates the URL with the new state of `config.urlConfig` values.
6573
function toolbarChanged() {
6574
var field = this;
6575
var params = {};
6576
6577
// Detect if field is a select menu or a checkbox
6578
var value;
6579
if ('selectedIndex' in field) {
6580
value = field.options[field.selectedIndex].value || undefined;
6581
} else {
6582
value = field.checked ? field.defaultValue || true : undefined;
6583
}
6584
params[field.name] = value;
6585
var updatedUrl = setUrl(params);
6586
6587
// Check if we can apply the change without a page refresh
6588
if (field.name === 'hidepassed' && 'replaceState' in window$1.history) {
6589
QUnit.urlParams[field.name] = value;
6590
config[field.name] = value || false;
6591
var tests = id('qunit-tests');
6592
if (tests) {
6593
var length = tests.children.length;
6594
var children = tests.children;
6595
if (field.checked) {
6596
for (var i = 0; i < length; i++) {
6597
var test = children[i];
6598
var className = test ? test.className : '';
6599
var classNameHasPass = className.indexOf('pass') > -1;
6600
var classNameHasSkipped = className.indexOf('skipped') > -1;
6601
if (classNameHasPass || classNameHasSkipped) {
6602
hiddenTests.push(test);
6603
}
6604
}
6605
var _iterator = _createForOfIteratorHelper(hiddenTests),
6606
_step;
6607
try {
6608
for (_iterator.s(); !(_step = _iterator.n()).done;) {
6609
var hiddenTest = _step.value;
6610
tests.removeChild(hiddenTest);
6611
}
6612
} catch (err) {
6613
_iterator.e(err);
6614
} finally {
6615
_iterator.f();
6616
}
6617
} else {
6618
while (hiddenTests.length) {
6619
tests.appendChild(hiddenTests.shift());
6620
}
6621
}
6622
}
6623
window$1.history.replaceState(null, '', updatedUrl);
6624
} else {
6625
window$1.location = updatedUrl;
6626
}
6627
}
6628
function setUrl(params) {
6629
var querystring = '?';
6630
var location = window$1.location;
6631
params = extend(extend({}, QUnit.urlParams), params);
6632
for (var key in params) {
6633
// Skip inherited or undefined properties
6634
if (hasOwn.call(params, key) && params[key] !== undefined) {
6635
// Output a parameter for each value of this key
6636
// (but usually just one)
6637
var arrValue = [].concat(params[key]);
6638
for (var i = 0; i < arrValue.length; i++) {
6639
querystring += encodeURIComponent(key);
6640
if (arrValue[i] !== true) {
6641
querystring += '=' + encodeURIComponent(arrValue[i]);
6642
}
6643
querystring += '&';
6644
}
6645
}
6646
}
6647
return location.protocol + '//' + location.host + location.pathname + querystring.slice(0, -1);
6648
}
6649
function applyUrlParams() {
6650
var filter = id('qunit-filter-input').value;
6651
window$1.location = setUrl({
6652
filter: filter === '' ? undefined : filter,
6653
moduleId: _toConsumableArray(dropdownData.selectedMap.keys()),
6654
// Remove module and testId filter
6655
module: undefined,
6656
testId: undefined
6657
});
6658
}
6659
function toolbarUrlConfigContainer() {
6660
var urlConfigContainer = document.createElement('span');
6661
urlConfigContainer.innerHTML = getUrlConfigHtml();
6662
addClass(urlConfigContainer, 'qunit-url-config');
6663
addEvents(urlConfigContainer.getElementsByTagName('input'), 'change', toolbarChanged);
6664
addEvents(urlConfigContainer.getElementsByTagName('select'), 'change', toolbarChanged);
6665
return urlConfigContainer;
6666
}
6667
function abortTestsButton() {
6668
var button = document.createElement('button');
6669
button.id = 'qunit-abort-tests-button';
6670
button.innerHTML = 'Abort';
6671
addEvent(button, 'click', abortTests);
6672
return button;
6673
}
6674
function toolbarLooseFilter() {
6675
var filter = document.createElement('form');
6676
var label = document.createElement('label');
6677
var input = document.createElement('input');
6678
var button = document.createElement('button');
6679
addClass(filter, 'qunit-filter');
6680
label.innerHTML = 'Filter: ';
6681
input.type = 'text';
6682
input.value = config.filter || '';
6683
input.name = 'filter';
6684
input.id = 'qunit-filter-input';
6685
button.innerHTML = 'Go';
6686
label.appendChild(input);
6687
filter.appendChild(label);
6688
filter.appendChild(document.createTextNode(' '));
6689
filter.appendChild(button);
6690
addEvent(filter, 'submit', interceptNavigation);
6691
return filter;
6692
}
6693
function createModuleListItem(moduleId, name, checked) {
6694
return '<li><label class="clickable' + (checked ? ' checked' : '') + '"><input type="checkbox" ' + 'value="' + escapeText(moduleId) + '"' + (checked ? ' checked="checked"' : '') + ' />' + escapeText(name) + '</label></li>';
6695
}
6696
6697
/**
6698
* @param {Array} Results from fuzzysort
6699
* @return {string} HTML
6700
*/
6701
function moduleListHtml(results) {
6702
var html = '';
6703
6704
// Hoist the already selected items, and show them always
6705
// even if not matched by the current search.
6706
dropdownData.selectedMap.forEach(function (name, moduleId) {
6707
html += createModuleListItem(moduleId, name, true);
6708
});
6709
for (var i = 0; i < results.length; i++) {
6710
var mod = results[i].obj;
6711
if (!dropdownData.selectedMap.has(mod.moduleId)) {
6712
html += createModuleListItem(mod.moduleId, mod.name, false);
6713
}
6714
}
6715
return html;
6716
}
6717
function toolbarModuleFilter(beginDetails) {
6718
var initialSelected = null;
6719
dropdownData = {
6720
options: beginDetails.modules.slice(),
6721
selectedMap: new StringMap(),
6722
isDirty: function isDirty() {
6723
return _toConsumableArray(dropdownData.selectedMap.keys()).sort().join(',') !== _toConsumableArray(initialSelected.keys()).sort().join(',');
6724
}
6725
};
6726
if (config.moduleId.length) {
6727
// The module dropdown is seeded with the runtime configuration of the last run.
6728
//
6729
// We don't reference `config.moduleId` directly after this and keep our own
6730
// copy because:
6731
// 1. This naturally filters out unknown moduleIds.
6732
// 2. Gives us a place to manage and remember unsubmitted checkbox changes.
6733
// 3. Gives us an efficient way to map a selected moduleId to module name
6734
// during rendering.
6735
for (var i = 0; i < beginDetails.modules.length; i++) {
6736
var mod = beginDetails.modules[i];
6737
if (config.moduleId.indexOf(mod.moduleId) !== -1) {
6738
dropdownData.selectedMap.set(mod.moduleId, mod.name);
6739
}
6740
}
6741
}
6742
initialSelected = new StringMap(dropdownData.selectedMap);
6743
var moduleSearch = document.createElement('input');
6744
moduleSearch.id = 'qunit-modulefilter-search';
6745
moduleSearch.autocomplete = 'off';
6746
addEvent(moduleSearch, 'input', searchInput);
6747
addEvent(moduleSearch, 'input', searchFocus);
6748
addEvent(moduleSearch, 'focus', searchFocus);
6749
addEvent(moduleSearch, 'click', searchFocus);
6750
var label = document.createElement('label');
6751
label.htmlFor = 'qunit-modulefilter-search';
6752
label.textContent = 'Module:';
6753
var searchContainer = document.createElement('span');
6754
searchContainer.id = 'qunit-modulefilter-search-container';
6755
searchContainer.appendChild(moduleSearch);
6756
var applyButton = document.createElement('button');
6757
applyButton.textContent = 'Apply';
6758
applyButton.title = 'Re-run the selected test modules';
6759
addEvent(applyButton, 'click', applyUrlParams);
6760
var resetButton = document.createElement('button');
6761
resetButton.textContent = 'Reset';
6762
resetButton.type = 'reset';
6763
resetButton.title = 'Restore the previous module selection';
6764
var clearButton = document.createElement('button');
6765
clearButton.textContent = 'Select none';
6766
clearButton.type = 'button';
6767
clearButton.title = 'Clear the current module selection';
6768
addEvent(clearButton, 'click', function () {
6769
dropdownData.selectedMap.clear();
6770
selectionChange();
6771
searchInput();
6772
});
6773
var actions = document.createElement('span');
6774
actions.id = 'qunit-modulefilter-actions';
6775
actions.appendChild(applyButton);
6776
actions.appendChild(resetButton);
6777
if (initialSelected.size) {
6778
// Only show clear button if functionally different from reset
6779
actions.appendChild(clearButton);
6780
}
6781
var dropDownList = document.createElement('ul');
6782
dropDownList.id = 'qunit-modulefilter-dropdown-list';
6783
var dropDown = document.createElement('div');
6784
dropDown.id = 'qunit-modulefilter-dropdown';
6785
dropDown.style.display = 'none';
6786
dropDown.appendChild(actions);
6787
dropDown.appendChild(dropDownList);
6788
addEvent(dropDown, 'change', selectionChange);
6789
searchContainer.appendChild(dropDown);
6790
// Set initial moduleSearch.placeholder and clearButton/resetButton.
6791
selectionChange();
6792
var moduleFilter = document.createElement('form');
6793
moduleFilter.id = 'qunit-modulefilter';
6794
moduleFilter.appendChild(label);
6795
moduleFilter.appendChild(document.createTextNode(' '));
6796
moduleFilter.appendChild(searchContainer);
6797
addEvent(moduleFilter, 'submit', interceptNavigation);
6798
addEvent(moduleFilter, 'reset', function () {
6799
dropdownData.selectedMap = new StringMap(initialSelected);
6800
// Set moduleSearch.placeholder and reflect non-dirty state
6801
selectionChange();
6802
searchInput();
6803
});
6804
6805
// Enables show/hide for the dropdown
6806
function searchFocus() {
6807
if (dropDown.style.display !== 'none') {
6808
return;
6809
}
6810
6811
// Optimization: Defer rendering options until focussed.
6812
// https://github.com/qunitjs/qunit/issues/1664
6813
searchInput();
6814
dropDown.style.display = 'block';
6815
6816
// Hide on Escape keydown or on click outside the container
6817
addEvent(document, 'click', hideHandler);
6818
addEvent(document, 'keydown', hideHandler);
6819
function hideHandler(e) {
6820
var inContainer = moduleFilter.contains(e.target);
6821
if (e.keyCode === 27 || !inContainer) {
6822
if (e.keyCode === 27 && inContainer) {
6823
moduleSearch.focus();
6824
}
6825
dropDown.style.display = 'none';
6826
removeEvent(document, 'click', hideHandler);
6827
removeEvent(document, 'keydown', hideHandler);
6828
moduleSearch.value = '';
6829
searchInput();
6830
}
6831
}
6832
}
6833
6834
/**
6835
* @param {string} searchText
6836
* @return {string} HTML
6837
*/
6838
function filterModules(searchText) {
6839
var results;
6840
if (searchText === '') {
6841
// Improve on-boarding experience by having an immediate display of
6842
// module names, indicating how the interface works. This also makes
6843
// for a quicker interaction in the common case of small projects.
6844
// Don't mandate typing just to get the menu.
6845
results = dropdownData.options.slice(0, 20).map(function (obj) {
6846
// Fake empty results. https://github.com/farzher/fuzzysort/issues/41
6847
return {
6848
obj: obj
6849
};
6850
});
6851
} else {
6852
results = fuzzysort.go(searchText, dropdownData.options, {
6853
limit: 20,
6854
key: 'name',
6855
allowTypo: true
6856
});
6857
}
6858
return moduleListHtml(results);
6859
}
6860
6861
// Processes module search box input
6862
var searchInputTimeout;
6863
function searchInput() {
6864
// Use a debounce with a ~0ms timeout. This is effectively instantaneous,
6865
// but is better than undebounced because it avoids an ever-growing
6866
// backlog of unprocessed now-outdated input events if fuzzysearch or
6867
// drodown DOM is slow (e.g. very large test suite).
6868
window$1.clearTimeout(searchInputTimeout);
6869
searchInputTimeout = window$1.setTimeout(function () {
6870
dropDownList.innerHTML = filterModules(moduleSearch.value);
6871
});
6872
}
6873
6874
// Processes checkbox change, or a generic render (initial render, or after reset event)
6875
// Avoid any dropdown rendering here as this is used by toolbarModuleFilter()
6876
// during the initial render, which should not delay test execution.
6877
function selectionChange(evt) {
6878
var checkbox = evt && evt.target || null;
6879
if (checkbox) {
6880
// Update internal state
6881
if (checkbox.checked) {
6882
dropdownData.selectedMap.set(checkbox.value, checkbox.parentNode.textContent);
6883
} else {
6884
dropdownData.selectedMap.delete(checkbox.value);
6885
}
6886
6887
// Update UI state
6888
toggleClass(checkbox.parentNode, 'checked', checkbox.checked);
6889
}
6890
var textForm = dropdownData.selectedMap.size ? dropdownData.selectedMap.size + ' ' + (dropdownData.selectedMap.size === 1 ? 'module' : 'modules') : 'All modules';
6891
moduleSearch.placeholder = textForm;
6892
moduleSearch.title = 'Type to search through and reduce the list.';
6893
resetButton.disabled = !dropdownData.isDirty();
6894
clearButton.style.display = dropdownData.selectedMap.size ? '' : 'none';
6895
}
6896
return moduleFilter;
6897
}
6898
function appendToolbar(beginDetails) {
6899
var toolbar = id('qunit-testrunner-toolbar');
6900
if (toolbar) {
6901
toolbar.appendChild(toolbarUrlConfigContainer());
6902
var toolbarFilters = document.createElement('span');
6903
toolbarFilters.id = 'qunit-toolbar-filters';
6904
toolbarFilters.appendChild(toolbarLooseFilter());
6905
toolbarFilters.appendChild(toolbarModuleFilter(beginDetails));
6906
var clearfix = document.createElement('div');
6907
clearfix.className = 'clearfix';
6908
toolbar.appendChild(toolbarFilters);
6909
toolbar.appendChild(clearfix);
6910
}
6911
}
6912
function appendHeader() {
6913
var header = id('qunit-header');
6914
if (header) {
6915
header.innerHTML = "<a href='" + escapeText(unfilteredUrl) + "'>" + header.innerHTML + '</a> ';
6916
}
6917
}
6918
function appendBanner() {
6919
var banner = id('qunit-banner');
6920
if (banner) {
6921
banner.className = '';
6922
}
6923
}
6924
function appendTestResults() {
6925
var tests = id('qunit-tests');
6926
var result = id('qunit-testresult');
6927
var controls;
6928
if (result) {
6929
result.parentNode.removeChild(result);
6930
}
6931
if (tests) {
6932
tests.innerHTML = '';
6933
result = document.createElement('p');
6934
result.id = 'qunit-testresult';
6935
result.className = 'result';
6936
tests.parentNode.insertBefore(result, tests);
6937
result.innerHTML = '<div id="qunit-testresult-display">Running...<br />&#160;</div>' + '<div id="qunit-testresult-controls"></div>' + '<div class="clearfix"></div>';
6938
controls = id('qunit-testresult-controls');
6939
}
6940
if (controls) {
6941
controls.appendChild(abortTestsButton());
6942
}
6943
}
6944
function appendFilteredTest() {
6945
var testId = QUnit.config.testId;
6946
if (!testId || testId.length <= 0) {
6947
return '';
6948
}
6949
return "<div id='qunit-filteredTest'>Rerunning selected tests: " + escapeText(testId.join(', ')) + " <a id='qunit-clearFilter' href='" + escapeText(unfilteredUrl) + "'>Run all tests</a></div>";
6950
}
6951
function appendUserAgent() {
6952
var userAgent = id('qunit-userAgent');
6953
if (userAgent) {
6954
userAgent.innerHTML = '';
6955
userAgent.appendChild(document.createTextNode('QUnit ' + QUnit.version + '; ' + navigator.userAgent));
6956
}
6957
}
6958
function appendInterface(beginDetails) {
6959
var qunit = id('qunit');
6960
6961
// For compat with QUnit 1.2, and to support fully custom theme HTML,
6962
// we will use any existing elements if no id="qunit" element exists.
6963
//
6964
// Note that we don't fail or fallback to creating it ourselves,
6965
// because not having id="qunit" (and not having the below elements)
6966
// simply means QUnit acts headless, allowing users to use their own
6967
// reporters, or for a test runner to listen for events directly without
6968
// having the HTML reporter actively render anything.
6969
if (qunit) {
6970
qunit.setAttribute('role', 'main');
6971
6972
// Since QUnit 1.3, these are created automatically if the page
6973
// contains id="qunit".
6974
qunit.innerHTML = "<h1 id='qunit-header'>" + escapeText(document.title) + '</h1>' + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar' role='navigation'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
6975
}
6976
appendHeader();
6977
appendBanner();
6978
appendTestResults();
6979
appendUserAgent();
6980
appendToolbar(beginDetails);
6981
}
6982
function appendTest(name, testId, moduleName) {
6983
var tests = id('qunit-tests');
6984
if (!tests) {
6985
return;
6986
}
6987
var title = document.createElement('strong');
6988
title.innerHTML = getNameHtml(name, moduleName);
6989
var testBlock = document.createElement('li');
6990
testBlock.appendChild(title);
6991
6992
// No ID or rerun link for "global failure" blocks
6993
if (testId !== undefined) {
6994
var rerunTrigger = document.createElement('a');
6995
rerunTrigger.innerHTML = 'Rerun';
6996
rerunTrigger.href = setUrl({
6997
testId: testId
6998
});
6999
testBlock.id = 'qunit-test-output-' + testId;
7000
testBlock.appendChild(rerunTrigger);
7001
}
7002
var assertList = document.createElement('ol');
7003
assertList.className = 'qunit-assert-list';
7004
testBlock.appendChild(assertList);
7005
tests.appendChild(testBlock);
7006
return testBlock;
7007
}
7008
7009
// HTML Reporter initialization and load
7010
QUnit.on('runStart', function (runStart) {
7011
stats.defined = runStart.testCounts.total;
7012
});
7013
QUnit.begin(function (beginDetails) {
7014
// Initialize QUnit elements
7015
// This is done from begin() instead of runStart, because
7016
// urlparams.js uses begin(), which we need to wait for.
7017
// urlparams.js in turn uses begin() to allow plugins to
7018
// add entries to QUnit.config.urlConfig, which may be done
7019
// asynchronously.
7020
// <https://github.com/qunitjs/qunit/issues/1657>
7021
appendInterface(beginDetails);
7022
});
7023
function getRerunFailedHtml(failedTests) {
7024
if (failedTests.length === 0) {
7025
return '';
7026
}
7027
var href = setUrl({
7028
testId: failedTests
7029
});
7030
return ["<br /><a href='" + escapeText(href) + "'>", failedTests.length === 1 ? 'Rerun 1 failed test' : 'Rerun ' + failedTests.length + ' failed tests', '</a>'].join('');
7031
}
7032
QUnit.on('runEnd', function (runEnd) {
7033
var banner = id('qunit-banner');
7034
var tests = id('qunit-tests');
7035
var abortButton = id('qunit-abort-tests-button');
7036
var assertPassed = config.stats.all - config.stats.bad;
7037
var html = [runEnd.testCounts.total, ' tests completed in ', runEnd.runtime, ' milliseconds, with ', runEnd.testCounts.failed, ' failed, ', runEnd.testCounts.skipped, ' skipped, and ', runEnd.testCounts.todo, ' todo.<br />', "<span class='passed'>", assertPassed, "</span> assertions of <span class='total'>", config.stats.all, "</span> passed, <span class='failed'>", config.stats.bad, '</span> failed.', getRerunFailedHtml(stats.failedTests)].join('');
7038
var test;
7039
var assertLi;
7040
var assertList;
7041
7042
// Update remaining tests to aborted
7043
if (abortButton && abortButton.disabled) {
7044
html = 'Tests aborted after ' + runEnd.runtime + ' milliseconds.';
7045
for (var i = 0; i < tests.children.length; i++) {
7046
test = tests.children[i];
7047
if (test.className === '' || test.className === 'running') {
7048
test.className = 'aborted';
7049
assertList = test.getElementsByTagName('ol')[0];
7050
assertLi = document.createElement('li');
7051
assertLi.className = 'fail';
7052
assertLi.innerHTML = 'Test aborted.';
7053
assertList.appendChild(assertLi);
7054
}
7055
}
7056
}
7057
if (banner && (!abortButton || abortButton.disabled === false)) {
7058
banner.className = runEnd.status === 'failed' ? 'qunit-fail' : 'qunit-pass';
7059
}
7060
if (abortButton) {
7061
abortButton.parentNode.removeChild(abortButton);
7062
}
7063
if (tests) {
7064
id('qunit-testresult-display').innerHTML = html;
7065
}
7066
if (config.altertitle && document.title) {
7067
// Show ✖ for good, ✔ for bad suite result in title
7068
// use escape sequences in case file gets loaded with non-utf-8
7069
// charset
7070
document.title = [runEnd.status === 'failed' ? "\u2716" : "\u2714", document.title.replace(/^[\u2714\u2716] /i, '')].join(' ');
7071
}
7072
7073
// Scroll back to top to show results
7074
if (config.scrolltop && window$1.scrollTo) {
7075
window$1.scrollTo(0, 0);
7076
}
7077
});
7078
function getNameHtml(name, module) {
7079
var nameHtml = '';
7080
if (module) {
7081
nameHtml = "<span class='module-name'>" + escapeText(module) + '</span>: ';
7082
}
7083
nameHtml += "<span class='test-name'>" + escapeText(name) + '</span>';
7084
return nameHtml;
7085
}
7086
function getProgressHtml(stats) {
7087
return [stats.completed, ' / ', stats.defined, ' tests completed.<br />'].join('');
7088
}
7089
QUnit.testStart(function (details) {
7090
var running, bad;
7091
appendTest(details.name, details.testId, details.module);
7092
running = id('qunit-testresult-display');
7093
if (running) {
7094
addClass(running, 'running');
7095
bad = QUnit.config.reorder && details.previousFailure;
7096
running.innerHTML = [getProgressHtml(stats), bad ? 'Rerunning previously failed test: <br />' : 'Running: ', getNameHtml(details.name, details.module), getRerunFailedHtml(stats.failedTests)].join('');
7097
}
7098
});
7099
function stripHtml(string) {
7100
// Strip tags, html entity and whitespaces
7101
return string.replace(/<\/?[^>]+(>|$)/g, '').replace(/&quot;/g, '').replace(/\s+/g, '');
7102
}
7103
QUnit.log(function (details) {
7104
var testItem = id('qunit-test-output-' + details.testId);
7105
if (!testItem) {
7106
return;
7107
}
7108
var message = escapeText(details.message) || (details.result ? 'okay' : 'failed');
7109
message = "<span class='test-message'>" + message + '</span>';
7110
message += "<span class='runtime'>@ " + details.runtime + ' ms</span>';
7111
var expected;
7112
var actual;
7113
var diff;
7114
var showDiff = false;
7115
7116
// When pushFailure() is called, it is implied that no expected value
7117
// or diff should be shown, because both expected and actual as undefined.
7118
//
7119
// This must check details.expected existence. If it exists as undefined,
7120
// that's a regular assertion for which to render actual/expected and a diff.
7121
var showAnyValues = !details.result && (details.expected !== undefined || details.actual !== undefined);
7122
if (showAnyValues) {
7123
if (details.negative) {
7124
expected = 'NOT ' + QUnit.dump.parse(details.expected);
7125
} else {
7126
expected = QUnit.dump.parse(details.expected);
7127
}
7128
actual = QUnit.dump.parse(details.actual);
7129
message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + escapeText(expected) + '</pre></td></tr>';
7130
if (actual !== expected) {
7131
message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + '</pre></td></tr>';
7132
if (typeof details.actual === 'number' && typeof details.expected === 'number') {
7133
if (!isNaN(details.actual) && !isNaN(details.expected)) {
7134
showDiff = true;
7135
diff = details.actual - details.expected;
7136
diff = (diff > 0 ? '+' : '') + diff;
7137
}
7138
} else if (typeof details.actual !== 'boolean' && typeof details.expected !== 'boolean') {
7139
diff = QUnit.diff(expected, actual);
7140
7141
// don't show diff if there is zero overlap
7142
showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
7143
}
7144
if (showDiff) {
7145
message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + '</pre></td></tr>';
7146
}
7147
} else if (expected.indexOf('[object Array]') !== -1 || expected.indexOf('[object Object]') !== -1) {
7148
message += "<tr class='test-message'><th>Message: </th><td>" + 'Diff suppressed as the depth of object is more than current max depth (' + QUnit.dump.maxDepth + ').<p>Hint: Use <code>QUnit.dump.maxDepth</code> to ' + " run with a higher max depth or <a href='" + escapeText(setUrl({
7149
maxDepth: 0
7150
})) + "'>" + 'Rerun without max depth</a>.</p></td></tr>';
7151
} else {
7152
message += "<tr class='test-message'><th>Message: </th><td>" + 'Diff suppressed as the expected and actual results have an equivalent' + ' serialization</td></tr>';
7153
}
7154
if (details.source) {
7155
message += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + '</pre></td></tr>';
7156
}
7157
message += '</table>';
7158
7159
// This occurs when pushFailure is set and we have an extracted stack trace
7160
} else if (!details.result && details.source) {
7161
message += '<table>' + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(details.source) + '</pre></td></tr>' + '</table>';
7162
}
7163
var assertList = testItem.getElementsByTagName('ol')[0];
7164
var assertLi = document.createElement('li');
7165
assertLi.className = details.result ? 'pass' : 'fail';
7166
assertLi.innerHTML = message;
7167
assertList.appendChild(assertLi);
7168
});
7169
QUnit.testDone(function (details) {
7170
var tests = id('qunit-tests');
7171
var testItem = id('qunit-test-output-' + details.testId);
7172
if (!tests || !testItem) {
7173
return;
7174
}
7175
removeClass(testItem, 'running');
7176
var status;
7177
if (details.failed > 0) {
7178
status = 'failed';
7179
} else if (details.todo) {
7180
status = 'todo';
7181
} else {
7182
status = details.skipped ? 'skipped' : 'passed';
7183
}
7184
var assertList = testItem.getElementsByTagName('ol')[0];
7185
var good = details.passed;
7186
var bad = details.failed;
7187
7188
// This test passed if it has no unexpected failed assertions
7189
var testPassed = details.failed > 0 ? details.todo : !details.todo;
7190
if (testPassed) {
7191
// Collapse the passing tests
7192
addClass(assertList, 'qunit-collapsed');
7193
} else {
7194
stats.failedTests.push(details.testId);
7195
if (config.collapse) {
7196
if (!collapseNext) {
7197
// Skip collapsing the first failing test
7198
collapseNext = true;
7199
} else {
7200
// Collapse remaining tests
7201
addClass(assertList, 'qunit-collapsed');
7202
}
7203
}
7204
}
7205
7206
// The testItem.firstChild is the test name
7207
var testTitle = testItem.firstChild;
7208
var testCounts = bad ? "<b class='failed'>" + bad + '</b>, ' + "<b class='passed'>" + good + '</b>, ' : '';
7209
testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ')</b>';
7210
stats.completed++;
7211
if (details.skipped) {
7212
testItem.className = 'skipped';
7213
var skipped = document.createElement('em');
7214
skipped.className = 'qunit-skipped-label';
7215
skipped.innerHTML = 'skipped';
7216
testItem.insertBefore(skipped, testTitle);
7217
} else {
7218
addEvent(testTitle, 'click', function () {
7219
toggleClass(assertList, 'qunit-collapsed');
7220
});
7221
testItem.className = testPassed ? 'pass' : 'fail';
7222
if (details.todo) {
7223
var todoLabel = document.createElement('em');
7224
todoLabel.className = 'qunit-todo-label';
7225
todoLabel.innerHTML = 'todo';
7226
testItem.className += ' todo';
7227
testItem.insertBefore(todoLabel, testTitle);
7228
}
7229
var time = document.createElement('span');
7230
time.className = 'runtime';
7231
time.innerHTML = details.runtime + ' ms';
7232
testItem.insertBefore(time, assertList);
7233
}
7234
7235
// Show the source of the test when showing assertions
7236
if (details.source) {
7237
var sourceName = document.createElement('p');
7238
sourceName.innerHTML = '<strong>Source: </strong>' + escapeText(details.source);
7239
addClass(sourceName, 'qunit-source');
7240
if (testPassed) {
7241
addClass(sourceName, 'qunit-collapsed');
7242
}
7243
addEvent(testTitle, 'click', function () {
7244
toggleClass(sourceName, 'qunit-collapsed');
7245
});
7246
testItem.appendChild(sourceName);
7247
}
7248
if (config.hidepassed && (status === 'passed' || details.skipped)) {
7249
// use removeChild instead of remove because of support
7250
hiddenTests.push(testItem);
7251
tests.removeChild(testItem);
7252
}
7253
});
7254
QUnit.on('error', function (error) {
7255
var testItem = appendTest('global failure');
7256
if (!testItem) {
7257
// HTML Reporter is probably disabled or not yet initialized.
7258
return;
7259
}
7260
7261
// Render similar to a failed assertion (see above QUnit.log callback)
7262
var message = escapeText(errorString(error));
7263
message = "<span class='test-message'>" + message + '</span>';
7264
if (error && error.stack) {
7265
message += '<table>' + "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText(error.stack) + '</pre></td></tr>' + '</table>';
7266
}
7267
var assertList = testItem.getElementsByTagName('ol')[0];
7268
var assertLi = document.createElement('li');
7269
assertLi.className = 'fail';
7270
assertLi.innerHTML = message;
7271
assertList.appendChild(assertLi);
7272
7273
// Make it visible
7274
testItem.className = 'fail';
7275
});
7276
7277
// Avoid readyState issue with phantomjs
7278
// Ref: #818
7279
var usingPhantom = function (p) {
7280
return p && p.version && p.version.major > 0;
7281
}(window$1.phantom);
7282
if (usingPhantom) {
7283
console$1.warn('Support for PhantomJS is deprecated and will be removed in QUnit 3.0.');
7284
}
7285
if (!usingPhantom && document.readyState === 'complete') {
7286
QUnit.autostart();
7287
} else {
7288
addEvent(window$1, 'load', QUnit.autostart);
7289
}
7290
7291
// Wrap window.onerror. We will call the original window.onerror to see if
7292
// the existing handler fully handles the error; if not, we will call the
7293
// QUnit.onError function.
7294
var originalWindowOnError = window$1.onerror;
7295
7296
// Cover uncaught exceptions
7297
// Returning true will suppress the default browser handler,
7298
// returning false will let it run.
7299
window$1.onerror = function (message, fileName, lineNumber, columnNumber, errorObj) {
7300
var ret = false;
7301
if (originalWindowOnError) {
7302
for (var _len = arguments.length, args = new Array(_len > 5 ? _len - 5 : 0), _key = 5; _key < _len; _key++) {
7303
args[_key - 5] = arguments[_key];
7304
}
7305
ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber, columnNumber, errorObj].concat(args));
7306
}
7307
7308
// Treat return value as window.onerror itself does,
7309
// Only do our handling if not suppressed.
7310
if (ret !== true) {
7311
// If there is a current test that sets the internal `ignoreGlobalErrors` field
7312
// (such as during `assert.throws()`), then the error is ignored and native
7313
// error reporting is suppressed as well. This is because in browsers, an error
7314
// can sometimes end up in `window.onerror` instead of in the local try/catch.
7315
// This ignoring of errors does not apply to our general onUncaughtException
7316
// method, nor to our `unhandledRejection` handlers, as those are not meant
7317
// to receive an "expected" error during `assert.throws()`.
7318
if (config.current && config.current.ignoreGlobalErrors) {
7319
return true;
7320
}
7321
7322
// According to
7323
// https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror,
7324
// most modern browsers support an errorObj argument; use that to
7325
// get a full stack trace if it's available.
7326
var error = errorObj || new Error(message);
7327
if (!error.stack && fileName && lineNumber) {
7328
error.stack = "".concat(fileName, ":").concat(lineNumber);
7329
}
7330
QUnit.onUncaughtException(error);
7331
}
7332
return ret;
7333
};
7334
window$1.addEventListener('unhandledrejection', function (event) {
7335
QUnit.onUncaughtException(event.reason);
7336
});
7337
})();
7338
7339
})();
7340
7341