Merge "Gallery: Use intrinsic width for gallery to center caption"
[lhc/web/wiklou.git] / resources / lib / qunitjs / qunit.js
1 /*!
2 * QUnit 1.22.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: 2016-02-23T15:57Z
10 */
11
12 (function( global ) {
13
14 var QUnit = {};
15
16 var Date = global.Date;
17 var now = Date.now || function() {
18 return new Date().getTime();
19 };
20
21 var setTimeout = global.setTimeout;
22 var clearTimeout = global.clearTimeout;
23
24 // Store a local window from the global to allow direct references.
25 var window = global.window;
26
27 var defined = {
28 document: window && window.document !== undefined,
29 setTimeout: setTimeout !== undefined,
30 sessionStorage: (function() {
31 var x = "qunit-test-string";
32 try {
33 sessionStorage.setItem( x, x );
34 sessionStorage.removeItem( x );
35 return true;
36 } catch ( e ) {
37 return false;
38 }
39 }() )
40 };
41
42 var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
43 var globalStartCalled = false;
44 var runStarted = false;
45
46 var toString = Object.prototype.toString,
47 hasOwn = Object.prototype.hasOwnProperty;
48
49 // returns a new Array with the elements that are in a but not in b
50 function diff( a, b ) {
51 var i, j,
52 result = a.slice();
53
54 for ( i = 0; i < result.length; i++ ) {
55 for ( j = 0; j < b.length; j++ ) {
56 if ( result[ i ] === b[ j ] ) {
57 result.splice( i, 1 );
58 i--;
59 break;
60 }
61 }
62 }
63 return result;
64 }
65
66 // from jquery.js
67 function inArray( elem, array ) {
68 if ( array.indexOf ) {
69 return array.indexOf( elem );
70 }
71
72 for ( var i = 0, length = array.length; i < length; i++ ) {
73 if ( array[ i ] === elem ) {
74 return i;
75 }
76 }
77
78 return -1;
79 }
80
81 /**
82 * Makes a clone of an object using only Array or Object as base,
83 * and copies over the own enumerable properties.
84 *
85 * @param {Object} obj
86 * @return {Object} New object with only the own properties (recursively).
87 */
88 function objectValues ( obj ) {
89 var key, val,
90 vals = QUnit.is( "array", obj ) ? [] : {};
91 for ( key in obj ) {
92 if ( hasOwn.call( obj, key ) ) {
93 val = obj[ key ];
94 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
95 }
96 }
97 return vals;
98 }
99
100 function extend( a, b, undefOnly ) {
101 for ( var prop in b ) {
102 if ( hasOwn.call( b, prop ) ) {
103
104 // Avoid "Member not found" error in IE8 caused by messing with window.constructor
105 // This block runs on every environment, so `global` is being used instead of `window`
106 // to avoid errors on node.
107 if ( prop !== "constructor" || a !== global ) {
108 if ( b[ prop ] === undefined ) {
109 delete a[ prop ];
110 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
111 a[ prop ] = b[ prop ];
112 }
113 }
114 }
115 }
116
117 return a;
118 }
119
120 function objectType( obj ) {
121 if ( typeof obj === "undefined" ) {
122 return "undefined";
123 }
124
125 // Consider: typeof null === object
126 if ( obj === null ) {
127 return "null";
128 }
129
130 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
131 type = match && match[ 1 ];
132
133 switch ( type ) {
134 case "Number":
135 if ( isNaN( obj ) ) {
136 return "nan";
137 }
138 return "number";
139 case "String":
140 case "Boolean":
141 case "Array":
142 case "Set":
143 case "Map":
144 case "Date":
145 case "RegExp":
146 case "Function":
147 case "Symbol":
148 return type.toLowerCase();
149 }
150 if ( typeof obj === "object" ) {
151 return "object";
152 }
153 }
154
155 // Safe object type checking
156 function is( type, obj ) {
157 return QUnit.objectType( obj ) === type;
158 }
159
160 var getUrlParams = function() {
161 var i, param, name, value;
162 var urlParams = {};
163 var location = window.location;
164 var params = location.search.slice( 1 ).split( "&" );
165 var length = params.length;
166
167 for ( i = 0; i < length; i++ ) {
168 if ( params[ i ] ) {
169 param = params[ i ].split( "=" );
170 name = decodeURIComponent( param[ 0 ] );
171
172 // allow just a key to turn on a flag, e.g., test.html?noglobals
173 value = param.length === 1 ||
174 decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
175 if ( urlParams[ name ] ) {
176 urlParams[ name ] = [].concat( urlParams[ name ], value );
177 } else {
178 urlParams[ name ] = value;
179 }
180 }
181 }
182
183 return urlParams;
184 };
185
186 // Doesn't support IE6 to IE9, it will return undefined on these browsers
187 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
188 function extractStacktrace( e, offset ) {
189 offset = offset === undefined ? 4 : offset;
190
191 var stack, include, i;
192
193 if ( e.stack ) {
194 stack = e.stack.split( "\n" );
195 if ( /^error$/i.test( stack[ 0 ] ) ) {
196 stack.shift();
197 }
198 if ( fileName ) {
199 include = [];
200 for ( i = offset; i < stack.length; i++ ) {
201 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
202 break;
203 }
204 include.push( stack[ i ] );
205 }
206 if ( include.length ) {
207 return include.join( "\n" );
208 }
209 }
210 return stack[ offset ];
211
212 // Support: Safari <=6 only
213 } else if ( e.sourceURL ) {
214
215 // exclude useless self-reference for generated Error objects
216 if ( /qunit.js$/.test( e.sourceURL ) ) {
217 return;
218 }
219
220 // for actual exceptions, this is useful
221 return e.sourceURL + ":" + e.line;
222 }
223 }
224
225 function sourceFromStacktrace( offset ) {
226 var error = new Error();
227
228 // Support: Safari <=7 only, IE <=10 - 11 only
229 // Not all browsers generate the `stack` property for `new Error()`, see also #636
230 if ( !error.stack ) {
231 try {
232 throw error;
233 } catch ( err ) {
234 error = err;
235 }
236 }
237
238 return extractStacktrace( error, offset );
239 }
240
241 /**
242 * Config object: Maintain internal state
243 * Later exposed as QUnit.config
244 * `config` initialized at top of scope
245 */
246 var config = {
247 // The queue of tests to run
248 queue: [],
249
250 // block until document ready
251 blocking: true,
252
253 // by default, run previously failed tests first
254 // very useful in combination with "Hide passed tests" checked
255 reorder: true,
256
257 // by default, modify document.title when suite is done
258 altertitle: true,
259
260 // HTML Reporter: collapse every test except the first failing test
261 // If false, all failing tests will be expanded
262 collapse: true,
263
264 // by default, scroll to top of the page when suite is done
265 scrolltop: true,
266
267 // depth up-to which object will be dumped
268 maxDepth: 5,
269
270 // when enabled, all tests must call expect()
271 requireExpects: false,
272
273 // add checkboxes that are persisted in the query-string
274 // when enabled, the id is set to `true` as a `QUnit.config` property
275 urlConfig: [
276 {
277 id: "hidepassed",
278 label: "Hide passed tests",
279 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
280 },
281 {
282 id: "noglobals",
283 label: "Check for Globals",
284 tooltip: "Enabling this will test if any test introduces new properties on the " +
285 "global object (`window` in Browsers). Stored as query-strings."
286 },
287 {
288 id: "notrycatch",
289 label: "No try-catch",
290 tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
291 "exceptions in IE reasonable. Stored as query-strings."
292 }
293 ],
294
295 // Set of all modules.
296 modules: [],
297
298 // Stack of nested modules
299 moduleStack: [],
300
301 // The first unnamed module
302 currentModule: {
303 name: "",
304 tests: []
305 },
306
307 callbacks: {}
308 };
309
310 var urlParams = defined.document ? getUrlParams() : {};
311
312 // Push a loose unnamed module to the modules collection
313 config.modules.push( config.currentModule );
314
315 if ( urlParams.filter === true ) {
316 delete urlParams.filter;
317 }
318
319 // String search anywhere in moduleName+testName
320 config.filter = urlParams.filter;
321
322 config.testId = [];
323 if ( urlParams.testId ) {
324 // Ensure that urlParams.testId is an array
325 urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
326 for (var i = 0; i < urlParams.testId.length; i++ ) {
327 config.testId.push( urlParams.testId[ i ] );
328 }
329 }
330
331 var loggingCallbacks = {};
332
333 // Register logging callbacks
334 function registerLoggingCallbacks( obj ) {
335 var i, l, key,
336 callbackNames = [ "begin", "done", "log", "testStart", "testDone",
337 "moduleStart", "moduleDone" ];
338
339 function registerLoggingCallback( key ) {
340 var loggingCallback = function( callback ) {
341 if ( objectType( callback ) !== "function" ) {
342 throw new Error(
343 "QUnit logging methods require a callback function as their first parameters."
344 );
345 }
346
347 config.callbacks[ key ].push( callback );
348 };
349
350 // DEPRECATED: This will be removed on QUnit 2.0.0+
351 // Stores the registered functions allowing restoring
352 // at verifyLoggingCallbacks() if modified
353 loggingCallbacks[ key ] = loggingCallback;
354
355 return loggingCallback;
356 }
357
358 for ( i = 0, l = callbackNames.length; i < l; i++ ) {
359 key = callbackNames[ i ];
360
361 // Initialize key collection of logging callback
362 if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
363 config.callbacks[ key ] = [];
364 }
365
366 obj[ key ] = registerLoggingCallback( key );
367 }
368 }
369
370 function runLoggingCallbacks( key, args ) {
371 var i, l, callbacks;
372
373 callbacks = config.callbacks[ key ];
374 for ( i = 0, l = callbacks.length; i < l; i++ ) {
375 callbacks[ i ]( args );
376 }
377 }
378
379 // DEPRECATED: This will be removed on 2.0.0+
380 // This function verifies if the loggingCallbacks were modified by the user
381 // If so, it will restore it, assign the given callback and print a console warning
382 function verifyLoggingCallbacks() {
383 var loggingCallback, userCallback;
384
385 for ( loggingCallback in loggingCallbacks ) {
386 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
387
388 userCallback = QUnit[ loggingCallback ];
389
390 // Restore the callback function
391 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
392
393 // Assign the deprecated given callback
394 QUnit[ loggingCallback ]( userCallback );
395
396 if ( global.console && global.console.warn ) {
397 global.console.warn(
398 "QUnit." + loggingCallback + " was replaced with a new value.\n" +
399 "Please, check out the documentation on how to apply logging callbacks.\n" +
400 "Reference: https://api.qunitjs.com/category/callbacks/"
401 );
402 }
403 }
404 }
405 }
406
407 ( function() {
408 if ( !defined.document ) {
409 return;
410 }
411
412 // `onErrorFnPrev` initialized at top of scope
413 // Preserve other handlers
414 var onErrorFnPrev = window.onerror;
415
416 // Cover uncaught exceptions
417 // Returning true will suppress the default browser handler,
418 // returning false will let it run.
419 window.onerror = function( error, filePath, linerNr ) {
420 var ret = false;
421 if ( onErrorFnPrev ) {
422 ret = onErrorFnPrev( error, filePath, linerNr );
423 }
424
425 // Treat return value as window.onerror itself does,
426 // Only do our handling if not suppressed.
427 if ( ret !== true ) {
428 if ( QUnit.config.current ) {
429 if ( QUnit.config.current.ignoreGlobalErrors ) {
430 return true;
431 }
432 QUnit.pushFailure( error, filePath + ":" + linerNr );
433 } else {
434 QUnit.test( "global failure", extend(function() {
435 QUnit.pushFailure( error, filePath + ":" + linerNr );
436 }, { validTest: true } ) );
437 }
438 return false;
439 }
440
441 return ret;
442 };
443 } )();
444
445 QUnit.urlParams = urlParams;
446
447 // Figure out if we're running the tests from a server or not
448 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
449
450 // Expose the current QUnit version
451 QUnit.version = "1.22.0";
452
453 extend( QUnit, {
454
455 // call on start of module test to prepend name to all tests
456 module: function( name, testEnvironment, executeNow ) {
457 var module, moduleFns;
458 var currentModule = config.currentModule;
459
460 if ( arguments.length === 2 ) {
461 if ( testEnvironment instanceof Function ) {
462 executeNow = testEnvironment;
463 testEnvironment = undefined;
464 }
465 }
466
467 // DEPRECATED: handles setup/teardown functions,
468 // beforeEach and afterEach should be used instead
469 if ( testEnvironment && testEnvironment.setup ) {
470 testEnvironment.beforeEach = testEnvironment.setup;
471 delete testEnvironment.setup;
472 }
473 if ( testEnvironment && testEnvironment.teardown ) {
474 testEnvironment.afterEach = testEnvironment.teardown;
475 delete testEnvironment.teardown;
476 }
477
478 module = createModule();
479
480 moduleFns = {
481 beforeEach: setHook( module, "beforeEach" ),
482 afterEach: setHook( module, "afterEach" )
483 };
484
485 if ( executeNow instanceof Function ) {
486 config.moduleStack.push( module );
487 setCurrentModule( module );
488 executeNow.call( module.testEnvironment, moduleFns );
489 config.moduleStack.pop();
490 module = module.parentModule || currentModule;
491 }
492
493 setCurrentModule( module );
494
495 function createModule() {
496 var parentModule = config.moduleStack.length ?
497 config.moduleStack.slice( -1 )[ 0 ] : null;
498 var moduleName = parentModule !== null ?
499 [ parentModule.name, name ].join( " > " ) : name;
500 var module = {
501 name: moduleName,
502 parentModule: parentModule,
503 tests: []
504 };
505
506 var env = {};
507 if ( parentModule ) {
508 extend( env, parentModule.testEnvironment );
509 delete env.beforeEach;
510 delete env.afterEach;
511 }
512 extend( env, testEnvironment );
513 module.testEnvironment = env;
514
515 config.modules.push( module );
516 return module;
517 }
518
519 function setCurrentModule( module ) {
520 config.currentModule = module;
521 }
522
523 },
524
525 // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
526 asyncTest: asyncTest,
527
528 test: test,
529
530 skip: skip,
531
532 only: only,
533
534 // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
535 // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
536 start: function( count ) {
537 var globalStartAlreadyCalled = globalStartCalled;
538
539 if ( !config.current ) {
540 globalStartCalled = true;
541
542 if ( runStarted ) {
543 throw new Error( "Called start() outside of a test context while already started" );
544 } else if ( globalStartAlreadyCalled || count > 1 ) {
545 throw new Error( "Called start() outside of a test context too many times" );
546 } else if ( config.autostart ) {
547 throw new Error( "Called start() outside of a test context when " +
548 "QUnit.config.autostart was true" );
549 } else if ( !config.pageLoaded ) {
550
551 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
552 config.autostart = true;
553 return;
554 }
555 } else {
556
557 // If a test is running, adjust its semaphore
558 config.current.semaphore -= count || 1;
559
560 // If semaphore is non-numeric, throw error
561 if ( isNaN( config.current.semaphore ) ) {
562 config.current.semaphore = 0;
563
564 QUnit.pushFailure(
565 "Called start() with a non-numeric decrement.",
566 sourceFromStacktrace( 2 )
567 );
568 return;
569 }
570
571 // Don't start until equal number of stop-calls
572 if ( config.current.semaphore > 0 ) {
573 return;
574 }
575
576 // throw an Error if start is called more often than stop
577 if ( config.current.semaphore < 0 ) {
578 config.current.semaphore = 0;
579
580 QUnit.pushFailure(
581 "Called start() while already started (test's semaphore was 0 already)",
582 sourceFromStacktrace( 2 )
583 );
584 return;
585 }
586 }
587
588 resumeProcessing();
589 },
590
591 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
592 stop: function( count ) {
593
594 // If there isn't a test running, don't allow QUnit.stop() to be called
595 if ( !config.current ) {
596 throw new Error( "Called stop() outside of a test context" );
597 }
598
599 // If a test is running, adjust its semaphore
600 config.current.semaphore += count || 1;
601
602 pauseProcessing();
603 },
604
605 config: config,
606
607 is: is,
608
609 objectType: objectType,
610
611 extend: extend,
612
613 load: function() {
614 config.pageLoaded = true;
615
616 // Initialize the configuration options
617 extend( config, {
618 stats: { all: 0, bad: 0 },
619 moduleStats: { all: 0, bad: 0 },
620 started: 0,
621 updateRate: 1000,
622 autostart: true,
623 filter: ""
624 }, true );
625
626 config.blocking = false;
627
628 if ( config.autostart ) {
629 resumeProcessing();
630 }
631 },
632
633 stack: function( offset ) {
634 offset = ( offset || 0 ) + 2;
635 return sourceFromStacktrace( offset );
636 }
637 });
638
639 registerLoggingCallbacks( QUnit );
640
641 function begin() {
642 var i, l,
643 modulesLog = [];
644
645 // If the test run hasn't officially begun yet
646 if ( !config.started ) {
647
648 // Record the time of the test run's beginning
649 config.started = now();
650
651 verifyLoggingCallbacks();
652
653 // Delete the loose unnamed module if unused.
654 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
655 config.modules.shift();
656 }
657
658 // Avoid unnecessary information by not logging modules' test environments
659 for ( i = 0, l = config.modules.length; i < l; i++ ) {
660 modulesLog.push({
661 name: config.modules[ i ].name,
662 tests: config.modules[ i ].tests
663 });
664 }
665
666 // The test run is officially beginning now
667 runLoggingCallbacks( "begin", {
668 totalTests: Test.count,
669 modules: modulesLog
670 });
671 }
672
673 config.blocking = false;
674 process( true );
675 }
676
677 function process( last ) {
678 function next() {
679 process( last );
680 }
681 var start = now();
682 config.depth = ( config.depth || 0 ) + 1;
683
684 while ( config.queue.length && !config.blocking ) {
685 if ( !defined.setTimeout || config.updateRate <= 0 ||
686 ( ( now() - start ) < config.updateRate ) ) {
687 if ( config.current ) {
688
689 // Reset async tracking for each phase of the Test lifecycle
690 config.current.usedAsync = false;
691 }
692 config.queue.shift()();
693 } else {
694 setTimeout( next, 13 );
695 break;
696 }
697 }
698 config.depth--;
699 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
700 done();
701 }
702 }
703
704 function pauseProcessing() {
705 config.blocking = true;
706
707 if ( config.testTimeout && defined.setTimeout ) {
708 clearTimeout( config.timeout );
709 config.timeout = setTimeout(function() {
710 if ( config.current ) {
711 config.current.semaphore = 0;
712 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
713 } else {
714 throw new Error( "Test timed out" );
715 }
716 resumeProcessing();
717 }, config.testTimeout );
718 }
719 }
720
721 function resumeProcessing() {
722 runStarted = true;
723
724 // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
725 if ( defined.setTimeout ) {
726 setTimeout(function() {
727 if ( config.current && config.current.semaphore > 0 ) {
728 return;
729 }
730 if ( config.timeout ) {
731 clearTimeout( config.timeout );
732 }
733
734 begin();
735 }, 13 );
736 } else {
737 begin();
738 }
739 }
740
741 function done() {
742 var runtime, passed;
743
744 config.autorun = true;
745
746 // Log the last module results
747 if ( config.previousModule ) {
748 runLoggingCallbacks( "moduleDone", {
749 name: config.previousModule.name,
750 tests: config.previousModule.tests,
751 failed: config.moduleStats.bad,
752 passed: config.moduleStats.all - config.moduleStats.bad,
753 total: config.moduleStats.all,
754 runtime: now() - config.moduleStats.started
755 });
756 }
757 delete config.previousModule;
758
759 runtime = now() - config.started;
760 passed = config.stats.all - config.stats.bad;
761
762 runLoggingCallbacks( "done", {
763 failed: config.stats.bad,
764 passed: passed,
765 total: config.stats.all,
766 runtime: runtime
767 });
768 }
769
770 function setHook( module, hookName ) {
771 if ( module.testEnvironment === undefined ) {
772 module.testEnvironment = {};
773 }
774
775 return function( callback ) {
776 module.testEnvironment[ hookName ] = callback;
777 };
778 }
779
780 var focused = false;
781 var priorityCount = 0;
782
783 function Test( settings ) {
784 var i, l;
785
786 ++Test.count;
787
788 extend( this, settings );
789 this.assertions = [];
790 this.semaphore = 0;
791 this.usedAsync = false;
792 this.module = config.currentModule;
793 this.stack = sourceFromStacktrace( 3 );
794
795 // Register unique strings
796 for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
797 if ( this.module.tests[ i ].name === this.testName ) {
798 this.testName += " ";
799 }
800 }
801
802 this.testId = generateHash( this.module.name, this.testName );
803
804 this.module.tests.push({
805 name: this.testName,
806 testId: this.testId
807 });
808
809 if ( settings.skip ) {
810
811 // Skipped tests will fully ignore any sent callback
812 this.callback = function() {};
813 this.async = false;
814 this.expected = 0;
815 } else {
816 this.assert = new Assert( this );
817 }
818 }
819
820 Test.count = 0;
821
822 Test.prototype = {
823 before: function() {
824 if (
825
826 // Emit moduleStart when we're switching from one module to another
827 this.module !== config.previousModule ||
828
829 // They could be equal (both undefined) but if the previousModule property doesn't
830 // yet exist it means this is the first test in a suite that isn't wrapped in a
831 // module, in which case we'll just emit a moduleStart event for 'undefined'.
832 // Without this, reporters can get testStart before moduleStart which is a problem.
833 !hasOwn.call( config, "previousModule" )
834 ) {
835 if ( hasOwn.call( config, "previousModule" ) ) {
836 runLoggingCallbacks( "moduleDone", {
837 name: config.previousModule.name,
838 tests: config.previousModule.tests,
839 failed: config.moduleStats.bad,
840 passed: config.moduleStats.all - config.moduleStats.bad,
841 total: config.moduleStats.all,
842 runtime: now() - config.moduleStats.started
843 });
844 }
845 config.previousModule = this.module;
846 config.moduleStats = { all: 0, bad: 0, started: now() };
847 runLoggingCallbacks( "moduleStart", {
848 name: this.module.name,
849 tests: this.module.tests
850 });
851 }
852
853 config.current = this;
854
855 if ( this.module.testEnvironment ) {
856 delete this.module.testEnvironment.beforeEach;
857 delete this.module.testEnvironment.afterEach;
858 }
859 this.testEnvironment = extend( {}, this.module.testEnvironment );
860
861 this.started = now();
862 runLoggingCallbacks( "testStart", {
863 name: this.testName,
864 module: this.module.name,
865 testId: this.testId
866 });
867
868 if ( !config.pollution ) {
869 saveGlobal();
870 }
871 },
872
873 run: function() {
874 var promise;
875
876 config.current = this;
877
878 if ( this.async ) {
879 QUnit.stop();
880 }
881
882 this.callbackStarted = now();
883
884 if ( config.notrycatch ) {
885 runTest( this );
886 return;
887 }
888
889 try {
890 runTest( this );
891 } catch ( e ) {
892 this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
893 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
894
895 // else next test will carry the responsibility
896 saveGlobal();
897
898 // Restart the tests if they're blocking
899 if ( config.blocking ) {
900 QUnit.start();
901 }
902 }
903
904 function runTest( test ) {
905 promise = test.callback.call( test.testEnvironment, test.assert );
906 test.resolvePromise( promise );
907 }
908 },
909
910 after: function() {
911 checkPollution();
912 },
913
914 queueHook: function( hook, hookName ) {
915 var promise,
916 test = this;
917 return function runHook() {
918 config.current = test;
919 if ( config.notrycatch ) {
920 callHook();
921 return;
922 }
923 try {
924 callHook();
925 } catch ( error ) {
926 test.pushFailure( hookName + " failed on " + test.testName + ": " +
927 ( error.message || error ), extractStacktrace( error, 0 ) );
928 }
929
930 function callHook() {
931 promise = hook.call( test.testEnvironment, test.assert );
932 test.resolvePromise( promise, hookName );
933 }
934 };
935 },
936
937 // Currently only used for module level hooks, can be used to add global level ones
938 hooks: function( handler ) {
939 var hooks = [];
940
941 function processHooks( test, module ) {
942 if ( module.parentModule ) {
943 processHooks( test, module.parentModule );
944 }
945 if ( module.testEnvironment &&
946 QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
947 hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) );
948 }
949 }
950
951 // Hooks are ignored on skipped tests
952 if ( !this.skip ) {
953 processHooks( this, this.module );
954 }
955 return hooks;
956 },
957
958 finish: function() {
959 config.current = this;
960 if ( config.requireExpects && this.expected === null ) {
961 this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
962 "not called.", this.stack );
963 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
964 this.pushFailure( "Expected " + this.expected + " assertions, but " +
965 this.assertions.length + " were run", this.stack );
966 } else if ( this.expected === null && !this.assertions.length ) {
967 this.pushFailure( "Expected at least one assertion, but none were run - call " +
968 "expect(0) to accept zero assertions.", this.stack );
969 }
970
971 var i,
972 bad = 0;
973
974 this.runtime = now() - this.started;
975 config.stats.all += this.assertions.length;
976 config.moduleStats.all += this.assertions.length;
977
978 for ( i = 0; i < this.assertions.length; i++ ) {
979 if ( !this.assertions[ i ].result ) {
980 bad++;
981 config.stats.bad++;
982 config.moduleStats.bad++;
983 }
984 }
985
986 runLoggingCallbacks( "testDone", {
987 name: this.testName,
988 module: this.module.name,
989 skipped: !!this.skip,
990 failed: bad,
991 passed: this.assertions.length - bad,
992 total: this.assertions.length,
993 runtime: this.runtime,
994
995 // HTML Reporter use
996 assertions: this.assertions,
997 testId: this.testId,
998
999 // Source of Test
1000 source: this.stack,
1001
1002 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1003 duration: this.runtime
1004 });
1005
1006 // QUnit.reset() is deprecated and will be replaced for a new
1007 // fixture reset function on QUnit 2.0/2.1.
1008 // It's still called here for backwards compatibility handling
1009 QUnit.reset();
1010
1011 config.current = undefined;
1012 },
1013
1014 queue: function() {
1015 var priority,
1016 test = this;
1017
1018 if ( !this.valid() ) {
1019 return;
1020 }
1021
1022 function run() {
1023
1024 // each of these can by async
1025 synchronize([
1026 function() {
1027 test.before();
1028 },
1029
1030 test.hooks( "beforeEach" ),
1031 function() {
1032 test.run();
1033 },
1034
1035 test.hooks( "afterEach" ).reverse(),
1036
1037 function() {
1038 test.after();
1039 },
1040 function() {
1041 test.finish();
1042 }
1043 ]);
1044 }
1045
1046 // Prioritize previously failed tests, detected from sessionStorage
1047 priority = QUnit.config.reorder && defined.sessionStorage &&
1048 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
1049
1050 return synchronize( run, priority );
1051 },
1052
1053 pushResult: function( resultInfo ) {
1054
1055 // resultInfo = { result, actual, expected, message, negative }
1056 var source,
1057 details = {
1058 module: this.module.name,
1059 name: this.testName,
1060 result: resultInfo.result,
1061 message: resultInfo.message,
1062 actual: resultInfo.actual,
1063 expected: resultInfo.expected,
1064 testId: this.testId,
1065 negative: resultInfo.negative || false,
1066 runtime: now() - this.started
1067 };
1068
1069 if ( !resultInfo.result ) {
1070 source = sourceFromStacktrace();
1071
1072 if ( source ) {
1073 details.source = source;
1074 }
1075 }
1076
1077 runLoggingCallbacks( "log", details );
1078
1079 this.assertions.push({
1080 result: !!resultInfo.result,
1081 message: resultInfo.message
1082 });
1083 },
1084
1085 pushFailure: function( message, source, actual ) {
1086 if ( !( this instanceof Test ) ) {
1087 throw new Error( "pushFailure() assertion outside test context, was " +
1088 sourceFromStacktrace( 2 ) );
1089 }
1090
1091 var details = {
1092 module: this.module.name,
1093 name: this.testName,
1094 result: false,
1095 message: message || "error",
1096 actual: actual || null,
1097 testId: this.testId,
1098 runtime: now() - this.started
1099 };
1100
1101 if ( source ) {
1102 details.source = source;
1103 }
1104
1105 runLoggingCallbacks( "log", details );
1106
1107 this.assertions.push({
1108 result: false,
1109 message: message
1110 });
1111 },
1112
1113 resolvePromise: function( promise, phase ) {
1114 var then, message,
1115 test = this;
1116 if ( promise != null ) {
1117 then = promise.then;
1118 if ( QUnit.objectType( then ) === "function" ) {
1119 QUnit.stop();
1120 then.call(
1121 promise,
1122 function() { QUnit.start(); },
1123 function( error ) {
1124 message = "Promise rejected " +
1125 ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
1126 " " + test.testName + ": " + ( error.message || error );
1127 test.pushFailure( message, extractStacktrace( error, 0 ) );
1128
1129 // else next test will carry the responsibility
1130 saveGlobal();
1131
1132 // Unblock
1133 QUnit.start();
1134 }
1135 );
1136 }
1137 }
1138 },
1139
1140 valid: function() {
1141 var filter = config.filter,
1142 regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
1143 module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
1144 fullName = ( this.module.name + ": " + this.testName );
1145
1146 function testInModuleChain( testModule ) {
1147 var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
1148 if ( testModuleName === module ) {
1149 return true;
1150 } else if ( testModule.parentModule ) {
1151 return testInModuleChain( testModule.parentModule );
1152 } else {
1153 return false;
1154 }
1155 }
1156
1157 // Internally-generated tests are always valid
1158 if ( this.callback && this.callback.validTest ) {
1159 return true;
1160 }
1161
1162 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1163 return false;
1164 }
1165
1166 if ( module && !testInModuleChain( this.module ) ) {
1167 return false;
1168 }
1169
1170 if ( !filter ) {
1171 return true;
1172 }
1173
1174 return regexFilter ?
1175 this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) :
1176 this.stringFilter( filter, fullName );
1177 },
1178
1179 regexFilter: function( exclude, pattern, flags, fullName ) {
1180 var regex = new RegExp( pattern, flags );
1181 var match = regex.test( fullName );
1182
1183 return match !== exclude;
1184 },
1185
1186 stringFilter: function( filter, fullName ) {
1187 filter = filter.toLowerCase();
1188 fullName = fullName.toLowerCase();
1189
1190 var include = filter.charAt( 0 ) !== "!";
1191 if ( !include ) {
1192 filter = filter.slice( 1 );
1193 }
1194
1195 // If the filter matches, we need to honour include
1196 if ( fullName.indexOf( filter ) !== -1 ) {
1197 return include;
1198 }
1199
1200 // Otherwise, do the opposite
1201 return !include;
1202 }
1203 };
1204
1205 // Resets the test setup. Useful for tests that modify the DOM.
1206 /*
1207 DEPRECATED: Use multiple tests instead of resetting inside a test.
1208 Use testStart or testDone for custom cleanup.
1209 This method will throw an error in 2.0, and will be removed in 2.1
1210 */
1211 QUnit.reset = function() {
1212
1213 // Return on non-browser environments
1214 // This is necessary to not break on node tests
1215 if ( !defined.document ) {
1216 return;
1217 }
1218
1219 var fixture = defined.document && document.getElementById &&
1220 document.getElementById( "qunit-fixture" );
1221
1222 if ( fixture ) {
1223 fixture.innerHTML = config.fixture;
1224 }
1225 };
1226
1227 QUnit.pushFailure = function() {
1228 if ( !QUnit.config.current ) {
1229 throw new Error( "pushFailure() assertion outside test context, in " +
1230 sourceFromStacktrace( 2 ) );
1231 }
1232
1233 // Gets current test obj
1234 var currentTest = QUnit.config.current;
1235
1236 return currentTest.pushFailure.apply( currentTest, arguments );
1237 };
1238
1239 // Based on Java's String.hashCode, a simple but not
1240 // rigorously collision resistant hashing function
1241 function generateHash( module, testName ) {
1242 var hex,
1243 i = 0,
1244 hash = 0,
1245 str = module + "\x1C" + testName,
1246 len = str.length;
1247
1248 for ( ; i < len; i++ ) {
1249 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1250 hash |= 0;
1251 }
1252
1253 // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
1254 // strictly necessary but increases user understanding that the id is a SHA-like hash
1255 hex = ( 0x100000000 + hash ).toString( 16 );
1256 if ( hex.length < 8 ) {
1257 hex = "0000000" + hex;
1258 }
1259
1260 return hex.slice( -8 );
1261 }
1262
1263 function synchronize( callback, priority ) {
1264 var last = !priority;
1265
1266 if ( QUnit.objectType( callback ) === "array" ) {
1267 while ( callback.length ) {
1268 synchronize( callback.shift() );
1269 }
1270 return;
1271 }
1272
1273 if ( priority ) {
1274 config.queue.splice( priorityCount++, 0, callback );
1275 } else {
1276 config.queue.push( callback );
1277 }
1278
1279 if ( config.autorun && !config.blocking ) {
1280 process( last );
1281 }
1282 }
1283
1284 function saveGlobal() {
1285 config.pollution = [];
1286
1287 if ( config.noglobals ) {
1288 for ( var key in global ) {
1289 if ( hasOwn.call( global, key ) ) {
1290
1291 // in Opera sometimes DOM element ids show up here, ignore them
1292 if ( /^qunit-test-output/.test( key ) ) {
1293 continue;
1294 }
1295 config.pollution.push( key );
1296 }
1297 }
1298 }
1299 }
1300
1301 function checkPollution() {
1302 var newGlobals,
1303 deletedGlobals,
1304 old = config.pollution;
1305
1306 saveGlobal();
1307
1308 newGlobals = diff( config.pollution, old );
1309 if ( newGlobals.length > 0 ) {
1310 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
1311 }
1312
1313 deletedGlobals = diff( old, config.pollution );
1314 if ( deletedGlobals.length > 0 ) {
1315 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
1316 }
1317 }
1318
1319 // Will be exposed as QUnit.asyncTest
1320 function asyncTest( testName, expected, callback ) {
1321 if ( arguments.length === 2 ) {
1322 callback = expected;
1323 expected = null;
1324 }
1325
1326 QUnit.test( testName, expected, callback, true );
1327 }
1328
1329 // Will be exposed as QUnit.test
1330 function test( testName, expected, callback, async ) {
1331 if ( focused ) { return; }
1332
1333 var newTest;
1334
1335 if ( arguments.length === 2 ) {
1336 callback = expected;
1337 expected = null;
1338 }
1339
1340 newTest = new Test({
1341 testName: testName,
1342 expected: expected,
1343 async: async,
1344 callback: callback
1345 });
1346
1347 newTest.queue();
1348 }
1349
1350 // Will be exposed as QUnit.skip
1351 function skip( testName ) {
1352 if ( focused ) { return; }
1353
1354 var test = new Test({
1355 testName: testName,
1356 skip: true
1357 });
1358
1359 test.queue();
1360 }
1361
1362 // Will be exposed as QUnit.only
1363 function only( testName, expected, callback, async ) {
1364 var newTest;
1365
1366 if ( focused ) { return; }
1367
1368 QUnit.config.queue.length = 0;
1369 focused = true;
1370
1371 if ( arguments.length === 2 ) {
1372 callback = expected;
1373 expected = null;
1374 }
1375
1376 newTest = new Test({
1377 testName: testName,
1378 expected: expected,
1379 async: async,
1380 callback: callback
1381 });
1382
1383 newTest.queue();
1384 }
1385
1386 function Assert( testContext ) {
1387 this.test = testContext;
1388 }
1389
1390 // Assert helpers
1391 QUnit.assert = Assert.prototype = {
1392
1393 // Specify the number of expected assertions to guarantee that failed test
1394 // (no assertions are run at all) don't slip through.
1395 expect: function( asserts ) {
1396 if ( arguments.length === 1 ) {
1397 this.test.expected = asserts;
1398 } else {
1399 return this.test.expected;
1400 }
1401 },
1402
1403 // Increment this Test's semaphore counter, then return a function that
1404 // decrements that counter a maximum of once.
1405 async: function( count ) {
1406 var test = this.test,
1407 popped = false,
1408 acceptCallCount = count;
1409
1410 if ( typeof acceptCallCount === "undefined" ) {
1411 acceptCallCount = 1;
1412 }
1413
1414 test.semaphore += 1;
1415 test.usedAsync = true;
1416 pauseProcessing();
1417
1418 return function done() {
1419
1420 if ( popped ) {
1421 test.pushFailure( "Too many calls to the `assert.async` callback",
1422 sourceFromStacktrace( 2 ) );
1423 return;
1424 }
1425 acceptCallCount -= 1;
1426 if ( acceptCallCount > 0 ) {
1427 return;
1428 }
1429
1430 test.semaphore -= 1;
1431 popped = true;
1432 resumeProcessing();
1433 };
1434 },
1435
1436 // Exports test.push() to the user API
1437 // Alias of pushResult.
1438 push: function( result, actual, expected, message, negative ) {
1439 var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert;
1440 return currentAssert.pushResult( {
1441 result: result,
1442 actual: actual,
1443 expected: expected,
1444 message: message,
1445 negative: negative
1446 } );
1447 },
1448
1449 pushResult: function( resultInfo ) {
1450
1451 // resultInfo = { result, actual, expected, message, negative }
1452 var assert = this,
1453 currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
1454
1455 // Backwards compatibility fix.
1456 // Allows the direct use of global exported assertions and QUnit.assert.*
1457 // Although, it's use is not recommended as it can leak assertions
1458 // to other tests from async tests, because we only get a reference to the current test,
1459 // not exactly the test where assertion were intended to be called.
1460 if ( !currentTest ) {
1461 throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
1462 }
1463
1464 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1465 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1466 sourceFromStacktrace( 2 ) );
1467
1468 // Allow this assertion to continue running anyway...
1469 }
1470
1471 if ( !( assert instanceof Assert ) ) {
1472 assert = currentTest.assert;
1473 }
1474
1475 return assert.test.pushResult( resultInfo );
1476 },
1477
1478 ok: function( result, message ) {
1479 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1480 QUnit.dump.parse( result ) );
1481 this.pushResult( {
1482 result: !!result,
1483 actual: result,
1484 expected: true,
1485 message: message
1486 } );
1487 },
1488
1489 notOk: function( result, message ) {
1490 message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
1491 QUnit.dump.parse( result ) );
1492 this.pushResult( {
1493 result: !result,
1494 actual: result,
1495 expected: false,
1496 message: message
1497 } );
1498 },
1499
1500 equal: function( actual, expected, message ) {
1501 /*jshint eqeqeq:false */
1502 this.pushResult( {
1503 result: expected == actual,
1504 actual: actual,
1505 expected: expected,
1506 message: message
1507 } );
1508 },
1509
1510 notEqual: function( actual, expected, message ) {
1511 /*jshint eqeqeq:false */
1512 this.pushResult( {
1513 result: expected != actual,
1514 actual: actual,
1515 expected: expected,
1516 message: message,
1517 negative: true
1518 } );
1519 },
1520
1521 propEqual: function( actual, expected, message ) {
1522 actual = objectValues( actual );
1523 expected = objectValues( expected );
1524 this.pushResult( {
1525 result: QUnit.equiv( actual, expected ),
1526 actual: actual,
1527 expected: expected,
1528 message: message
1529 } );
1530 },
1531
1532 notPropEqual: function( actual, expected, message ) {
1533 actual = objectValues( actual );
1534 expected = objectValues( expected );
1535 this.pushResult( {
1536 result: !QUnit.equiv( actual, expected ),
1537 actual: actual,
1538 expected: expected,
1539 message: message,
1540 negative: true
1541 } );
1542 },
1543
1544 deepEqual: function( actual, expected, message ) {
1545 this.pushResult( {
1546 result: QUnit.equiv( actual, expected ),
1547 actual: actual,
1548 expected: expected,
1549 message: message
1550 } );
1551 },
1552
1553 notDeepEqual: function( actual, expected, message ) {
1554 this.pushResult( {
1555 result: !QUnit.equiv( actual, expected ),
1556 actual: actual,
1557 expected: expected,
1558 message: message,
1559 negative: true
1560 } );
1561 },
1562
1563 strictEqual: function( actual, expected, message ) {
1564 this.pushResult( {
1565 result: expected === actual,
1566 actual: actual,
1567 expected: expected,
1568 message: message
1569 } );
1570 },
1571
1572 notStrictEqual: function( actual, expected, message ) {
1573 this.pushResult( {
1574 result: expected !== actual,
1575 actual: actual,
1576 expected: expected,
1577 message: message,
1578 negative: true
1579 } );
1580 },
1581
1582 "throws": function( block, expected, message ) {
1583 var actual, expectedType,
1584 expectedOutput = expected,
1585 ok = false,
1586 currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
1587
1588 // 'expected' is optional unless doing string comparison
1589 if ( message == null && typeof expected === "string" ) {
1590 message = expected;
1591 expected = null;
1592 }
1593
1594 currentTest.ignoreGlobalErrors = true;
1595 try {
1596 block.call( currentTest.testEnvironment );
1597 } catch (e) {
1598 actual = e;
1599 }
1600 currentTest.ignoreGlobalErrors = false;
1601
1602 if ( actual ) {
1603 expectedType = QUnit.objectType( expected );
1604
1605 // we don't want to validate thrown error
1606 if ( !expected ) {
1607 ok = true;
1608 expectedOutput = null;
1609
1610 // expected is a regexp
1611 } else if ( expectedType === "regexp" ) {
1612 ok = expected.test( errorString( actual ) );
1613
1614 // expected is a string
1615 } else if ( expectedType === "string" ) {
1616 ok = expected === errorString( actual );
1617
1618 // expected is a constructor, maybe an Error constructor
1619 } else if ( expectedType === "function" && actual instanceof expected ) {
1620 ok = true;
1621
1622 // expected is an Error object
1623 } else if ( expectedType === "object" ) {
1624 ok = actual instanceof expected.constructor &&
1625 actual.name === expected.name &&
1626 actual.message === expected.message;
1627
1628 // expected is a validation function which returns true if validation passed
1629 } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1630 expectedOutput = null;
1631 ok = true;
1632 }
1633 }
1634
1635 currentTest.assert.pushResult( {
1636 result: ok,
1637 actual: actual,
1638 expected: expectedOutput,
1639 message: message
1640 } );
1641 }
1642 };
1643
1644 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
1645 // Known to us are: Closure Compiler, Narwhal
1646 (function() {
1647 /*jshint sub:true */
1648 Assert.prototype.raises = Assert.prototype[ "throws" ];
1649 }());
1650
1651 function errorString( error ) {
1652 var name, message,
1653 resultErrorString = error.toString();
1654 if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
1655 name = error.name ? error.name.toString() : "Error";
1656 message = error.message ? error.message.toString() : "";
1657 if ( name && message ) {
1658 return name + ": " + message;
1659 } else if ( name ) {
1660 return name;
1661 } else if ( message ) {
1662 return message;
1663 } else {
1664 return "Error";
1665 }
1666 } else {
1667 return resultErrorString;
1668 }
1669 }
1670
1671 // Test for equality any JavaScript type.
1672 // Author: Philippe Rathé <prathe@gmail.com>
1673 QUnit.equiv = (function() {
1674
1675 // Stack to decide between skip/abort functions
1676 var callers = [];
1677
1678 // Stack to avoiding loops from circular referencing
1679 var parents = [];
1680 var parentsB = [];
1681
1682 var getProto = Object.getPrototypeOf || function( obj ) {
1683
1684 /*jshint proto: true */
1685 return obj.__proto__;
1686 };
1687
1688 function useStrictEquality( b, a ) {
1689
1690 // To catch short annotation VS 'new' annotation of a declaration. e.g.:
1691 // `var i = 1;`
1692 // `var j = new Number(1);`
1693 if ( typeof a === "object" ) {
1694 a = a.valueOf();
1695 }
1696 if ( typeof b === "object" ) {
1697 b = b.valueOf();
1698 }
1699
1700 return a === b;
1701 }
1702
1703 function compareConstructors( a, b ) {
1704 var protoA = getProto( a );
1705 var protoB = getProto( b );
1706
1707 // Comparing constructors is more strict than using `instanceof`
1708 if ( a.constructor === b.constructor ) {
1709 return true;
1710 }
1711
1712 // Ref #851
1713 // If the obj prototype descends from a null constructor, treat it
1714 // as a null prototype.
1715 if ( protoA && protoA.constructor === null ) {
1716 protoA = null;
1717 }
1718 if ( protoB && protoB.constructor === null ) {
1719 protoB = null;
1720 }
1721
1722 // Allow objects with no prototype to be equivalent to
1723 // objects with Object as their constructor.
1724 if ( ( protoA === null && protoB === Object.prototype ) ||
1725 ( protoB === null && protoA === Object.prototype ) ) {
1726 return true;
1727 }
1728
1729 return false;
1730 }
1731
1732 function getRegExpFlags( regexp ) {
1733 return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
1734 }
1735
1736 var callbacks = {
1737 "string": useStrictEquality,
1738 "boolean": useStrictEquality,
1739 "number": useStrictEquality,
1740 "null": useStrictEquality,
1741 "undefined": useStrictEquality,
1742 "symbol": useStrictEquality,
1743 "date": useStrictEquality,
1744
1745 "nan": function() {
1746 return true;
1747 },
1748
1749 "regexp": function( b, a ) {
1750 return a.source === b.source &&
1751
1752 // Include flags in the comparison
1753 getRegExpFlags( a ) === getRegExpFlags( b );
1754 },
1755
1756 // - skip when the property is a method of an instance (OOP)
1757 // - abort otherwise,
1758 // initial === would have catch identical references anyway
1759 "function": function() {
1760 var caller = callers[ callers.length - 1 ];
1761 return caller !== Object && typeof caller !== "undefined";
1762 },
1763
1764 "array": function( b, a ) {
1765 var i, j, len, loop, aCircular, bCircular;
1766
1767 len = a.length;
1768 if ( len !== b.length ) {
1769 // safe and faster
1770 return false;
1771 }
1772
1773 // Track reference to avoid circular references
1774 parents.push( a );
1775 parentsB.push( b );
1776 for ( i = 0; i < len; i++ ) {
1777 loop = false;
1778 for ( j = 0; j < parents.length; j++ ) {
1779 aCircular = parents[ j ] === a[ i ];
1780 bCircular = parentsB[ j ] === b[ i ];
1781 if ( aCircular || bCircular ) {
1782 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1783 loop = true;
1784 } else {
1785 parents.pop();
1786 parentsB.pop();
1787 return false;
1788 }
1789 }
1790 }
1791 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1792 parents.pop();
1793 parentsB.pop();
1794 return false;
1795 }
1796 }
1797 parents.pop();
1798 parentsB.pop();
1799 return true;
1800 },
1801
1802 "set": function( b, a ) {
1803 var aArray, bArray;
1804
1805 aArray = [];
1806 a.forEach( function( v ) {
1807 aArray.push( v );
1808 });
1809 bArray = [];
1810 b.forEach( function( v ) {
1811 bArray.push( v );
1812 });
1813
1814 return innerEquiv( bArray, aArray );
1815 },
1816
1817 "map": function( b, a ) {
1818 var aArray, bArray;
1819
1820 aArray = [];
1821 a.forEach( function( v, k ) {
1822 aArray.push( [ k, v ] );
1823 });
1824 bArray = [];
1825 b.forEach( function( v, k ) {
1826 bArray.push( [ k, v ] );
1827 });
1828
1829 return innerEquiv( bArray, aArray );
1830 },
1831
1832 "object": function( b, a ) {
1833 var i, j, loop, aCircular, bCircular;
1834
1835 // Default to true
1836 var eq = true;
1837 var aProperties = [];
1838 var bProperties = [];
1839
1840 if ( compareConstructors( a, b ) === false ) {
1841 return false;
1842 }
1843
1844 // Stack constructor before traversing properties
1845 callers.push( a.constructor );
1846
1847 // Track reference to avoid circular references
1848 parents.push( a );
1849 parentsB.push( b );
1850
1851 // Be strict: don't ensure hasOwnProperty and go deep
1852 for ( i in a ) {
1853 loop = false;
1854 for ( j = 0; j < parents.length; j++ ) {
1855 aCircular = parents[ j ] === a[ i ];
1856 bCircular = parentsB[ j ] === b[ i ];
1857 if ( aCircular || bCircular ) {
1858 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1859 loop = true;
1860 } else {
1861 eq = false;
1862 break;
1863 }
1864 }
1865 }
1866 aProperties.push( i );
1867 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1868 eq = false;
1869 break;
1870 }
1871 }
1872
1873 parents.pop();
1874 parentsB.pop();
1875
1876 // Unstack, we are done
1877 callers.pop();
1878
1879 for ( i in b ) {
1880
1881 // Collect b's properties
1882 bProperties.push( i );
1883 }
1884
1885 // Ensures identical properties name
1886 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1887 }
1888 };
1889
1890 function typeEquiv( a, b ) {
1891 var type = QUnit.objectType( a );
1892 return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
1893 }
1894
1895 // The real equiv function
1896 function innerEquiv( a, b ) {
1897
1898 // We're done when there's nothing more to compare
1899 if ( arguments.length < 2 ) {
1900 return true;
1901 }
1902
1903 // Require type-specific equality
1904 return ( a === b || typeEquiv( a, b ) ) &&
1905
1906 // ...across all consecutive argument pairs
1907 ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
1908 }
1909
1910 return innerEquiv;
1911 }());
1912
1913 // Based on jsDump by Ariel Flesler
1914 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1915 QUnit.dump = (function() {
1916 function quote( str ) {
1917 return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
1918 }
1919 function literal( o ) {
1920 return o + "";
1921 }
1922 function join( pre, arr, post ) {
1923 var s = dump.separator(),
1924 base = dump.indent(),
1925 inner = dump.indent( 1 );
1926 if ( arr.join ) {
1927 arr = arr.join( "," + s + inner );
1928 }
1929 if ( !arr ) {
1930 return pre + post;
1931 }
1932 return [ pre, inner + arr, base + post ].join( s );
1933 }
1934 function array( arr, stack ) {
1935 var i = arr.length,
1936 ret = new Array( i );
1937
1938 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1939 return "[object Array]";
1940 }
1941
1942 this.up();
1943 while ( i-- ) {
1944 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1945 }
1946 this.down();
1947 return join( "[", ret, "]" );
1948 }
1949
1950 var reName = /^function (\w+)/,
1951 dump = {
1952
1953 // objType is used mostly internally, you can fix a (custom) type in advance
1954 parse: function( obj, objType, stack ) {
1955 stack = stack || [];
1956 var res, parser, parserType,
1957 inStack = inArray( obj, stack );
1958
1959 if ( inStack !== -1 ) {
1960 return "recursion(" + ( inStack - stack.length ) + ")";
1961 }
1962
1963 objType = objType || this.typeOf( obj );
1964 parser = this.parsers[ objType ];
1965 parserType = typeof parser;
1966
1967 if ( parserType === "function" ) {
1968 stack.push( obj );
1969 res = parser.call( this, obj, stack );
1970 stack.pop();
1971 return res;
1972 }
1973 return ( parserType === "string" ) ? parser : this.parsers.error;
1974 },
1975 typeOf: function( obj ) {
1976 var type;
1977 if ( obj === null ) {
1978 type = "null";
1979 } else if ( typeof obj === "undefined" ) {
1980 type = "undefined";
1981 } else if ( QUnit.is( "regexp", obj ) ) {
1982 type = "regexp";
1983 } else if ( QUnit.is( "date", obj ) ) {
1984 type = "date";
1985 } else if ( QUnit.is( "function", obj ) ) {
1986 type = "function";
1987 } else if ( obj.setInterval !== undefined &&
1988 obj.document !== undefined &&
1989 obj.nodeType === undefined ) {
1990 type = "window";
1991 } else if ( obj.nodeType === 9 ) {
1992 type = "document";
1993 } else if ( obj.nodeType ) {
1994 type = "node";
1995 } else if (
1996
1997 // native arrays
1998 toString.call( obj ) === "[object Array]" ||
1999
2000 // NodeList objects
2001 ( typeof obj.length === "number" && obj.item !== undefined &&
2002 ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
2003 obj[ 0 ] === undefined ) ) )
2004 ) {
2005 type = "array";
2006 } else if ( obj.constructor === Error.prototype.constructor ) {
2007 type = "error";
2008 } else {
2009 type = typeof obj;
2010 }
2011 return type;
2012 },
2013 separator: function() {
2014 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
2015 },
2016 // extra can be a number, shortcut for increasing-calling-decreasing
2017 indent: function( extra ) {
2018 if ( !this.multiline ) {
2019 return "";
2020 }
2021 var chr = this.indentChar;
2022 if ( this.HTML ) {
2023 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
2024 }
2025 return new Array( this.depth + ( extra || 0 ) ).join( chr );
2026 },
2027 up: function( a ) {
2028 this.depth += a || 1;
2029 },
2030 down: function( a ) {
2031 this.depth -= a || 1;
2032 },
2033 setParser: function( name, parser ) {
2034 this.parsers[ name ] = parser;
2035 },
2036 // The next 3 are exposed so you can use them
2037 quote: quote,
2038 literal: literal,
2039 join: join,
2040 //
2041 depth: 1,
2042 maxDepth: QUnit.config.maxDepth,
2043
2044 // This is the list of parsers, to modify them, use dump.setParser
2045 parsers: {
2046 window: "[Window]",
2047 document: "[Document]",
2048 error: function( error ) {
2049 return "Error(\"" + error.message + "\")";
2050 },
2051 unknown: "[Unknown]",
2052 "null": "null",
2053 "undefined": "undefined",
2054 "function": function( fn ) {
2055 var ret = "function",
2056
2057 // functions never have name in IE
2058 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
2059
2060 if ( name ) {
2061 ret += " " + name;
2062 }
2063 ret += "( ";
2064
2065 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
2066 return join( ret, dump.parse( fn, "functionCode" ), "}" );
2067 },
2068 array: array,
2069 nodelist: array,
2070 "arguments": array,
2071 object: function( map, stack ) {
2072 var keys, key, val, i, nonEnumerableProperties,
2073 ret = [];
2074
2075 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
2076 return "[object Object]";
2077 }
2078
2079 dump.up();
2080 keys = [];
2081 for ( key in map ) {
2082 keys.push( key );
2083 }
2084
2085 // Some properties are not always enumerable on Error objects.
2086 nonEnumerableProperties = [ "message", "name" ];
2087 for ( i in nonEnumerableProperties ) {
2088 key = nonEnumerableProperties[ i ];
2089 if ( key in map && inArray( key, keys ) < 0 ) {
2090 keys.push( key );
2091 }
2092 }
2093 keys.sort();
2094 for ( i = 0; i < keys.length; i++ ) {
2095 key = keys[ i ];
2096 val = map[ key ];
2097 ret.push( dump.parse( key, "key" ) + ": " +
2098 dump.parse( val, undefined, stack ) );
2099 }
2100 dump.down();
2101 return join( "{", ret, "}" );
2102 },
2103 node: function( node ) {
2104 var len, i, val,
2105 open = dump.HTML ? "&lt;" : "<",
2106 close = dump.HTML ? "&gt;" : ">",
2107 tag = node.nodeName.toLowerCase(),
2108 ret = open + tag,
2109 attrs = node.attributes;
2110
2111 if ( attrs ) {
2112 for ( i = 0, len = attrs.length; i < len; i++ ) {
2113 val = attrs[ i ].nodeValue;
2114
2115 // IE6 includes all attributes in .attributes, even ones not explicitly
2116 // set. Those have values like undefined, null, 0, false, "" or
2117 // "inherit".
2118 if ( val && val !== "inherit" ) {
2119 ret += " " + attrs[ i ].nodeName + "=" +
2120 dump.parse( val, "attribute" );
2121 }
2122 }
2123 }
2124 ret += close;
2125
2126 // Show content of TextNode or CDATASection
2127 if ( node.nodeType === 3 || node.nodeType === 4 ) {
2128 ret += node.nodeValue;
2129 }
2130
2131 return ret + open + "/" + tag + close;
2132 },
2133
2134 // function calls it internally, it's the arguments part of the function
2135 functionArgs: function( fn ) {
2136 var args,
2137 l = fn.length;
2138
2139 if ( !l ) {
2140 return "";
2141 }
2142
2143 args = new Array( l );
2144 while ( l-- ) {
2145
2146 // 97 is 'a'
2147 args[ l ] = String.fromCharCode( 97 + l );
2148 }
2149 return " " + args.join( ", " ) + " ";
2150 },
2151 // object calls it internally, the key part of an item in a map
2152 key: quote,
2153 // function calls it internally, it's the content of the function
2154 functionCode: "[code]",
2155 // node calls it internally, it's a html attribute value
2156 attribute: quote,
2157 string: quote,
2158 date: quote,
2159 regexp: literal,
2160 number: literal,
2161 "boolean": literal
2162 },
2163 // if true, entities are escaped ( <, >, \t, space and \n )
2164 HTML: false,
2165 // indentation unit
2166 indentChar: " ",
2167 // if true, items in a collection, are separated by a \n, else just a space.
2168 multiline: true
2169 };
2170
2171 return dump;
2172 }());
2173
2174 // back compat
2175 QUnit.jsDump = QUnit.dump;
2176
2177 // Deprecated
2178 // Extend assert methods to QUnit for Backwards compatibility
2179 (function() {
2180 var i,
2181 assertions = Assert.prototype;
2182
2183 function applyCurrent( current ) {
2184 return function() {
2185 var assert = new Assert( QUnit.config.current );
2186 current.apply( assert, arguments );
2187 };
2188 }
2189
2190 for ( i in assertions ) {
2191 QUnit[ i ] = applyCurrent( assertions[ i ] );
2192 }
2193 })();
2194
2195 // For browser, export only select globals
2196 if ( defined.document ) {
2197
2198 (function() {
2199 var i, l,
2200 keys = [
2201 "test",
2202 "module",
2203 "expect",
2204 "asyncTest",
2205 "start",
2206 "stop",
2207 "ok",
2208 "notOk",
2209 "equal",
2210 "notEqual",
2211 "propEqual",
2212 "notPropEqual",
2213 "deepEqual",
2214 "notDeepEqual",
2215 "strictEqual",
2216 "notStrictEqual",
2217 "throws",
2218 "raises"
2219 ];
2220
2221 for ( i = 0, l = keys.length; i < l; i++ ) {
2222 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
2223 }
2224 })();
2225
2226 window.QUnit = QUnit;
2227 }
2228
2229 // For nodejs
2230 if ( typeof module !== "undefined" && module && module.exports ) {
2231 module.exports = QUnit;
2232
2233 // For consistency with CommonJS environments' exports
2234 module.exports.QUnit = QUnit;
2235 }
2236
2237 // For CommonJS with exports, but without module.exports, like Rhino
2238 if ( typeof exports !== "undefined" && exports ) {
2239 exports.QUnit = QUnit;
2240 }
2241
2242 if ( typeof define === "function" && define.amd ) {
2243 define( function() {
2244 return QUnit;
2245 } );
2246 QUnit.config.autostart = false;
2247 }
2248
2249 /*
2250 * This file is a modified version of google-diff-match-patch's JavaScript implementation
2251 * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
2252 * modifications are licensed as more fully set forth in LICENSE.txt.
2253 *
2254 * The original source of google-diff-match-patch is attributable and licensed as follows:
2255 *
2256 * Copyright 2006 Google Inc.
2257 * https://code.google.com/p/google-diff-match-patch/
2258 *
2259 * Licensed under the Apache License, Version 2.0 (the "License");
2260 * you may not use this file except in compliance with the License.
2261 * You may obtain a copy of the License at
2262 *
2263 * https://www.apache.org/licenses/LICENSE-2.0
2264 *
2265 * Unless required by applicable law or agreed to in writing, software
2266 * distributed under the License is distributed on an "AS IS" BASIS,
2267 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2268 * See the License for the specific language governing permissions and
2269 * limitations under the License.
2270 *
2271 * More Info:
2272 * https://code.google.com/p/google-diff-match-patch/
2273 *
2274 * Usage: QUnit.diff(expected, actual)
2275 *
2276 */
2277 QUnit.diff = ( function() {
2278 function DiffMatchPatch() {
2279 }
2280
2281 // DIFF FUNCTIONS
2282
2283 /**
2284 * The data structure representing a diff is an array of tuples:
2285 * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
2286 * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
2287 */
2288 var DIFF_DELETE = -1,
2289 DIFF_INSERT = 1,
2290 DIFF_EQUAL = 0;
2291
2292 /**
2293 * Find the differences between two texts. Simplifies the problem by stripping
2294 * any common prefix or suffix off the texts before diffing.
2295 * @param {string} text1 Old string to be diffed.
2296 * @param {string} text2 New string to be diffed.
2297 * @param {boolean=} optChecklines Optional speedup flag. If present and false,
2298 * then don't run a line-level diff first to identify the changed areas.
2299 * Defaults to true, which does a faster, slightly less optimal diff.
2300 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2301 */
2302 DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
2303 var deadline, checklines, commonlength,
2304 commonprefix, commonsuffix, diffs;
2305
2306 // The diff must be complete in up to 1 second.
2307 deadline = ( new Date() ).getTime() + 1000;
2308
2309 // Check for null inputs.
2310 if ( text1 === null || text2 === null ) {
2311 throw new Error( "Null input. (DiffMain)" );
2312 }
2313
2314 // Check for equality (speedup).
2315 if ( text1 === text2 ) {
2316 if ( text1 ) {
2317 return [
2318 [ DIFF_EQUAL, text1 ]
2319 ];
2320 }
2321 return [];
2322 }
2323
2324 if ( typeof optChecklines === "undefined" ) {
2325 optChecklines = true;
2326 }
2327
2328 checklines = optChecklines;
2329
2330 // Trim off common prefix (speedup).
2331 commonlength = this.diffCommonPrefix( text1, text2 );
2332 commonprefix = text1.substring( 0, commonlength );
2333 text1 = text1.substring( commonlength );
2334 text2 = text2.substring( commonlength );
2335
2336 // Trim off common suffix (speedup).
2337 commonlength = this.diffCommonSuffix( text1, text2 );
2338 commonsuffix = text1.substring( text1.length - commonlength );
2339 text1 = text1.substring( 0, text1.length - commonlength );
2340 text2 = text2.substring( 0, text2.length - commonlength );
2341
2342 // Compute the diff on the middle block.
2343 diffs = this.diffCompute( text1, text2, checklines, deadline );
2344
2345 // Restore the prefix and suffix.
2346 if ( commonprefix ) {
2347 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2348 }
2349 if ( commonsuffix ) {
2350 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2351 }
2352 this.diffCleanupMerge( diffs );
2353 return diffs;
2354 };
2355
2356 /**
2357 * Reduce the number of edits by eliminating operationally trivial equalities.
2358 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2359 */
2360 DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
2361 var changes, equalities, equalitiesLength, lastequality,
2362 pointer, preIns, preDel, postIns, postDel;
2363 changes = false;
2364 equalities = []; // Stack of indices where equalities are found.
2365 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2366 /** @type {?string} */
2367 lastequality = null;
2368 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2369 pointer = 0; // Index of current position.
2370 // Is there an insertion operation before the last equality.
2371 preIns = false;
2372 // Is there a deletion operation before the last equality.
2373 preDel = false;
2374 // Is there an insertion operation after the last equality.
2375 postIns = false;
2376 // Is there a deletion operation after the last equality.
2377 postDel = false;
2378 while ( pointer < diffs.length ) {
2379
2380 // Equality found.
2381 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
2382 if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
2383
2384 // Candidate found.
2385 equalities[ equalitiesLength++ ] = pointer;
2386 preIns = postIns;
2387 preDel = postDel;
2388 lastequality = diffs[ pointer ][ 1 ];
2389 } else {
2390
2391 // Not a candidate, and can never become one.
2392 equalitiesLength = 0;
2393 lastequality = null;
2394 }
2395 postIns = postDel = false;
2396
2397 // An insertion or deletion.
2398 } else {
2399
2400 if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
2401 postDel = true;
2402 } else {
2403 postIns = true;
2404 }
2405
2406 /*
2407 * Five types to be split:
2408 * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
2409 * <ins>A</ins>X<ins>C</ins><del>D</del>
2410 * <ins>A</ins><del>B</del>X<ins>C</ins>
2411 * <ins>A</del>X<ins>C</ins><del>D</del>
2412 * <ins>A</ins><del>B</del>X<del>C</del>
2413 */
2414 if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
2415 ( ( lastequality.length < 2 ) &&
2416 ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
2417
2418 // Duplicate record.
2419 diffs.splice(
2420 equalities[ equalitiesLength - 1 ],
2421 0,
2422 [ DIFF_DELETE, lastequality ]
2423 );
2424
2425 // Change second copy to insert.
2426 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2427 equalitiesLength--; // Throw away the equality we just deleted;
2428 lastequality = null;
2429 if ( preIns && preDel ) {
2430 // No changes made which could affect previous entry, keep going.
2431 postIns = postDel = true;
2432 equalitiesLength = 0;
2433 } else {
2434 equalitiesLength--; // Throw away the previous equality.
2435 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
2436 postIns = postDel = false;
2437 }
2438 changes = true;
2439 }
2440 }
2441 pointer++;
2442 }
2443
2444 if ( changes ) {
2445 this.diffCleanupMerge( diffs );
2446 }
2447 };
2448
2449 /**
2450 * Convert a diff array into a pretty HTML report.
2451 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2452 * @param {integer} string to be beautified.
2453 * @return {string} HTML representation.
2454 */
2455 DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
2456 var op, data, x,
2457 html = [];
2458 for ( x = 0; x < diffs.length; x++ ) {
2459 op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
2460 data = diffs[ x ][ 1 ]; // Text of change.
2461 switch ( op ) {
2462 case DIFF_INSERT:
2463 html[ x ] = "<ins>" + data + "</ins>";
2464 break;
2465 case DIFF_DELETE:
2466 html[ x ] = "<del>" + data + "</del>";
2467 break;
2468 case DIFF_EQUAL:
2469 html[ x ] = "<span>" + data + "</span>";
2470 break;
2471 }
2472 }
2473 return html.join( "" );
2474 };
2475
2476 /**
2477 * Determine the common prefix of two strings.
2478 * @param {string} text1 First string.
2479 * @param {string} text2 Second string.
2480 * @return {number} The number of characters common to the start of each
2481 * string.
2482 */
2483 DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
2484 var pointermid, pointermax, pointermin, pointerstart;
2485 // Quick check for common null cases.
2486 if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
2487 return 0;
2488 }
2489 // Binary search.
2490 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2491 pointermin = 0;
2492 pointermax = Math.min( text1.length, text2.length );
2493 pointermid = pointermax;
2494 pointerstart = 0;
2495 while ( pointermin < pointermid ) {
2496 if ( text1.substring( pointerstart, pointermid ) ===
2497 text2.substring( pointerstart, pointermid ) ) {
2498 pointermin = pointermid;
2499 pointerstart = pointermin;
2500 } else {
2501 pointermax = pointermid;
2502 }
2503 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2504 }
2505 return pointermid;
2506 };
2507
2508 /**
2509 * Determine the common suffix of two strings.
2510 * @param {string} text1 First string.
2511 * @param {string} text2 Second string.
2512 * @return {number} The number of characters common to the end of each string.
2513 */
2514 DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
2515 var pointermid, pointermax, pointermin, pointerend;
2516 // Quick check for common null cases.
2517 if ( !text1 ||
2518 !text2 ||
2519 text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
2520 return 0;
2521 }
2522 // Binary search.
2523 // Performance analysis: https://neil.fraser.name/news/2007/10/09/
2524 pointermin = 0;
2525 pointermax = Math.min( text1.length, text2.length );
2526 pointermid = pointermax;
2527 pointerend = 0;
2528 while ( pointermin < pointermid ) {
2529 if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
2530 text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
2531 pointermin = pointermid;
2532 pointerend = pointermin;
2533 } else {
2534 pointermax = pointermid;
2535 }
2536 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2537 }
2538 return pointermid;
2539 };
2540
2541 /**
2542 * Find the differences between two texts. Assumes that the texts do not
2543 * have any common prefix or suffix.
2544 * @param {string} text1 Old string to be diffed.
2545 * @param {string} text2 New string to be diffed.
2546 * @param {boolean} checklines Speedup flag. If false, then don't run a
2547 * line-level diff first to identify the changed areas.
2548 * If true, then run a faster, slightly less optimal diff.
2549 * @param {number} deadline Time when the diff should be complete by.
2550 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2551 * @private
2552 */
2553 DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
2554 var diffs, longtext, shorttext, i, hm,
2555 text1A, text2A, text1B, text2B,
2556 midCommon, diffsA, diffsB;
2557
2558 if ( !text1 ) {
2559 // Just add some text (speedup).
2560 return [
2561 [ DIFF_INSERT, text2 ]
2562 ];
2563 }
2564
2565 if ( !text2 ) {
2566 // Just delete some text (speedup).
2567 return [
2568 [ DIFF_DELETE, text1 ]
2569 ];
2570 }
2571
2572 longtext = text1.length > text2.length ? text1 : text2;
2573 shorttext = text1.length > text2.length ? text2 : text1;
2574 i = longtext.indexOf( shorttext );
2575 if ( i !== -1 ) {
2576 // Shorter text is inside the longer text (speedup).
2577 diffs = [
2578 [ DIFF_INSERT, longtext.substring( 0, i ) ],
2579 [ DIFF_EQUAL, shorttext ],
2580 [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
2581 ];
2582 // Swap insertions for deletions if diff is reversed.
2583 if ( text1.length > text2.length ) {
2584 diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
2585 }
2586 return diffs;
2587 }
2588
2589 if ( shorttext.length === 1 ) {
2590 // Single character string.
2591 // After the previous speedup, the character can't be an equality.
2592 return [
2593 [ DIFF_DELETE, text1 ],
2594 [ DIFF_INSERT, text2 ]
2595 ];
2596 }
2597
2598 // Check to see if the problem can be split in two.
2599 hm = this.diffHalfMatch( text1, text2 );
2600 if ( hm ) {
2601 // A half-match was found, sort out the return data.
2602 text1A = hm[ 0 ];
2603 text1B = hm[ 1 ];
2604 text2A = hm[ 2 ];
2605 text2B = hm[ 3 ];
2606 midCommon = hm[ 4 ];
2607 // Send both pairs off for separate processing.
2608 diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
2609 diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
2610 // Merge the results.
2611 return diffsA.concat( [
2612 [ DIFF_EQUAL, midCommon ]
2613 ], diffsB );
2614 }
2615
2616 if ( checklines && text1.length > 100 && text2.length > 100 ) {
2617 return this.diffLineMode( text1, text2, deadline );
2618 }
2619
2620 return this.diffBisect( text1, text2, deadline );
2621 };
2622
2623 /**
2624 * Do the two texts share a substring which is at least half the length of the
2625 * longer text?
2626 * This speedup can produce non-minimal diffs.
2627 * @param {string} text1 First string.
2628 * @param {string} text2 Second string.
2629 * @return {Array.<string>} Five element Array, containing the prefix of
2630 * text1, the suffix of text1, the prefix of text2, the suffix of
2631 * text2 and the common middle. Or null if there was no match.
2632 * @private
2633 */
2634 DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
2635 var longtext, shorttext, dmp,
2636 text1A, text2B, text2A, text1B, midCommon,
2637 hm1, hm2, hm;
2638
2639 longtext = text1.length > text2.length ? text1 : text2;
2640 shorttext = text1.length > text2.length ? text2 : text1;
2641 if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
2642 return null; // Pointless.
2643 }
2644 dmp = this; // 'this' becomes 'window' in a closure.
2645
2646 /**
2647 * Does a substring of shorttext exist within longtext such that the substring
2648 * is at least half the length of longtext?
2649 * Closure, but does not reference any external variables.
2650 * @param {string} longtext Longer string.
2651 * @param {string} shorttext Shorter string.
2652 * @param {number} i Start index of quarter length substring within longtext.
2653 * @return {Array.<string>} Five element Array, containing the prefix of
2654 * longtext, the suffix of longtext, the prefix of shorttext, the suffix
2655 * of shorttext and the common middle. Or null if there was no match.
2656 * @private
2657 */
2658 function diffHalfMatchI( longtext, shorttext, i ) {
2659 var seed, j, bestCommon, prefixLength, suffixLength,
2660 bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
2661 // Start with a 1/4 length substring at position i as a seed.
2662 seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
2663 j = -1;
2664 bestCommon = "";
2665 while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
2666 prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
2667 shorttext.substring( j ) );
2668 suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
2669 shorttext.substring( 0, j ) );
2670 if ( bestCommon.length < suffixLength + prefixLength ) {
2671 bestCommon = shorttext.substring( j - suffixLength, j ) +
2672 shorttext.substring( j, j + prefixLength );
2673 bestLongtextA = longtext.substring( 0, i - suffixLength );
2674 bestLongtextB = longtext.substring( i + prefixLength );
2675 bestShorttextA = shorttext.substring( 0, j - suffixLength );
2676 bestShorttextB = shorttext.substring( j + prefixLength );
2677 }
2678 }
2679 if ( bestCommon.length * 2 >= longtext.length ) {
2680 return [ bestLongtextA, bestLongtextB,
2681 bestShorttextA, bestShorttextB, bestCommon
2682 ];
2683 } else {
2684 return null;
2685 }
2686 }
2687
2688 // First check if the second quarter is the seed for a half-match.
2689 hm1 = diffHalfMatchI( longtext, shorttext,
2690 Math.ceil( longtext.length / 4 ) );
2691 // Check again based on the third quarter.
2692 hm2 = diffHalfMatchI( longtext, shorttext,
2693 Math.ceil( longtext.length / 2 ) );
2694 if ( !hm1 && !hm2 ) {
2695 return null;
2696 } else if ( !hm2 ) {
2697 hm = hm1;
2698 } else if ( !hm1 ) {
2699 hm = hm2;
2700 } else {
2701 // Both matched. Select the longest.
2702 hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
2703 }
2704
2705 // A half-match was found, sort out the return data.
2706 text1A, text1B, text2A, text2B;
2707 if ( text1.length > text2.length ) {
2708 text1A = hm[ 0 ];
2709 text1B = hm[ 1 ];
2710 text2A = hm[ 2 ];
2711 text2B = hm[ 3 ];
2712 } else {
2713 text2A = hm[ 0 ];
2714 text2B = hm[ 1 ];
2715 text1A = hm[ 2 ];
2716 text1B = hm[ 3 ];
2717 }
2718 midCommon = hm[ 4 ];
2719 return [ text1A, text1B, text2A, text2B, midCommon ];
2720 };
2721
2722 /**
2723 * Do a quick line-level diff on both strings, then rediff the parts for
2724 * greater accuracy.
2725 * This speedup can produce non-minimal diffs.
2726 * @param {string} text1 Old string to be diffed.
2727 * @param {string} text2 New string to be diffed.
2728 * @param {number} deadline Time when the diff should be complete by.
2729 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2730 * @private
2731 */
2732 DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
2733 var a, diffs, linearray, pointer, countInsert,
2734 countDelete, textInsert, textDelete, j;
2735 // Scan the text on a line-by-line basis first.
2736 a = this.diffLinesToChars( text1, text2 );
2737 text1 = a.chars1;
2738 text2 = a.chars2;
2739 linearray = a.lineArray;
2740
2741 diffs = this.DiffMain( text1, text2, false, deadline );
2742
2743 // Convert the diff back to original text.
2744 this.diffCharsToLines( diffs, linearray );
2745 // Eliminate freak matches (e.g. blank lines)
2746 this.diffCleanupSemantic( diffs );
2747
2748 // Rediff any replacement blocks, this time character-by-character.
2749 // Add a dummy entry at the end.
2750 diffs.push( [ DIFF_EQUAL, "" ] );
2751 pointer = 0;
2752 countDelete = 0;
2753 countInsert = 0;
2754 textDelete = "";
2755 textInsert = "";
2756 while ( pointer < diffs.length ) {
2757 switch ( diffs[ pointer ][ 0 ] ) {
2758 case DIFF_INSERT:
2759 countInsert++;
2760 textInsert += diffs[ pointer ][ 1 ];
2761 break;
2762 case DIFF_DELETE:
2763 countDelete++;
2764 textDelete += diffs[ pointer ][ 1 ];
2765 break;
2766 case DIFF_EQUAL:
2767 // Upon reaching an equality, check for prior redundancies.
2768 if ( countDelete >= 1 && countInsert >= 1 ) {
2769 // Delete the offending records and add the merged ones.
2770 diffs.splice( pointer - countDelete - countInsert,
2771 countDelete + countInsert );
2772 pointer = pointer - countDelete - countInsert;
2773 a = this.DiffMain( textDelete, textInsert, false, deadline );
2774 for ( j = a.length - 1; j >= 0; j-- ) {
2775 diffs.splice( pointer, 0, a[ j ] );
2776 }
2777 pointer = pointer + a.length;
2778 }
2779 countInsert = 0;
2780 countDelete = 0;
2781 textDelete = "";
2782 textInsert = "";
2783 break;
2784 }
2785 pointer++;
2786 }
2787 diffs.pop(); // Remove the dummy entry at the end.
2788
2789 return diffs;
2790 };
2791
2792 /**
2793 * Find the 'middle snake' of a diff, split the problem in two
2794 * and return the recursively constructed diff.
2795 * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
2796 * @param {string} text1 Old string to be diffed.
2797 * @param {string} text2 New string to be diffed.
2798 * @param {number} deadline Time at which to bail if not yet complete.
2799 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2800 * @private
2801 */
2802 DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
2803 var text1Length, text2Length, maxD, vOffset, vLength,
2804 v1, v2, x, delta, front, k1start, k1end, k2start,
2805 k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
2806 // Cache the text lengths to prevent multiple calls.
2807 text1Length = text1.length;
2808 text2Length = text2.length;
2809 maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
2810 vOffset = maxD;
2811 vLength = 2 * maxD;
2812 v1 = new Array( vLength );
2813 v2 = new Array( vLength );
2814 // Setting all elements to -1 is faster in Chrome & Firefox than mixing
2815 // integers and undefined.
2816 for ( x = 0; x < vLength; x++ ) {
2817 v1[ x ] = -1;
2818 v2[ x ] = -1;
2819 }
2820 v1[ vOffset + 1 ] = 0;
2821 v2[ vOffset + 1 ] = 0;
2822 delta = text1Length - text2Length;
2823 // If the total number of characters is odd, then the front path will collide
2824 // with the reverse path.
2825 front = ( delta % 2 !== 0 );
2826 // Offsets for start and end of k loop.
2827 // Prevents mapping of space beyond the grid.
2828 k1start = 0;
2829 k1end = 0;
2830 k2start = 0;
2831 k2end = 0;
2832 for ( d = 0; d < maxD; d++ ) {
2833 // Bail out if deadline is reached.
2834 if ( ( new Date() ).getTime() > deadline ) {
2835 break;
2836 }
2837
2838 // Walk the front path one step.
2839 for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
2840 k1Offset = vOffset + k1;
2841 if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
2842 x1 = v1[ k1Offset + 1 ];
2843 } else {
2844 x1 = v1[ k1Offset - 1 ] + 1;
2845 }
2846 y1 = x1 - k1;
2847 while ( x1 < text1Length && y1 < text2Length &&
2848 text1.charAt( x1 ) === text2.charAt( y1 ) ) {
2849 x1++;
2850 y1++;
2851 }
2852 v1[ k1Offset ] = x1;
2853 if ( x1 > text1Length ) {
2854 // Ran off the right of the graph.
2855 k1end += 2;
2856 } else if ( y1 > text2Length ) {
2857 // Ran off the bottom of the graph.
2858 k1start += 2;
2859 } else if ( front ) {
2860 k2Offset = vOffset + delta - k1;
2861 if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
2862 // Mirror x2 onto top-left coordinate system.
2863 x2 = text1Length - v2[ k2Offset ];
2864 if ( x1 >= x2 ) {
2865 // Overlap detected.
2866 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2867 }
2868 }
2869 }
2870 }
2871
2872 // Walk the reverse path one step.
2873 for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
2874 k2Offset = vOffset + k2;
2875 if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
2876 x2 = v2[ k2Offset + 1 ];
2877 } else {
2878 x2 = v2[ k2Offset - 1 ] + 1;
2879 }
2880 y2 = x2 - k2;
2881 while ( x2 < text1Length && y2 < text2Length &&
2882 text1.charAt( text1Length - x2 - 1 ) ===
2883 text2.charAt( text2Length - y2 - 1 ) ) {
2884 x2++;
2885 y2++;
2886 }
2887 v2[ k2Offset ] = x2;
2888 if ( x2 > text1Length ) {
2889 // Ran off the left of the graph.
2890 k2end += 2;
2891 } else if ( y2 > text2Length ) {
2892 // Ran off the top of the graph.
2893 k2start += 2;
2894 } else if ( !front ) {
2895 k1Offset = vOffset + delta - k2;
2896 if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
2897 x1 = v1[ k1Offset ];
2898 y1 = vOffset + x1 - k1Offset;
2899 // Mirror x2 onto top-left coordinate system.
2900 x2 = text1Length - x2;
2901 if ( x1 >= x2 ) {
2902 // Overlap detected.
2903 return this.diffBisectSplit( text1, text2, x1, y1, deadline );
2904 }
2905 }
2906 }
2907 }
2908 }
2909 // Diff took too long and hit the deadline or
2910 // number of diffs equals number of characters, no commonality at all.
2911 return [
2912 [ DIFF_DELETE, text1 ],
2913 [ DIFF_INSERT, text2 ]
2914 ];
2915 };
2916
2917 /**
2918 * Given the location of the 'middle snake', split the diff in two parts
2919 * and recurse.
2920 * @param {string} text1 Old string to be diffed.
2921 * @param {string} text2 New string to be diffed.
2922 * @param {number} x Index of split point in text1.
2923 * @param {number} y Index of split point in text2.
2924 * @param {number} deadline Time at which to bail if not yet complete.
2925 * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
2926 * @private
2927 */
2928 DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
2929 var text1a, text1b, text2a, text2b, diffs, diffsb;
2930 text1a = text1.substring( 0, x );
2931 text2a = text2.substring( 0, y );
2932 text1b = text1.substring( x );
2933 text2b = text2.substring( y );
2934
2935 // Compute both diffs serially.
2936 diffs = this.DiffMain( text1a, text2a, false, deadline );
2937 diffsb = this.DiffMain( text1b, text2b, false, deadline );
2938
2939 return diffs.concat( diffsb );
2940 };
2941
2942 /**
2943 * Reduce the number of edits by eliminating semantically trivial equalities.
2944 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
2945 */
2946 DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
2947 var changes, equalities, equalitiesLength, lastequality,
2948 pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
2949 lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
2950 changes = false;
2951 equalities = []; // Stack of indices where equalities are found.
2952 equalitiesLength = 0; // Keeping our own length var is faster in JS.
2953 /** @type {?string} */
2954 lastequality = null;
2955 // Always equal to diffs[equalities[equalitiesLength - 1]][1]
2956 pointer = 0; // Index of current position.
2957 // Number of characters that changed prior to the equality.
2958 lengthInsertions1 = 0;
2959 lengthDeletions1 = 0;
2960 // Number of characters that changed after the equality.
2961 lengthInsertions2 = 0;
2962 lengthDeletions2 = 0;
2963 while ( pointer < diffs.length ) {
2964 if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
2965 equalities[ equalitiesLength++ ] = pointer;
2966 lengthInsertions1 = lengthInsertions2;
2967 lengthDeletions1 = lengthDeletions2;
2968 lengthInsertions2 = 0;
2969 lengthDeletions2 = 0;
2970 lastequality = diffs[ pointer ][ 1 ];
2971 } else { // An insertion or deletion.
2972 if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
2973 lengthInsertions2 += diffs[ pointer ][ 1 ].length;
2974 } else {
2975 lengthDeletions2 += diffs[ pointer ][ 1 ].length;
2976 }
2977 // Eliminate an equality that is smaller or equal to the edits on both
2978 // sides of it.
2979 if ( lastequality && ( lastequality.length <=
2980 Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
2981 ( lastequality.length <= Math.max( lengthInsertions2,
2982 lengthDeletions2 ) ) ) {
2983
2984 // Duplicate record.
2985 diffs.splice(
2986 equalities[ equalitiesLength - 1 ],
2987 0,
2988 [ DIFF_DELETE, lastequality ]
2989 );
2990
2991 // Change second copy to insert.
2992 diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
2993
2994 // Throw away the equality we just deleted.
2995 equalitiesLength--;
2996
2997 // Throw away the previous equality (it needs to be reevaluated).
2998 equalitiesLength--;
2999 pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
3000
3001 // Reset the counters.
3002 lengthInsertions1 = 0;
3003 lengthDeletions1 = 0;
3004 lengthInsertions2 = 0;
3005 lengthDeletions2 = 0;
3006 lastequality = null;
3007 changes = true;
3008 }
3009 }
3010 pointer++;
3011 }
3012
3013 // Normalize the diff.
3014 if ( changes ) {
3015 this.diffCleanupMerge( diffs );
3016 }
3017
3018 // Find any overlaps between deletions and insertions.
3019 // e.g: <del>abcxxx</del><ins>xxxdef</ins>
3020 // -> <del>abc</del>xxx<ins>def</ins>
3021 // e.g: <del>xxxabc</del><ins>defxxx</ins>
3022 // -> <ins>def</ins>xxx<del>abc</del>
3023 // Only extract an overlap if it is as big as the edit ahead or behind it.
3024 pointer = 1;
3025 while ( pointer < diffs.length ) {
3026 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
3027 diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
3028 deletion = diffs[ pointer - 1 ][ 1 ];
3029 insertion = diffs[ pointer ][ 1 ];
3030 overlapLength1 = this.diffCommonOverlap( deletion, insertion );
3031 overlapLength2 = this.diffCommonOverlap( insertion, deletion );
3032 if ( overlapLength1 >= overlapLength2 ) {
3033 if ( overlapLength1 >= deletion.length / 2 ||
3034 overlapLength1 >= insertion.length / 2 ) {
3035 // Overlap found. Insert an equality and trim the surrounding edits.
3036 diffs.splice(
3037 pointer,
3038 0,
3039 [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
3040 );
3041 diffs[ pointer - 1 ][ 1 ] =
3042 deletion.substring( 0, deletion.length - overlapLength1 );
3043 diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
3044 pointer++;
3045 }
3046 } else {
3047 if ( overlapLength2 >= deletion.length / 2 ||
3048 overlapLength2 >= insertion.length / 2 ) {
3049
3050 // Reverse overlap found.
3051 // Insert an equality and swap and trim the surrounding edits.
3052 diffs.splice(
3053 pointer,
3054 0,
3055 [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
3056 );
3057
3058 diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
3059 diffs[ pointer - 1 ][ 1 ] =
3060 insertion.substring( 0, insertion.length - overlapLength2 );
3061 diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
3062 diffs[ pointer + 1 ][ 1 ] =
3063 deletion.substring( overlapLength2 );
3064 pointer++;
3065 }
3066 }
3067 pointer++;
3068 }
3069 pointer++;
3070 }
3071 };
3072
3073 /**
3074 * Determine if the suffix of one string is the prefix of another.
3075 * @param {string} text1 First string.
3076 * @param {string} text2 Second string.
3077 * @return {number} The number of characters common to the end of the first
3078 * string and the start of the second string.
3079 * @private
3080 */
3081 DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
3082 var text1Length, text2Length, textLength,
3083 best, length, pattern, found;
3084 // Cache the text lengths to prevent multiple calls.
3085 text1Length = text1.length;
3086 text2Length = text2.length;
3087 // Eliminate the null case.
3088 if ( text1Length === 0 || text2Length === 0 ) {
3089 return 0;
3090 }
3091 // Truncate the longer string.
3092 if ( text1Length > text2Length ) {
3093 text1 = text1.substring( text1Length - text2Length );
3094 } else if ( text1Length < text2Length ) {
3095 text2 = text2.substring( 0, text1Length );
3096 }
3097 textLength = Math.min( text1Length, text2Length );
3098 // Quick check for the worst case.
3099 if ( text1 === text2 ) {
3100 return textLength;
3101 }
3102
3103 // Start by looking for a single character match
3104 // and increase length until no match is found.
3105 // Performance analysis: https://neil.fraser.name/news/2010/11/04/
3106 best = 0;
3107 length = 1;
3108 while ( true ) {
3109 pattern = text1.substring( textLength - length );
3110 found = text2.indexOf( pattern );
3111 if ( found === -1 ) {
3112 return best;
3113 }
3114 length += found;
3115 if ( found === 0 || text1.substring( textLength - length ) ===
3116 text2.substring( 0, length ) ) {
3117 best = length;
3118 length++;
3119 }
3120 }
3121 };
3122
3123 /**
3124 * Split two texts into an array of strings. Reduce the texts to a string of
3125 * hashes where each Unicode character represents one line.
3126 * @param {string} text1 First string.
3127 * @param {string} text2 Second string.
3128 * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
3129 * An object containing the encoded text1, the encoded text2 and
3130 * the array of unique strings.
3131 * The zeroth element of the array of unique strings is intentionally blank.
3132 * @private
3133 */
3134 DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
3135 var lineArray, lineHash, chars1, chars2;
3136 lineArray = []; // e.g. lineArray[4] === 'Hello\n'
3137 lineHash = {}; // e.g. lineHash['Hello\n'] === 4
3138
3139 // '\x00' is a valid character, but various debuggers don't like it.
3140 // So we'll insert a junk entry to avoid generating a null character.
3141 lineArray[ 0 ] = "";
3142
3143 /**
3144 * Split a text into an array of strings. Reduce the texts to a string of
3145 * hashes where each Unicode character represents one line.
3146 * Modifies linearray and linehash through being a closure.
3147 * @param {string} text String to encode.
3148 * @return {string} Encoded string.
3149 * @private
3150 */
3151 function diffLinesToCharsMunge( text ) {
3152 var chars, lineStart, lineEnd, lineArrayLength, line;
3153 chars = "";
3154 // Walk the text, pulling out a substring for each line.
3155 // text.split('\n') would would temporarily double our memory footprint.
3156 // Modifying text would create many large strings to garbage collect.
3157 lineStart = 0;
3158 lineEnd = -1;
3159 // Keeping our own length variable is faster than looking it up.
3160 lineArrayLength = lineArray.length;
3161 while ( lineEnd < text.length - 1 ) {
3162 lineEnd = text.indexOf( "\n", lineStart );
3163 if ( lineEnd === -1 ) {
3164 lineEnd = text.length - 1;
3165 }
3166 line = text.substring( lineStart, lineEnd + 1 );
3167 lineStart = lineEnd + 1;
3168
3169 if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
3170 ( lineHash[ line ] !== undefined ) ) {
3171 chars += String.fromCharCode( lineHash[ line ] );
3172 } else {
3173 chars += String.fromCharCode( lineArrayLength );
3174 lineHash[ line ] = lineArrayLength;
3175 lineArray[ lineArrayLength++ ] = line;
3176 }
3177 }
3178 return chars;
3179 }
3180
3181 chars1 = diffLinesToCharsMunge( text1 );
3182 chars2 = diffLinesToCharsMunge( text2 );
3183 return {
3184 chars1: chars1,
3185 chars2: chars2,
3186 lineArray: lineArray
3187 };
3188 };
3189
3190 /**
3191 * Rehydrate the text in a diff from a string of line hashes to real lines of
3192 * text.
3193 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3194 * @param {!Array.<string>} lineArray Array of unique strings.
3195 * @private
3196 */
3197 DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
3198 var x, chars, text, y;
3199 for ( x = 0; x < diffs.length; x++ ) {
3200 chars = diffs[ x ][ 1 ];
3201 text = [];
3202 for ( y = 0; y < chars.length; y++ ) {
3203 text[ y ] = lineArray[ chars.charCodeAt( y ) ];
3204 }
3205 diffs[ x ][ 1 ] = text.join( "" );
3206 }
3207 };
3208
3209 /**
3210 * Reorder and merge like edit sections. Merge equalities.
3211 * Any edit section can move as long as it doesn't cross an equality.
3212 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
3213 */
3214 DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
3215 var pointer, countDelete, countInsert, textInsert, textDelete,
3216 commonlength, changes, diffPointer, position;
3217 diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
3218 pointer = 0;
3219 countDelete = 0;
3220 countInsert = 0;
3221 textDelete = "";
3222 textInsert = "";
3223 commonlength;
3224 while ( pointer < diffs.length ) {
3225 switch ( diffs[ pointer ][ 0 ] ) {
3226 case DIFF_INSERT:
3227 countInsert++;
3228 textInsert += diffs[ pointer ][ 1 ];
3229 pointer++;
3230 break;
3231 case DIFF_DELETE:
3232 countDelete++;
3233 textDelete += diffs[ pointer ][ 1 ];
3234 pointer++;
3235 break;
3236 case DIFF_EQUAL:
3237 // Upon reaching an equality, check for prior redundancies.
3238 if ( countDelete + countInsert > 1 ) {
3239 if ( countDelete !== 0 && countInsert !== 0 ) {
3240 // Factor out any common prefixes.
3241 commonlength = this.diffCommonPrefix( textInsert, textDelete );
3242 if ( commonlength !== 0 ) {
3243 if ( ( pointer - countDelete - countInsert ) > 0 &&
3244 diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
3245 DIFF_EQUAL ) {
3246 diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
3247 textInsert.substring( 0, commonlength );
3248 } else {
3249 diffs.splice( 0, 0, [ DIFF_EQUAL,
3250 textInsert.substring( 0, commonlength )
3251 ] );
3252 pointer++;
3253 }
3254 textInsert = textInsert.substring( commonlength );
3255 textDelete = textDelete.substring( commonlength );
3256 }
3257 // Factor out any common suffixies.
3258 commonlength = this.diffCommonSuffix( textInsert, textDelete );
3259 if ( commonlength !== 0 ) {
3260 diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
3261 commonlength ) + diffs[ pointer ][ 1 ];
3262 textInsert = textInsert.substring( 0, textInsert.length -
3263 commonlength );
3264 textDelete = textDelete.substring( 0, textDelete.length -
3265 commonlength );
3266 }
3267 }
3268 // Delete the offending records and add the merged ones.
3269 if ( countDelete === 0 ) {
3270 diffs.splice( pointer - countInsert,
3271 countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
3272 } else if ( countInsert === 0 ) {
3273 diffs.splice( pointer - countDelete,
3274 countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
3275 } else {
3276 diffs.splice(
3277 pointer - countDelete - countInsert,
3278 countDelete + countInsert,
3279 [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
3280 );
3281 }
3282 pointer = pointer - countDelete - countInsert +
3283 ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
3284 } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
3285
3286 // Merge this equality with the previous one.
3287 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
3288 diffs.splice( pointer, 1 );
3289 } else {
3290 pointer++;
3291 }
3292 countInsert = 0;
3293 countDelete = 0;
3294 textDelete = "";
3295 textInsert = "";
3296 break;
3297 }
3298 }
3299 if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
3300 diffs.pop(); // Remove the dummy entry at the end.
3301 }
3302
3303 // Second pass: look for single edits surrounded on both sides by equalities
3304 // which can be shifted sideways to eliminate an equality.
3305 // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
3306 changes = false;
3307 pointer = 1;
3308
3309 // Intentionally ignore the first and last element (don't need checking).
3310 while ( pointer < diffs.length - 1 ) {
3311 if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
3312 diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
3313
3314 diffPointer = diffs[ pointer ][ 1 ];
3315 position = diffPointer.substring(
3316 diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
3317 );
3318
3319 // This is a single edit surrounded by equalities.
3320 if ( position === diffs[ pointer - 1 ][ 1 ] ) {
3321
3322 // Shift the edit over the previous equality.
3323 diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
3324 diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
3325 diffs[ pointer - 1 ][ 1 ].length );
3326 diffs[ pointer + 1 ][ 1 ] =
3327 diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
3328 diffs.splice( pointer - 1, 1 );
3329 changes = true;
3330 } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
3331 diffs[ pointer + 1 ][ 1 ] ) {
3332
3333 // Shift the edit over the next equality.
3334 diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
3335 diffs[ pointer ][ 1 ] =
3336 diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
3337 diffs[ pointer + 1 ][ 1 ];
3338 diffs.splice( pointer + 1, 1 );
3339 changes = true;
3340 }
3341 }
3342 pointer++;
3343 }
3344 // If shifts were made, the diff needs reordering and another shift sweep.
3345 if ( changes ) {
3346 this.diffCleanupMerge( diffs );
3347 }
3348 };
3349
3350 return function( o, n ) {
3351 var diff, output, text;
3352 diff = new DiffMatchPatch();
3353 output = diff.DiffMain( o, n );
3354 diff.diffCleanupEfficiency( output );
3355 text = diff.diffPrettyHtml( output );
3356
3357 return text;
3358 };
3359 }() );
3360
3361 // Get a reference to the global object, like window in browsers
3362 }( (function() {
3363 return this;
3364 })() ));
3365
3366 (function() {
3367
3368 // Don't load the HTML Reporter on non-Browser environments
3369 if ( typeof window === "undefined" || !window.document ) {
3370 return;
3371 }
3372
3373 // Deprecated QUnit.init - Ref #530
3374 // Re-initialize the configuration options
3375 QUnit.init = function() {
3376 var tests, banner, result, qunit,
3377 config = QUnit.config;
3378
3379 config.stats = { all: 0, bad: 0 };
3380 config.moduleStats = { all: 0, bad: 0 };
3381 config.started = 0;
3382 config.updateRate = 1000;
3383 config.blocking = false;
3384 config.autostart = true;
3385 config.autorun = false;
3386 config.filter = "";
3387 config.queue = [];
3388
3389 // Return on non-browser environments
3390 // This is necessary to not break on node tests
3391 if ( typeof window === "undefined" ) {
3392 return;
3393 }
3394
3395 qunit = id( "qunit" );
3396 if ( qunit ) {
3397 qunit.innerHTML =
3398 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3399 "<h2 id='qunit-banner'></h2>" +
3400 "<div id='qunit-testrunner-toolbar'></div>" +
3401 "<h2 id='qunit-userAgent'></h2>" +
3402 "<ol id='qunit-tests'></ol>";
3403 }
3404
3405 tests = id( "qunit-tests" );
3406 banner = id( "qunit-banner" );
3407 result = id( "qunit-testresult" );
3408
3409 if ( tests ) {
3410 tests.innerHTML = "";
3411 }
3412
3413 if ( banner ) {
3414 banner.className = "";
3415 }
3416
3417 if ( result ) {
3418 result.parentNode.removeChild( result );
3419 }
3420
3421 if ( tests ) {
3422 result = document.createElement( "p" );
3423 result.id = "qunit-testresult";
3424 result.className = "result";
3425 tests.parentNode.insertBefore( result, tests );
3426 result.innerHTML = "Running...<br />&#160;";
3427 }
3428 };
3429
3430 var config = QUnit.config,
3431 collapseNext = false,
3432 hasOwn = Object.prototype.hasOwnProperty,
3433 defined = {
3434 document: window.document !== undefined,
3435 sessionStorage: (function() {
3436 var x = "qunit-test-string";
3437 try {
3438 sessionStorage.setItem( x, x );
3439 sessionStorage.removeItem( x );
3440 return true;
3441 } catch ( e ) {
3442 return false;
3443 }
3444 }())
3445 },
3446 modulesList = [];
3447
3448 /**
3449 * Escape text for attribute or text content.
3450 */
3451 function escapeText( s ) {
3452 if ( !s ) {
3453 return "";
3454 }
3455 s = s + "";
3456
3457 // Both single quotes and double quotes (for attributes)
3458 return s.replace( /['"<>&]/g, function( s ) {
3459 switch ( s ) {
3460 case "'":
3461 return "&#039;";
3462 case "\"":
3463 return "&quot;";
3464 case "<":
3465 return "&lt;";
3466 case ">":
3467 return "&gt;";
3468 case "&":
3469 return "&amp;";
3470 }
3471 });
3472 }
3473
3474 /**
3475 * @param {HTMLElement} elem
3476 * @param {string} type
3477 * @param {Function} fn
3478 */
3479 function addEvent( elem, type, fn ) {
3480 if ( elem.addEventListener ) {
3481
3482 // Standards-based browsers
3483 elem.addEventListener( type, fn, false );
3484 } else if ( elem.attachEvent ) {
3485
3486 // support: IE <9
3487 elem.attachEvent( "on" + type, function() {
3488 var event = window.event;
3489 if ( !event.target ) {
3490 event.target = event.srcElement || document;
3491 }
3492
3493 fn.call( elem, event );
3494 });
3495 }
3496 }
3497
3498 /**
3499 * @param {Array|NodeList} elems
3500 * @param {string} type
3501 * @param {Function} fn
3502 */
3503 function addEvents( elems, type, fn ) {
3504 var i = elems.length;
3505 while ( i-- ) {
3506 addEvent( elems[ i ], type, fn );
3507 }
3508 }
3509
3510 function hasClass( elem, name ) {
3511 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
3512 }
3513
3514 function addClass( elem, name ) {
3515 if ( !hasClass( elem, name ) ) {
3516 elem.className += ( elem.className ? " " : "" ) + name;
3517 }
3518 }
3519
3520 function toggleClass( elem, name ) {
3521 if ( hasClass( elem, name ) ) {
3522 removeClass( elem, name );
3523 } else {
3524 addClass( elem, name );
3525 }
3526 }
3527
3528 function removeClass( elem, name ) {
3529 var set = " " + elem.className + " ";
3530
3531 // Class name may appear multiple times
3532 while ( set.indexOf( " " + name + " " ) >= 0 ) {
3533 set = set.replace( " " + name + " ", " " );
3534 }
3535
3536 // trim for prettiness
3537 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3538 }
3539
3540 function id( name ) {
3541 return defined.document && document.getElementById && document.getElementById( name );
3542 }
3543
3544 function getUrlConfigHtml() {
3545 var i, j, val,
3546 escaped, escapedTooltip,
3547 selection = false,
3548 len = config.urlConfig.length,
3549 urlConfigHtml = "";
3550
3551 for ( i = 0; i < len; i++ ) {
3552 val = config.urlConfig[ i ];
3553 if ( typeof val === "string" ) {
3554 val = {
3555 id: val,
3556 label: val
3557 };
3558 }
3559
3560 escaped = escapeText( val.id );
3561 escapedTooltip = escapeText( val.tooltip );
3562
3563 if ( config[ val.id ] === undefined ) {
3564 config[ val.id ] = QUnit.urlParams[ val.id ];
3565 }
3566
3567 if ( !val.value || typeof val.value === "string" ) {
3568 urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
3569 "' name='" + escaped + "' type='checkbox'" +
3570 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
3571 ( config[ val.id ] ? " checked='checked'" : "" ) +
3572 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
3573 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
3574 } else {
3575 urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
3576 "' title='" + escapedTooltip + "'>" + val.label +
3577 ": </label><select id='qunit-urlconfig-" + escaped +
3578 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
3579
3580 if ( QUnit.is( "array", val.value ) ) {
3581 for ( j = 0; j < val.value.length; j++ ) {
3582 escaped = escapeText( val.value[ j ] );
3583 urlConfigHtml += "<option value='" + escaped + "'" +
3584 ( config[ val.id ] === val.value[ j ] ?
3585 ( selection = true ) && " selected='selected'" : "" ) +
3586 ">" + escaped + "</option>";
3587 }
3588 } else {
3589 for ( j in val.value ) {
3590 if ( hasOwn.call( val.value, j ) ) {
3591 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
3592 ( config[ val.id ] === j ?
3593 ( selection = true ) && " selected='selected'" : "" ) +
3594 ">" + escapeText( val.value[ j ] ) + "</option>";
3595 }
3596 }
3597 }
3598 if ( config[ val.id ] && !selection ) {
3599 escaped = escapeText( config[ val.id ] );
3600 urlConfigHtml += "<option value='" + escaped +
3601 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3602 }
3603 urlConfigHtml += "</select>";
3604 }
3605 }
3606
3607 return urlConfigHtml;
3608 }
3609
3610 // Handle "click" events on toolbar checkboxes and "change" for select menus.
3611 // Updates the URL with the new state of `config.urlConfig` values.
3612 function toolbarChanged() {
3613 var updatedUrl, value,
3614 field = this,
3615 params = {};
3616
3617 // Detect if field is a select menu or a checkbox
3618 if ( "selectedIndex" in field ) {
3619 value = field.options[ field.selectedIndex ].value || undefined;
3620 } else {
3621 value = field.checked ? ( field.defaultValue || true ) : undefined;
3622 }
3623
3624 params[ field.name ] = value;
3625 updatedUrl = setUrl( params );
3626
3627 if ( "hidepassed" === field.name && "replaceState" in window.history ) {
3628 config[ field.name ] = value || false;
3629 if ( value ) {
3630 addClass( id( "qunit-tests" ), "hidepass" );
3631 } else {
3632 removeClass( id( "qunit-tests" ), "hidepass" );
3633 }
3634
3635 // It is not necessary to refresh the whole page
3636 window.history.replaceState( null, "", updatedUrl );
3637 } else {
3638 window.location = updatedUrl;
3639 }
3640 }
3641
3642 function setUrl( params ) {
3643 var key,
3644 querystring = "?";
3645
3646 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
3647
3648 for ( key in params ) {
3649 if ( hasOwn.call( params, key ) ) {
3650 if ( params[ key ] === undefined ) {
3651 continue;
3652 }
3653 querystring += encodeURIComponent( key );
3654 if ( params[ key ] !== true ) {
3655 querystring += "=" + encodeURIComponent( params[ key ] );
3656 }
3657 querystring += "&";
3658 }
3659 }
3660 return location.protocol + "//" + location.host +
3661 location.pathname + querystring.slice( 0, -1 );
3662 }
3663
3664 function applyUrlParams() {
3665 var selectedModule,
3666 modulesList = id( "qunit-modulefilter" ),
3667 filter = id( "qunit-filter-input" ).value;
3668
3669 selectedModule = modulesList ?
3670 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
3671 undefined;
3672
3673 window.location = setUrl({
3674 module: ( selectedModule === "" ) ? undefined : selectedModule,
3675 filter: ( filter === "" ) ? undefined : filter,
3676
3677 // Remove testId filter
3678 testId: undefined
3679 });
3680 }
3681
3682 function toolbarUrlConfigContainer() {
3683 var urlConfigContainer = document.createElement( "span" );
3684
3685 urlConfigContainer.innerHTML = getUrlConfigHtml();
3686 addClass( urlConfigContainer, "qunit-url-config" );
3687
3688 // For oldIE support:
3689 // * Add handlers to the individual elements instead of the container
3690 // * Use "click" instead of "change" for checkboxes
3691 addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
3692 addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
3693
3694 return urlConfigContainer;
3695 }
3696
3697 function toolbarLooseFilter() {
3698 var filter = document.createElement( "form" ),
3699 label = document.createElement( "label" ),
3700 input = document.createElement( "input" ),
3701 button = document.createElement( "button" );
3702
3703 addClass( filter, "qunit-filter" );
3704
3705 label.innerHTML = "Filter: ";
3706
3707 input.type = "text";
3708 input.value = config.filter || "";
3709 input.name = "filter";
3710 input.id = "qunit-filter-input";
3711
3712 button.innerHTML = "Go";
3713
3714 label.appendChild( input );
3715
3716 filter.appendChild( label );
3717 filter.appendChild( button );
3718 addEvent( filter, "submit", function( ev ) {
3719 applyUrlParams();
3720
3721 if ( ev && ev.preventDefault ) {
3722 ev.preventDefault();
3723 }
3724
3725 return false;
3726 });
3727
3728 return filter;
3729 }
3730
3731 function toolbarModuleFilterHtml() {
3732 var i,
3733 moduleFilterHtml = "";
3734
3735 if ( !modulesList.length ) {
3736 return false;
3737 }
3738
3739 modulesList.sort(function( a, b ) {
3740 return a.localeCompare( b );
3741 });
3742
3743 moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
3744 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
3745 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
3746 ">< All Modules ></option>";
3747
3748 for ( i = 0; i < modulesList.length; i++ ) {
3749 moduleFilterHtml += "<option value='" +
3750 escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
3751 ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
3752 ">" + escapeText( modulesList[ i ] ) + "</option>";
3753 }
3754 moduleFilterHtml += "</select>";
3755
3756 return moduleFilterHtml;
3757 }
3758
3759 function toolbarModuleFilter() {
3760 var toolbar = id( "qunit-testrunner-toolbar" ),
3761 moduleFilter = document.createElement( "span" ),
3762 moduleFilterHtml = toolbarModuleFilterHtml();
3763
3764 if ( !toolbar || !moduleFilterHtml ) {
3765 return false;
3766 }
3767
3768 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
3769 moduleFilter.innerHTML = moduleFilterHtml;
3770
3771 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
3772
3773 toolbar.appendChild( moduleFilter );
3774 }
3775
3776 function appendToolbar() {
3777 var toolbar = id( "qunit-testrunner-toolbar" );
3778
3779 if ( toolbar ) {
3780 toolbar.appendChild( toolbarUrlConfigContainer() );
3781 toolbar.appendChild( toolbarLooseFilter() );
3782 }
3783 }
3784
3785 function appendHeader() {
3786 var header = id( "qunit-header" );
3787
3788 if ( header ) {
3789 header.innerHTML = "<a href='" +
3790 escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
3791 "'>" + header.innerHTML + "</a> ";
3792 }
3793 }
3794
3795 function appendBanner() {
3796 var banner = id( "qunit-banner" );
3797
3798 if ( banner ) {
3799 banner.className = "";
3800 }
3801 }
3802
3803 function appendTestResults() {
3804 var tests = id( "qunit-tests" ),
3805 result = id( "qunit-testresult" );
3806
3807 if ( result ) {
3808 result.parentNode.removeChild( result );
3809 }
3810
3811 if ( tests ) {
3812 tests.innerHTML = "";
3813 result = document.createElement( "p" );
3814 result.id = "qunit-testresult";
3815 result.className = "result";
3816 tests.parentNode.insertBefore( result, tests );
3817 result.innerHTML = "Running...<br />&#160;";
3818 }
3819 }
3820
3821 function storeFixture() {
3822 var fixture = id( "qunit-fixture" );
3823 if ( fixture ) {
3824 config.fixture = fixture.innerHTML;
3825 }
3826 }
3827
3828 function appendFilteredTest() {
3829 var testId = QUnit.config.testId;
3830 if ( !testId || testId.length <= 0 ) {
3831 return "";
3832 }
3833 return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
3834 escapeText( testId.join(", ") ) +
3835 " <a id='qunit-clearFilter' href='" +
3836 escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
3837 "'>" + "Run all tests" + "</a></div>";
3838 }
3839
3840 function appendUserAgent() {
3841 var userAgent = id( "qunit-userAgent" );
3842
3843 if ( userAgent ) {
3844 userAgent.innerHTML = "";
3845 userAgent.appendChild(
3846 document.createTextNode(
3847 "QUnit " + QUnit.version + "; " + navigator.userAgent
3848 )
3849 );
3850 }
3851 }
3852
3853 function appendTestsList( modules ) {
3854 var i, l, x, z, test, moduleObj;
3855
3856 for ( i = 0, l = modules.length; i < l; i++ ) {
3857 moduleObj = modules[ i ];
3858
3859 if ( moduleObj.name ) {
3860 modulesList.push( moduleObj.name );
3861 }
3862
3863 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
3864 test = moduleObj.tests[ x ];
3865
3866 appendTest( test.name, test.testId, moduleObj.name );
3867 }
3868 }
3869 }
3870
3871 function appendTest( name, testId, moduleName ) {
3872 var title, rerunTrigger, testBlock, assertList,
3873 tests = id( "qunit-tests" );
3874
3875 if ( !tests ) {
3876 return;
3877 }
3878
3879 title = document.createElement( "strong" );
3880 title.innerHTML = getNameHtml( name, moduleName );
3881
3882 rerunTrigger = document.createElement( "a" );
3883 rerunTrigger.innerHTML = "Rerun";
3884 rerunTrigger.href = setUrl({ testId: testId });
3885
3886 testBlock = document.createElement( "li" );
3887 testBlock.appendChild( title );
3888 testBlock.appendChild( rerunTrigger );
3889 testBlock.id = "qunit-test-output-" + testId;
3890
3891 assertList = document.createElement( "ol" );
3892 assertList.className = "qunit-assert-list";
3893
3894 testBlock.appendChild( assertList );
3895
3896 tests.appendChild( testBlock );
3897 }
3898
3899 // HTML Reporter initialization and load
3900 QUnit.begin(function( details ) {
3901 var qunit = id( "qunit" );
3902
3903 // Fixture is the only one necessary to run without the #qunit element
3904 storeFixture();
3905
3906 if ( qunit ) {
3907 qunit.innerHTML =
3908 "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
3909 "<h2 id='qunit-banner'></h2>" +
3910 "<div id='qunit-testrunner-toolbar'></div>" +
3911 appendFilteredTest() +
3912 "<h2 id='qunit-userAgent'></h2>" +
3913 "<ol id='qunit-tests'></ol>";
3914 }
3915
3916 appendHeader();
3917 appendBanner();
3918 appendTestResults();
3919 appendUserAgent();
3920 appendToolbar();
3921 appendTestsList( details.modules );
3922 toolbarModuleFilter();
3923
3924 if ( qunit && config.hidepassed ) {
3925 addClass( qunit.lastChild, "hidepass" );
3926 }
3927 });
3928
3929 QUnit.done(function( details ) {
3930 var i, key,
3931 banner = id( "qunit-banner" ),
3932 tests = id( "qunit-tests" ),
3933 html = [
3934 "Tests completed in ",
3935 details.runtime,
3936 " milliseconds.<br />",
3937 "<span class='passed'>",
3938 details.passed,
3939 "</span> assertions of <span class='total'>",
3940 details.total,
3941 "</span> passed, <span class='failed'>",
3942 details.failed,
3943 "</span> failed."
3944 ].join( "" );
3945
3946 if ( banner ) {
3947 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
3948 }
3949
3950 if ( tests ) {
3951 id( "qunit-testresult" ).innerHTML = html;
3952 }
3953
3954 if ( config.altertitle && defined.document && document.title ) {
3955
3956 // show ✖ for good, ✔ for bad suite result in title
3957 // use escape sequences in case file gets loaded with non-utf-8-charset
3958 document.title = [
3959 ( details.failed ? "\u2716" : "\u2714" ),
3960 document.title.replace( /^[\u2714\u2716] /i, "" )
3961 ].join( " " );
3962 }
3963
3964 // clear own sessionStorage items if all tests passed
3965 if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
3966 for ( i = 0; i < sessionStorage.length; i++ ) {
3967 key = sessionStorage.key( i++ );
3968 if ( key.indexOf( "qunit-test-" ) === 0 ) {
3969 sessionStorage.removeItem( key );
3970 }
3971 }
3972 }
3973
3974 // scroll back to top to show results
3975 if ( config.scrolltop && window.scrollTo ) {
3976 window.scrollTo( 0, 0 );
3977 }
3978 });
3979
3980 function getNameHtml( name, module ) {
3981 var nameHtml = "";
3982
3983 if ( module ) {
3984 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
3985 }
3986
3987 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
3988
3989 return nameHtml;
3990 }
3991
3992 QUnit.testStart(function( details ) {
3993 var running, testBlock, bad;
3994
3995 testBlock = id( "qunit-test-output-" + details.testId );
3996 if ( testBlock ) {
3997 testBlock.className = "running";
3998 } else {
3999
4000 // Report later registered tests
4001 appendTest( details.name, details.testId, details.module );
4002 }
4003
4004 running = id( "qunit-testresult" );
4005 if ( running ) {
4006 bad = QUnit.config.reorder && defined.sessionStorage &&
4007 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
4008
4009 running.innerHTML = ( bad ?
4010 "Rerunning previously failed test: <br />" :
4011 "Running: <br />" ) +
4012 getNameHtml( details.name, details.module );
4013 }
4014
4015 });
4016
4017 function stripHtml( string ) {
4018 // strip tags, html entity and whitespaces
4019 return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
4020 }
4021
4022 QUnit.log(function( details ) {
4023 var assertList, assertLi,
4024 message, expected, actual, diff,
4025 showDiff = false,
4026 testItem = id( "qunit-test-output-" + details.testId );
4027
4028 if ( !testItem ) {
4029 return;
4030 }
4031
4032 message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
4033 message = "<span class='test-message'>" + message + "</span>";
4034 message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
4035
4036 // pushFailure doesn't provide details.expected
4037 // when it calls, it's implicit to also not show expected and diff stuff
4038 // Also, we need to check details.expected existence, as it can exist and be undefined
4039 if ( !details.result && hasOwn.call( details, "expected" ) ) {
4040 if ( details.negative ) {
4041 expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
4042 } else {
4043 expected = escapeText( QUnit.dump.parse( details.expected ) );
4044 }
4045
4046 actual = escapeText( QUnit.dump.parse( details.actual ) );
4047 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
4048 expected +
4049 "</pre></td></tr>";
4050
4051 if ( actual !== expected ) {
4052
4053 message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
4054 actual + "</pre></td></tr>";
4055
4056 // Don't show diff if actual or expected are booleans
4057 if ( !( /^(true|false)$/.test( actual ) ) &&
4058 !( /^(true|false)$/.test( expected ) ) ) {
4059 diff = QUnit.diff( expected, actual );
4060 showDiff = stripHtml( diff ).length !==
4061 stripHtml( expected ).length +
4062 stripHtml( actual ).length;
4063 }
4064
4065 // Don't show diff if expected and actual are totally different
4066 if ( showDiff ) {
4067 message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
4068 diff + "</pre></td></tr>";
4069 }
4070 } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
4071 expected.indexOf( "[object Object]" ) !== -1 ) {
4072 message += "<tr class='test-message'><th>Message: </th><td>" +
4073 "Diff suppressed as the depth of object is more than current max depth (" +
4074 QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
4075 " run with a higher max depth or <a href='" +
4076 escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
4077 "Rerun</a> without max depth.</p></td></tr>";
4078 }
4079
4080 if ( details.source ) {
4081 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
4082 escapeText( details.source ) + "</pre></td></tr>";
4083 }
4084
4085 message += "</table>";
4086
4087 // this occurs when pushFailure is set and we have an extracted stack trace
4088 } else if ( !details.result && details.source ) {
4089 message += "<table>" +
4090 "<tr class='test-source'><th>Source: </th><td><pre>" +
4091 escapeText( details.source ) + "</pre></td></tr>" +
4092 "</table>";
4093 }
4094
4095 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4096
4097 assertLi = document.createElement( "li" );
4098 assertLi.className = details.result ? "pass" : "fail";
4099 assertLi.innerHTML = message;
4100 assertList.appendChild( assertLi );
4101 });
4102
4103 QUnit.testDone(function( details ) {
4104 var testTitle, time, testItem, assertList,
4105 good, bad, testCounts, skipped, sourceName,
4106 tests = id( "qunit-tests" );
4107
4108 if ( !tests ) {
4109 return;
4110 }
4111
4112 testItem = id( "qunit-test-output-" + details.testId );
4113
4114 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
4115
4116 good = details.passed;
4117 bad = details.failed;
4118
4119 // store result when possible
4120 if ( config.reorder && defined.sessionStorage ) {
4121 if ( bad ) {
4122 sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
4123 } else {
4124 sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
4125 }
4126 }
4127
4128 if ( bad === 0 ) {
4129
4130 // Collapse the passing tests
4131 addClass( assertList, "qunit-collapsed" );
4132 } else if ( bad && config.collapse && !collapseNext ) {
4133
4134 // Skip collapsing the first failing test
4135 collapseNext = true;
4136 } else {
4137
4138 // Collapse remaining tests
4139 addClass( assertList, "qunit-collapsed" );
4140 }
4141
4142 // testItem.firstChild is the test name
4143 testTitle = testItem.firstChild;
4144
4145 testCounts = bad ?
4146 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
4147 "";
4148
4149 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
4150 details.assertions.length + ")</b>";
4151
4152 if ( details.skipped ) {
4153 testItem.className = "skipped";
4154 skipped = document.createElement( "em" );
4155 skipped.className = "qunit-skipped-label";
4156 skipped.innerHTML = "skipped";
4157 testItem.insertBefore( skipped, testTitle );
4158 } else {
4159 addEvent( testTitle, "click", function() {
4160 toggleClass( assertList, "qunit-collapsed" );
4161 });
4162
4163 testItem.className = bad ? "fail" : "pass";
4164
4165 time = document.createElement( "span" );
4166 time.className = "runtime";
4167 time.innerHTML = details.runtime + " ms";
4168 testItem.insertBefore( time, assertList );
4169 }
4170
4171 // Show the source of the test when showing assertions
4172 if ( details.source ) {
4173 sourceName = document.createElement( "p" );
4174 sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
4175 addClass( sourceName, "qunit-source" );
4176 if ( bad === 0 ) {
4177 addClass( sourceName, "qunit-collapsed" );
4178 }
4179 addEvent( testTitle, "click", function() {
4180 toggleClass( sourceName, "qunit-collapsed" );
4181 });
4182 testItem.appendChild( sourceName );
4183 }
4184 });
4185
4186 if ( defined.document ) {
4187
4188 // Avoid readyState issue with phantomjs
4189 // Ref: #818
4190 var notPhantom = ( function( p ) {
4191 return !( p && p.version && p.version.major > 0 );
4192 } )( window.phantom );
4193
4194 if ( notPhantom && document.readyState === "complete" ) {
4195 QUnit.load();
4196 } else {
4197 addEvent( window, "load", QUnit.load );
4198 }
4199 } else {
4200 config.pageLoaded = true;
4201 config.autorun = true;
4202 }
4203
4204 })();