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