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