ApiMessage: Use a trait to avoid code duplication
[mediawiki.git] / resources / lib / qunitjs / qunit.js
blobf3542ca9d4c2495b5fe73cf8685cce3b32405de1
1 /*!
2 * QUnit 1.18.0
3 * http://qunitjs.com/
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license
7 * http://jquery.org/license
9 * Date: 2015-04-03T10:23Z
12 (function( window ) {
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();
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;
42 }())
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
49 * Based on http://es5.github.com/#x15.11.4.4
51 * @param {String|Error} error
52 * @return {String} error message
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";
69 } else {
70 return errorString;
73 /**
74 * Makes a clone of an object using only Array or Object as base,
75 * and copies over the own enumerable properties.
77 * @param {Object} obj
78 * @return {Object} New object with only the own properties (recursively).
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;
89 return vals;
92 QUnit = {};
94 /**
95 * Config object: Maintain internal state
96 * Later exposed as QUnit.config
97 * `config` initialized at top of scope
99 config = {
100 // The queue of tests to run
101 queue: [],
103 // block until document ready
104 blocking: true,
106 // by default, run previously failed tests first
107 // very useful in combination with "Hide passed tests" checked
108 reorder: true,
110 // by default, modify document.title when suite is done
111 altertitle: true,
113 // by default, scroll to top of the page when suite is done
114 scrolltop: true,
116 // when enabled, all tests must call expect()
117 requireExpects: false,
119 // depth up-to which object will be dumped
120 maxDepth: 5,
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: [
126 id: "hidepassed",
127 label: "Hide passed tests",
128 tooltip: "Only show tests and assertions that fail. Stored as query-strings."
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."
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."
144 // Set of all modules.
145 modules: [],
147 // The first unnamed module
148 currentModule: {
149 name: "",
150 tests: []
153 callbacks: {}
156 // Push a loose unnamed module to the modules collection
157 config.modules.push( config.currentModule );
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 = {};
167 if ( params[ 0 ] ) {
168 for ( i = 0; i < length; i++ ) {
169 current = params[ i ].split( "=" );
170 current[ 0 ] = decodeURIComponent( current[ 0 ] );
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 ];
182 if ( urlParams.filter === true ) {
183 delete urlParams.filter;
186 QUnit.urlParams = urlParams;
188 // String search anywhere in moduleName+testName
189 config.filter = urlParams.filter;
191 if ( urlParams.maxDepth ) {
192 config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
193 Number.POSITIVE_INFINITY :
194 urlParams.maxDepth;
197 config.testId = [];
198 if ( urlParams.testId ) {
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 ] );
207 // Figure out if we're running the tests from a server or not
208 QUnit.isLocal = location.protocol === "file:";
210 // Expose the current QUnit version
211 QUnit.version = "1.18.0";
212 }());
214 // Root QUnit object.
215 // `QUnit` initialized at top of scope
216 extend( QUnit, {
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: []
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;
232 if ( testEnvironment && testEnvironment.teardown ) {
233 testEnvironment.afterEach = testEnvironment.teardown;
234 delete testEnvironment.teardown;
237 config.modules.push( currentModule );
238 config.currentModule = currentModule;
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;
248 QUnit.test( testName, expected, callback, true );
251 test: function( testName, expected, callback, async ) {
252 var test;
254 if ( arguments.length === 2 ) {
255 callback = expected;
256 expected = null;
259 test = new Test({
260 testName: testName,
261 expected: expected,
262 async: async,
263 callback: callback
266 test.queue();
269 skip: function( testName ) {
270 var test = new Test({
271 testName: testName,
272 skip: true
275 test.queue();
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;
283 if ( !config.current ) {
284 globalStartCalled = true;
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 ) {
295 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
296 config.autostart = true;
297 return;
299 } else {
301 // If a test is running, adjust its semaphore
302 config.current.semaphore -= count || 1;
304 // Don't start until equal number of stop-calls
305 if ( config.current.semaphore > 0 ) {
306 return;
309 // throw an Error if start is called more often than stop
310 if ( config.current.semaphore < 0 ) {
311 config.current.semaphore = 0;
313 QUnit.pushFailure(
314 "Called start() while already started (test's semaphore was 0 already)",
315 sourceFromStacktrace( 2 )
317 return;
321 resumeProcessing();
324 // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
325 stop: function( count ) {
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" );
332 // If a test is running, adjust its semaphore
333 config.current.semaphore += count || 1;
335 pauseProcessing();
338 config: config,
340 // Safe object type checking
341 is: function( type, obj ) {
342 return QUnit.objectType( obj ) === type;
345 objectType: function( obj ) {
346 if ( typeof obj === "undefined" ) {
347 return "undefined";
350 // Consider: typeof null === object
351 if ( obj === null ) {
352 return "null";
355 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
356 type = match && match[ 1 ] || "";
358 switch ( type ) {
359 case "Number":
360 if ( isNaN( obj ) ) {
361 return "nan";
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();
372 if ( typeof obj === "object" ) {
373 return "object";
375 return undefined;
378 extend: extend,
380 load: function() {
381 config.pageLoaded = true;
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 );
393 config.blocking = false;
395 if ( config.autostart ) {
396 resumeProcessing();
401 // Register logging callbacks
402 (function() {
403 var i, l, key,
404 callbacks = [ "begin", "done", "log", "testStart", "testDone",
405 "moduleStart", "moduleDone" ];
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."
415 config.callbacks[ key ].push( callback );
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;
423 return loggingCallback;
426 for ( i = 0, l = callbacks.length; i < l; i++ ) {
427 key = callbacks[ i ];
429 // Initialize key collection of logging callback
430 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
431 config.callbacks[ key ] = [];
434 QUnit[ key ] = registerLoggingCallback( key );
436 })();
438 // `onErrorFnPrev` initialized at top of scope
439 // Preserve other handlers
440 onErrorFnPrev = window.onerror;
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 );
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;
458 QUnit.pushFailure( error, filePath + ":" + linerNr );
459 } else {
460 QUnit.test( "global failure", extend(function() {
461 QUnit.pushFailure( error, filePath + ":" + linerNr );
462 }, { validTest: true } ) );
464 return false;
467 return ret;
470 function done() {
471 var runtime, passed;
473 config.autorun = true;
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
486 delete config.previousModule;
488 runtime = now() - config.started;
489 passed = config.stats.all - config.stats.bad;
491 runLoggingCallbacks( "done", {
492 failed: config.stats.bad,
493 passed: passed,
494 total: config.stats.all,
495 runtime: runtime
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;
504 var stack, include, i;
506 if ( e.stack ) {
507 stack = e.stack.split( "\n" );
508 if ( /^error$/i.test( stack[ 0 ] ) ) {
509 stack.shift();
511 if ( fileName ) {
512 include = [];
513 for ( i = offset; i < stack.length; i++ ) {
514 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
515 break;
517 include.push( stack[ i ] );
519 if ( include.length ) {
520 return include.join( "\n" );
523 return stack[ offset ];
525 // Support: Safari <=6 only
526 } else if ( e.sourceURL ) {
528 // exclude useless self-reference for generated Error objects
529 if ( /qunit.js$/.test( e.sourceURL ) ) {
530 return;
533 // for actual exceptions, this is useful
534 return e.sourceURL + ":" + e.line;
538 function sourceFromStacktrace( offset ) {
539 var error = new Error();
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;
551 return extractStacktrace( error, offset );
554 function synchronize( callback, last ) {
555 if ( QUnit.objectType( callback ) === "array" ) {
556 while ( callback.length ) {
557 synchronize( callback.shift() );
559 return;
561 config.queue.push( callback );
563 if ( config.autorun && !config.blocking ) {
564 process( last );
568 function process( last ) {
569 function next() {
570 process( last );
572 var start = now();
573 config.depth = ( config.depth || 0 ) + 1;
575 while ( config.queue.length && !config.blocking ) {
576 if ( !defined.setTimeout || config.updateRate <= 0 ||
577 ( ( now() - start ) < config.updateRate ) ) {
578 if ( config.current ) {
580 // Reset async tracking for each phase of the Test lifecycle
581 config.current.usedAsync = false;
583 config.queue.shift()();
584 } else {
585 setTimeout( next, 13 );
586 break;
589 config.depth--;
590 if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
591 done();
595 function begin() {
596 var i, l,
597 modulesLog = [];
599 // If the test run hasn't officially begun yet
600 if ( !config.started ) {
602 // Record the time of the test run's beginning
603 config.started = now();
605 verifyLoggingCallbacks();
607 // Delete the loose unnamed module if unused.
608 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
609 config.modules.shift();
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
620 // The test run is officially beginning now
621 runLoggingCallbacks( "begin", {
622 totalTests: Test.count,
623 modules: modulesLog
627 config.blocking = false;
628 process( true );
631 function resumeProcessing() {
632 runStarted = true;
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;
640 if ( config.timeout ) {
641 clearTimeout( config.timeout );
644 begin();
645 }, 13 );
646 } else {
647 begin();
651 function pauseProcessing() {
652 config.blocking = true;
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" );
663 resumeProcessing();
664 }, config.testTimeout );
668 function saveGlobal() {
669 config.pollution = [];
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;
678 config.pollution.push( key );
684 function checkPollution() {
685 var newGlobals,
686 deletedGlobals,
687 old = config.pollution;
689 saveGlobal();
691 newGlobals = diff( config.pollution, old );
692 if ( newGlobals.length > 0 ) {
693 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
696 deletedGlobals = diff( old, config.pollution );
697 if ( deletedGlobals.length > 0 ) {
698 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
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();
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;
716 return result;
719 function extend( a, b, undefOnly ) {
720 for ( var prop in b ) {
721 if ( hasOwn.call( b, prop ) ) {
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 ];
734 return a;
737 function runLoggingCallbacks( key, args ) {
738 var i, l, callbacks;
740 callbacks = config.callbacks[ key ];
741 for ( i = 0, l = callbacks.length; i < l; i++ ) {
742 callbacks[ i ]( args );
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;
752 for ( loggingCallback in loggingCallbacks ) {
753 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
755 userCallback = QUnit[ loggingCallback ];
757 // Restore the callback function
758 QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
760 // Assign the deprecated given callback
761 QUnit[ loggingCallback ]( userCallback );
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/"
774 // from jquery.js
775 function inArray( elem, array ) {
776 if ( array.indexOf ) {
777 return array.indexOf( elem );
780 for ( var i = 0, length = array.length; i < length; i++ ) {
781 if ( array[ i ] === elem ) {
782 return i;
786 return -1;
789 function Test( settings ) {
790 var i, l;
792 ++Test.count;
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 );
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 += " ";
808 this.testId = generateHash( this.module.name, this.testName );
810 this.module.tests.push({
811 name: this.testName,
812 testId: this.testId
815 if ( settings.skip ) {
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 );
826 Test.count = 0;
828 Test.prototype = {
829 before: function() {
830 if (
832 // Emit moduleStart when we're switching from one module to another
833 this.module !== config.previousModule ||
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" )
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
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
859 config.current = this;
861 this.testEnvironment = extend( {}, this.module.testEnvironment );
862 delete this.testEnvironment.beforeEach;
863 delete this.testEnvironment.afterEach;
865 this.started = now();
866 runLoggingCallbacks( "testStart", {
867 name: this.testName,
868 module: this.module.name,
869 testId: this.testId
872 if ( !config.pollution ) {
873 saveGlobal();
877 run: function() {
878 var promise;
880 config.current = this;
882 if ( this.async ) {
883 QUnit.stop();
886 this.callbackStarted = now();
888 if ( config.notrycatch ) {
889 promise = this.callback.call( this.testEnvironment, this.assert );
890 this.resolvePromise( promise );
891 return;
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 ) );
901 // else next test will carry the responsibility
902 saveGlobal();
904 // Restart the tests if they're blocking
905 if ( config.blocking ) {
906 QUnit.start();
911 after: function() {
912 checkPollution();
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;
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 ) );
935 // Currently only used for module level hooks, can be used to add global level ones
936 hooks: function( handler ) {
937 var hooks = [];
939 // Hooks are ignored on skipped tests
940 if ( this.skip ) {
941 return hooks;
944 if ( this.module.testEnvironment &&
945 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
946 hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
949 return hooks;
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 );
965 var i,
966 bad = 0;
968 this.runtime = now() - this.started;
969 config.stats.all += this.assertions.length;
970 config.moduleStats.all += this.assertions.length;
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++;
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,
989 // HTML Reporter use
990 assertions: this.assertions,
991 testId: this.testId,
993 // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
994 duration: this.runtime
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();
1002 config.current = undefined;
1005 queue: function() {
1006 var bad,
1007 test = this;
1009 if ( !this.valid() ) {
1010 return;
1013 function run() {
1015 // each of these can by async
1016 synchronize([
1017 function() {
1018 test.before();
1021 test.hooks( "beforeEach" ),
1023 function() {
1024 test.run();
1027 test.hooks( "afterEach" ).reverse(),
1029 function() {
1030 test.after();
1032 function() {
1033 test.finish();
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 );
1043 if ( bad ) {
1044 run();
1045 } else {
1046 synchronize( run, true );
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
1063 if ( !result ) {
1064 source = sourceFromStacktrace();
1066 if ( source ) {
1067 details.source = source;
1071 runLoggingCallbacks( "log", details );
1073 this.assertions.push({
1074 result: !!result,
1075 message: message
1079 pushFailure: function( message, source, actual ) {
1080 if ( !this instanceof Test ) {
1081 throw new Error( "pushFailure() assertion outside test context, was " +
1082 sourceFromStacktrace( 2 ) );
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
1095 if ( source ) {
1096 details.source = source;
1099 runLoggingCallbacks( "log", details );
1101 this.assertions.push({
1102 result: false,
1103 message: message
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 ) );
1123 // else next test will carry the responsibility
1124 saveGlobal();
1126 // Unblock
1127 QUnit.start();
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();
1140 // Internally-generated tests are always valid
1141 if ( this.callback && this.callback.validTest ) {
1142 return true;
1145 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
1146 return false;
1149 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
1150 return false;
1153 if ( !filter ) {
1154 return true;
1157 include = filter.charAt( 0 ) !== "!";
1158 if ( !include ) {
1159 filter = filter.slice( 1 );
1162 // If the filter matches, we need to honour include
1163 if ( fullName.indexOf( filter ) !== -1 ) {
1164 return include;
1167 // Otherwise, do the opposite
1168 return !include;
1173 // Resets the test setup. Useful for tests that modify the DOM.
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
1179 QUnit.reset = function() {
1181 // Return on non-browser environments
1182 // This is necessary to not break on node tests
1183 if ( typeof window === "undefined" ) {
1184 return;
1187 var fixture = defined.document && document.getElementById &&
1188 document.getElementById( "qunit-fixture" );
1190 if ( fixture ) {
1191 fixture.innerHTML = config.fixture;
1195 QUnit.pushFailure = function() {
1196 if ( !QUnit.config.current ) {
1197 throw new Error( "pushFailure() assertion outside test context, in " +
1198 sourceFromStacktrace( 2 ) );
1201 // Gets current test obj
1202 var currentTest = QUnit.config.current;
1204 return currentTest.pushFailure.apply( currentTest, arguments );
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;
1216 for ( ; i < len; i++ ) {
1217 hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
1218 hash |= 0;
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;
1228 return hex.slice( -8 );
1231 function Assert( testContext ) {
1232 this.test = testContext;
1235 // Assert helpers
1236 QUnit.assert = Assert.prototype = {
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;
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;
1254 test.semaphore += 1;
1255 test.usedAsync = true;
1256 pauseProcessing();
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 ) );
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;
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 ) );
1284 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
1285 currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
1286 sourceFromStacktrace( 2 ) );
1288 // Allow this assertion to continue running anyway...
1291 if ( !( assert instanceof Assert ) ) {
1292 assert = currentTest.assert;
1294 return assert.test.push.apply( assert.test, arguments );
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 );
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 );
1309 equal: function( actual, expected, message ) {
1310 /*jshint eqeqeq:false */
1311 this.push( expected == actual, actual, expected, message );
1314 notEqual: function( actual, expected, message ) {
1315 /*jshint eqeqeq:false */
1316 this.push( expected != actual, actual, expected, message );
1319 propEqual: function( actual, expected, message ) {
1320 actual = objectValues( actual );
1321 expected = objectValues( expected );
1322 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1325 notPropEqual: function( actual, expected, message ) {
1326 actual = objectValues( actual );
1327 expected = objectValues( expected );
1328 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1331 deepEqual: function( actual, expected, message ) {
1332 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1335 notDeepEqual: function( actual, expected, message ) {
1336 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1339 strictEqual: function( actual, expected, message ) {
1340 this.push( expected === actual, actual, expected, message );
1343 notStrictEqual: function( actual, expected, message ) {
1344 this.push( expected !== actual, actual, expected, message );
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;
1353 // 'expected' is optional unless doing string comparison
1354 if ( message == null && typeof expected === "string" ) {
1355 message = expected;
1356 expected = null;
1359 currentTest.ignoreGlobalErrors = true;
1360 try {
1361 block.call( currentTest.testEnvironment );
1362 } catch (e) {
1363 actual = e;
1365 currentTest.ignoreGlobalErrors = false;
1367 if ( actual ) {
1368 expectedType = QUnit.objectType( expected );
1370 // we don't want to validate thrown error
1371 if ( !expected ) {
1372 ok = true;
1373 expectedOutput = null;
1375 // expected is a regexp
1376 } else if ( expectedType === "regexp" ) {
1377 ok = expected.test( errorString( actual ) );
1379 // expected is a string
1380 } else if ( expectedType === "string" ) {
1381 ok = expected === errorString( actual );
1383 // expected is a constructor, maybe an Error constructor
1384 } else if ( expectedType === "function" && actual instanceof expected ) {
1385 ok = true;
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;
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;
1400 currentTest.assert.push( ok, actual, expectedOutput, message );
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 }());
1411 // Test for equality any JavaScript type.
1412 // Author: Philippe Rathé <prathe@gmail.com>
1413 QUnit.equiv = (function() {
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
1427 // the real equiv function
1428 var innerEquiv,
1430 // stack to decide between skip/abort functions
1431 callers = [],
1433 // stack to avoiding loops from circular referencing
1434 parents = [],
1435 parentsB = [],
1437 getProto = Object.getPrototypeOf || function( obj ) {
1438 /* jshint camelcase: false, proto: true */
1439 return obj.__proto__;
1441 callbacks = (function() {
1443 // for string, boolean, number and null
1444 function useStrictEquality( b, a ) {
1446 /*jshint eqeqeq:false */
1447 if ( b instanceof a.constructor || a instanceof b.constructor ) {
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;
1459 return {
1460 "string": useStrictEquality,
1461 "boolean": useStrictEquality,
1462 "number": useStrictEquality,
1463 "null": useStrictEquality,
1464 "undefined": useStrictEquality,
1466 "nan": function( b ) {
1467 return isNaN( b );
1470 "date": function( b, a ) {
1471 return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1474 "regexp": function( b, a ) {
1475 return QUnit.objectType( b ) === "regexp" &&
1477 // the regex itself
1478 a.source === b.source &&
1480 // and its modifiers
1481 a.global === b.global &&
1483 // (gmi) ...
1484 a.ignoreCase === b.ignoreCase &&
1485 a.multiline === b.multiline &&
1486 a.sticky === b.sticky;
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";
1497 "array": function( b, a ) {
1498 var i, j, len, loop, aCircular, bCircular;
1500 // b could be an object literal here
1501 if ( QUnit.objectType( b ) !== "array" ) {
1502 return false;
1505 len = a.length;
1506 if ( len !== b.length ) {
1507 // safe and faster
1508 return false;
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;
1529 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1530 parents.pop();
1531 parentsB.pop();
1532 return false;
1535 parents.pop();
1536 parentsB.pop();
1537 return true;
1540 "object": function( b, a ) {
1542 /*jshint forin:false */
1543 var i, j, loop, aCircular, bCircular,
1544 // Default to true
1545 eq = true,
1546 aProperties = [],
1547 bProperties = [];
1549 // comparing constructors is more strict than using
1550 // instanceof
1551 if ( a.constructor !== b.constructor ) {
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;
1561 // stack constructor before traversing properties
1562 callers.push( a.constructor );
1564 // track reference to avoid circular references
1565 parents.push( a );
1566 parentsB.push( b );
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;
1583 aProperties.push( i );
1584 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1585 eq = false;
1586 break;
1590 parents.pop();
1591 parentsB.pop();
1592 callers.pop(); // unstack, we are done
1594 for ( i in b ) {
1595 bProperties.push( i ); // collect b's properties
1598 // Ensures identical properties name
1599 return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1602 }());
1604 innerEquiv = function() { // can take multiple arguments
1605 var args = [].slice.apply( arguments );
1606 if ( args.length < 2 ) {
1607 return true; // end transition
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 ) ) {
1617 // don't lose time with error prone cases
1618 return false;
1619 } else {
1620 return bindCallbacks( a, callbacks, [ b, a ] );
1623 // apply transition with (1..n) arguments
1624 }( args[ 0 ], args[ 1 ] ) ) &&
1625 innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1628 return innerEquiv;
1629 }());
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, "\\\"" ) + "\"";
1637 function literal( o ) {
1638 return o + "";
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 );
1647 if ( !arr ) {
1648 return pre + post;
1650 return [ pre, inner + arr, base + post ].join( s );
1652 function array( arr, stack ) {
1653 var i = arr.length,
1654 ret = new Array( i );
1656 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1657 return "[object Array]";
1660 this.up();
1661 while ( i-- ) {
1662 ret[ i ] = this.parse( arr[ i ], undefined, stack );
1664 this.down();
1665 return join( "[", ret, "]" );
1668 var reName = /^function (\w+)/,
1669 dump = {
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 );
1677 if ( inStack !== -1 ) {
1678 return "recursion(" + ( inStack - stack.length ) + ")";
1681 objType = objType || this.typeOf( obj );
1682 parser = this.parsers[ objType ];
1683 parserType = typeof parser;
1685 if ( parserType === "function" ) {
1686 stack.push( obj );
1687 res = parser.call( this, obj, stack );
1688 stack.pop();
1689 return res;
1691 return ( parserType === "string" ) ? parser : this.parsers.error;
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 (
1715 // native arrays
1716 toString.call( obj ) === "[object Array]" ||
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 ) ) )
1723 type = "array";
1724 } else if ( obj.constructor === Error.prototype.constructor ) {
1725 type = "error";
1726 } else {
1727 type = typeof obj;
1729 return type;
1731 separator: function() {
1732 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
1734 // extra can be a number, shortcut for increasing-calling-decreasing
1735 indent: function( extra ) {
1736 if ( !this.multiline ) {
1737 return "";
1739 var chr = this.indentChar;
1740 if ( this.HTML ) {
1741 chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
1743 return new Array( this.depth + ( extra || 0 ) ).join( chr );
1745 up: function( a ) {
1746 this.depth += a || 1;
1748 down: function( a ) {
1749 this.depth -= a || 1;
1751 setParser: function( name, parser ) {
1752 this.parsers[ name ] = parser;
1754 // The next 3 are exposed so you can use them
1755 quote: quote,
1756 literal: literal,
1757 join: join,
1759 depth: 1,
1760 maxDepth: QUnit.config.maxDepth,
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 + "\")";
1769 unknown: "[Unknown]",
1770 "null": "null",
1771 "undefined": "undefined",
1772 "function": function( fn ) {
1773 var ret = "function",
1775 // functions never have name in IE
1776 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1778 if ( name ) {
1779 ret += " " + name;
1781 ret += "( ";
1783 ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1784 return join( ret, dump.parse( fn, "functionCode" ), "}" );
1786 array: array,
1787 nodelist: array,
1788 "arguments": array,
1789 object: function( map, stack ) {
1790 var keys, key, val, i, nonEnumerableProperties,
1791 ret = [];
1793 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
1794 return "[object Object]";
1797 dump.up();
1798 keys = [];
1799 for ( key in map ) {
1800 keys.push( key );
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 );
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 ) );
1818 dump.down();
1819 return join( "{", ret, "}" );
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;
1829 if ( attrs ) {
1830 for ( i = 0, len = attrs.length; i < len; i++ ) {
1831 val = attrs[ i ].nodeValue;
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" );
1842 ret += close;
1844 // Show content of TextNode or CDATASection
1845 if ( node.nodeType === 3 || node.nodeType === 4 ) {
1846 ret += node.nodeValue;
1849 return ret + open + "/" + tag + close;
1852 // function calls it internally, it's the arguments part of the function
1853 functionArgs: function( fn ) {
1854 var args,
1855 l = fn.length;
1857 if ( !l ) {
1858 return "";
1861 args = new Array( l );
1862 while ( l-- ) {
1864 // 97 is 'a'
1865 args[ l ] = String.fromCharCode( 97 + l );
1867 return " " + args.join( ", " ) + " ";
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
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
1889 return dump;
1890 }());
1892 // back compat
1893 QUnit.jsDump = QUnit.dump;
1895 // For browser, export only select globals
1896 if ( typeof window !== "undefined" ) {
1898 // Deprecated
1899 // Extend assert methods to QUnit and Global scope through Backwards compatibility
1900 (function() {
1901 var i,
1902 assertions = Assert.prototype;
1904 function applyCurrent( current ) {
1905 return function() {
1906 var assert = new Assert( QUnit.config.current );
1907 current.apply( assert, arguments );
1911 for ( i in assertions ) {
1912 QUnit[ i ] = applyCurrent( assertions[ i ] );
1914 })();
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"
1938 for ( i = 0, l = keys.length; i < l; i++ ) {
1939 window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1941 })();
1943 window.QUnit = QUnit;
1946 // For nodejs
1947 if ( typeof module !== "undefined" && module && module.exports ) {
1948 module.exports = QUnit;
1950 // For consistency with CommonJS environments' exports
1951 module.exports.QUnit = QUnit;
1954 // For CommonJS with exports, but without module.exports, like Rhino
1955 if ( typeof exports !== "undefined" && exports ) {
1956 exports.QUnit = QUnit;
1959 if ( typeof define === "function" && define.amd ) {
1960 define( function() {
1961 return QUnit;
1962 } );
1963 QUnit.config.autostart = false;
1966 // Get a reference to the global object, like window in browsers
1967 }( (function() {
1968 return this;
1969 })() ));
1971 /*istanbul ignore next */
1972 // jscs:disable maximumLineLength
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.
1978 * The original source of google-diff-match-patch is attributable and licensed as follows:
1980 * Copyright 2006 Google Inc.
1981 * http://code.google.com/p/google-diff-match-patch/
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
1987 * http://www.apache.org/licenses/LICENSE-2.0
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.
1995 * More Info:
1996 * https://code.google.com/p/google-diff-match-patch/
1998 * Usage: QUnit.diff(expected, actual)
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"
2002 QUnit.diff = (function() {
2004 function DiffMatchPatch() {
2006 // Defaults.
2007 // Redefine these in your program to override the defaults.
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;
2015 // DIFF FUNCTIONS
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.'
2022 var DIFF_DELETE = -1,
2023 DIFF_INSERT = 1,
2024 DIFF_EQUAL = 0;
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.
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;
2050 deadline = optDeadline;
2052 // Check for null inputs.
2053 if ( text1 === null || text2 === null ) {
2054 throw new Error( "Null input. (DiffMain)" );
2057 // Check for equality (speedup).
2058 if ( text1 === text2 ) {
2059 if ( text1 ) {
2060 return [
2061 [ DIFF_EQUAL, text1 ]
2064 return [];
2067 if ( typeof optChecklines === "undefined" ) {
2068 optChecklines = true;
2071 checklines = optChecklines;
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 );
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 );
2086 // Compute the diff on the middle block.
2087 diffs = this.diffCompute( text1, text2, checklines, deadline );
2089 // Restore the prefix and suffix.
2090 if ( commonprefix ) {
2091 diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
2093 if ( commonsuffix ) {
2094 diffs.push( [ DIFF_EQUAL, commonsuffix ] );
2096 this.diffCleanupMerge( diffs );
2097 return diffs;
2101 * Reduce the number of edits by eliminating operationally trivial equalities.
2102 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
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;
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;
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>
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;
2168 changes = true;
2171 pointer++;
2174 if ( changes ) {
2175 this.diffCleanupMerge( diffs );
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.
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;
2202 return html.join("");
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.
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;
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;
2231 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2233 return pointermid;
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.
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;
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;
2262 pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
2264 return pointermid;
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
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;
2284 if ( !text1 ) {
2285 // Just add some text (speedup).
2286 return [
2287 [ DIFF_INSERT, text2 ]
2291 if (!text2) {
2292 // Just delete some text (speedup).
2293 return [
2294 [ DIFF_DELETE, text1 ]
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 ) ]
2308 // Swap insertions for deletions if diff is reversed.
2309 if ( text1.length > text2.length ) {
2310 diffs[0][0] = diffs[2][0] = DIFF_DELETE;
2312 return diffs;
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 ]
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);
2342 if (checklines && text1.length > 100 && text2.length > 100) {
2343 return this.diffLineMode(text1, text2, deadline);
2346 return this.diffBisect(text1, text2, deadline);
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
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;
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.
2373 dmp = this; // 'this' becomes 'window' in a closure.
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
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);
2408 if (bestCommon.length * 2 >= longtext.length) {
2409 return [ bestLongtextA, bestLongtextB,
2410 bestShorttextA, bestShorttextB, bestCommon
2412 } else {
2413 return null;
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;
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];
2447 midCommon = hm[4];
2448 return [ text1A, text1B, text2A, text2B, midCommon ];
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
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;
2470 diffs = this.DiffMain(text1, text2, false, deadline);
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);
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] );
2506 pointer = pointer + a.length;
2508 countInsert = 0;
2509 countDelete = 0;
2510 textDelete = "";
2511 textInsert = "";
2512 break;
2514 pointer++;
2516 diffs.pop(); // Remove the dummy entry at the end.
2518 return diffs;
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
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;
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;
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;
2575 y1 = x1 - k1;
2576 while (x1 < text1Length && y1 < text2Length &&
2577 text1.charAt(x1) === text2.charAt(y1)) {
2578 x1++;
2579 y1++;
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);
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;
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++;
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);
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 ]
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
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);
2664 // Compute both diffs serially.
2665 diffs = this.DiffMain(text1a, text2a, false, deadline);
2666 diffsb = this.DiffMain(text1b, text2b, false, deadline);
2668 return diffs.concat(diffsb);
2672 * Reduce the number of edits by eliminating semantically trivial equalities.
2673 * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
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;
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;
2729 pointer++;
2732 // Normalize the diff.
2733 if (changes) {
2734 this.diffCleanupMerge(diffs);
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++;
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++;
2776 pointer++;
2778 pointer++;
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
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;
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);
2806 textLength = Math.min(text1Length, text2Length);
2807 // Quick check for the worst case.
2808 if (text1 === text2) {
2809 return textLength;
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;
2823 length += found;
2824 if (found === 0 || text1.substring(textLength - length) ===
2825 text2.substring(0, length)) {
2826 best = length;
2827 length++;
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
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
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] = "";
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
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;
2875 line = text.substring(lineStart, lineEnd + 1);
2876 lineStart = lineEnd + 1;
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;
2887 return chars;
2890 chars1 = diffLinesToCharsMunge(text1);
2891 chars2 = diffLinesToCharsMunge(text2);
2892 return {
2893 chars1: chars1,
2894 chars2: chars2,
2895 lineArray: lineArray
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
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)];
2914 diffs[x][1] = text.join("");
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.
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++;
2963 textInsert = textInsert.substring(commonlength);
2964 textDelete = textDelete.substring(commonlength);
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);
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 ] );
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++;
2997 countInsert = 0;
2998 countDelete = 0;
2999 textDelete = "";
3000 textInsert = "";
3001 break;
3004 if (diffs[diffs.length - 1][1] === "") {
3005 diffs.pop(); // Remove the dummy entry at the end.
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;
3038 pointer++;
3040 // If shifts were made, the diff needs reordering and another shift sweep.
3041 if (changes) {
3042 this.diffCleanupMerge(diffs);
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);
3054 return text;
3056 }());
3057 // jscs:enable
3059 (function() {
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;
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 = [];
3077 // Return on non-browser environments
3078 // This is necessary to not break on node tests
3079 if ( typeof window === "undefined" ) {
3080 return;
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>";
3093 tests = id( "qunit-tests" );
3094 banner = id( "qunit-banner" );
3095 result = id( "qunit-testresult" );
3097 if ( tests ) {
3098 tests.innerHTML = "";
3101 if ( banner ) {
3102 banner.className = "";
3105 if ( result ) {
3106 result.parentNode.removeChild( result );
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;";
3118 // Don't load the HTML Reporter on non-Browser environments
3119 if ( typeof window === "undefined" ) {
3120 return;
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;
3136 }())
3138 modulesList = [];
3141 * Escape text for attribute or text content.
3143 function escapeText( s ) {
3144 if ( !s ) {
3145 return "";
3147 s = s + "";
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;";
3167 * @param {HTMLElement} elem
3168 * @param {string} type
3169 * @param {Function} fn
3171 function addEvent( elem, type, fn ) {
3172 if ( elem.addEventListener ) {
3174 // Standards-based browsers
3175 elem.addEventListener( type, fn, false );
3176 } else if ( elem.attachEvent ) {
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;
3185 fn.call( elem, event );
3191 * @param {Array|NodeList} elems
3192 * @param {string} type
3193 * @param {Function} fn
3195 function addEvents( elems, type, fn ) {
3196 var i = elems.length;
3197 while ( i-- ) {
3198 addEvent( elems[ i ], type, fn );
3202 function hasClass( elem, name ) {
3203 return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
3206 function addClass( elem, name ) {
3207 if ( !hasClass( elem, name ) ) {
3208 elem.className += ( elem.className ? " " : "" ) + name;
3212 function toggleClass( elem, name ) {
3213 if ( hasClass( elem, name ) ) {
3214 removeClass( elem, name );
3215 } else {
3216 addClass( elem, name );
3220 function removeClass( elem, name ) {
3221 var set = " " + elem.className + " ";
3223 // Class name may appear multiple times
3224 while ( set.indexOf( " " + name + " " ) >= 0 ) {
3225 set = set.replace( " " + name + " ", " " );
3228 // trim for prettiness
3229 elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
3232 function id( name ) {
3233 return defined.document && document.getElementById && document.getElementById( name );
3236 function getUrlConfigHtml() {
3237 var i, j, val,
3238 escaped, escapedTooltip,
3239 selection = false,
3240 len = config.urlConfig.length,
3241 urlConfigHtml = "";
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
3252 escaped = escapeText( val.id );
3253 escapedTooltip = escapeText( val.tooltip );
3255 if ( config[ val.id ] === undefined ) {
3256 config[ val.id ] = QUnit.urlParams[ val.id ];
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>";
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>";
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>";
3290 if ( config[ val.id ] && !selection ) {
3291 escaped = escapeText( config[ val.id ] );
3292 urlConfigHtml += "<option value='" + escaped +
3293 "' selected='selected' disabled='disabled'>" + escaped + "</option>";
3295 urlConfigHtml += "</select>";
3299 return urlConfigHtml;
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 = {};
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;
3316 params[ field.name ] = value;
3317 updatedUrl = setUrl( params );
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" );
3327 // It is not necessary to refresh the whole page
3328 window.history.replaceState( null, "", updatedUrl );
3329 } else {
3330 window.location = updatedUrl;
3334 function setUrl( params ) {
3335 var key,
3336 querystring = "?";
3338 params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
3340 for ( key in params ) {
3341 if ( hasOwn.call( params, key ) ) {
3342 if ( params[ key ] === undefined ) {
3343 continue;
3345 querystring += encodeURIComponent( key );
3346 if ( params[ key ] !== true ) {
3347 querystring += "=" + encodeURIComponent( params[ key ] );
3349 querystring += "&";
3352 return location.protocol + "//" + location.host +
3353 location.pathname + querystring.slice( 0, -1 );
3356 function applyUrlParams() {
3357 var selectedModule,
3358 modulesList = id( "qunit-modulefilter" ),
3359 filter = id( "qunit-filter-input" ).value;
3361 selectedModule = modulesList ?
3362 decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
3363 undefined;
3365 window.location = setUrl({
3366 module: ( selectedModule === "" ) ? undefined : selectedModule,
3367 filter: ( filter === "" ) ? undefined : filter,
3369 // Remove testId filter
3370 testId: undefined
3374 function toolbarUrlConfigContainer() {
3375 var urlConfigContainer = document.createElement( "span" );
3377 urlConfigContainer.innerHTML = getUrlConfigHtml();
3378 addClass( urlConfigContainer, "qunit-url-config" );
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 );
3386 return urlConfigContainer;
3389 function toolbarLooseFilter() {
3390 var filter = document.createElement( "form" ),
3391 label = document.createElement( "label" ),
3392 input = document.createElement( "input" ),
3393 button = document.createElement( "button" );
3395 addClass( filter, "qunit-filter" );
3397 label.innerHTML = "Filter: ";
3399 input.type = "text";
3400 input.value = config.filter || "";
3401 input.name = "filter";
3402 input.id = "qunit-filter-input";
3404 button.innerHTML = "Go";
3406 label.appendChild( input );
3408 filter.appendChild( label );
3409 filter.appendChild( button );
3410 addEvent( filter, "submit", function( ev ) {
3411 applyUrlParams();
3413 if ( ev && ev.preventDefault ) {
3414 ev.preventDefault();
3417 return false;
3420 return filter;
3423 function toolbarModuleFilterHtml() {
3424 var i,
3425 moduleFilterHtml = "";
3427 if ( !modulesList.length ) {
3428 return false;
3431 modulesList.sort(function( a, b ) {
3432 return a.localeCompare( b );
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>";
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>";
3446 moduleFilterHtml += "</select>";
3448 return moduleFilterHtml;
3451 function toolbarModuleFilter() {
3452 var toolbar = id( "qunit-testrunner-toolbar" ),
3453 moduleFilter = document.createElement( "span" ),
3454 moduleFilterHtml = toolbarModuleFilterHtml();
3456 if ( !toolbar || !moduleFilterHtml ) {
3457 return false;
3460 moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
3461 moduleFilter.innerHTML = moduleFilterHtml;
3463 addEvent( moduleFilter.lastChild, "change", applyUrlParams );
3465 toolbar.appendChild( moduleFilter );
3468 function appendToolbar() {
3469 var toolbar = id( "qunit-testrunner-toolbar" );
3471 if ( toolbar ) {
3472 toolbar.appendChild( toolbarUrlConfigContainer() );
3473 toolbar.appendChild( toolbarLooseFilter() );
3477 function appendHeader() {
3478 var header = id( "qunit-header" );
3480 if ( header ) {
3481 header.innerHTML = "<a href='" +
3482 setUrl({ filter: undefined, module: undefined, testId: undefined }) +
3483 "'>" + header.innerHTML + "</a> ";
3487 function appendBanner() {
3488 var banner = id( "qunit-banner" );
3490 if ( banner ) {
3491 banner.className = "";
3495 function appendTestResults() {
3496 var tests = id( "qunit-tests" ),
3497 result = id( "qunit-testresult" );
3499 if ( result ) {
3500 result.parentNode.removeChild( result );
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;";
3513 function storeFixture() {
3514 var fixture = id( "qunit-fixture" );
3515 if ( fixture ) {
3516 config.fixture = fixture.innerHTML;
3520 function appendUserAgent() {
3521 var userAgent = id( "qunit-userAgent" );
3523 if ( userAgent ) {
3524 userAgent.innerHTML = "";
3525 userAgent.appendChild(
3526 document.createTextNode(
3527 "QUnit " + QUnit.version + "; " + navigator.userAgent
3533 function appendTestsList( modules ) {
3534 var i, l, x, z, test, moduleObj;
3536 for ( i = 0, l = modules.length; i < l; i++ ) {
3537 moduleObj = modules[ i ];
3539 if ( moduleObj.name ) {
3540 modulesList.push( moduleObj.name );
3543 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
3544 test = moduleObj.tests[ x ];
3546 appendTest( test.name, test.testId, moduleObj.name );
3551 function appendTest( name, testId, moduleName ) {
3552 var title, rerunTrigger, testBlock, assertList,
3553 tests = id( "qunit-tests" );
3555 if ( !tests ) {
3556 return;
3559 title = document.createElement( "strong" );
3560 title.innerHTML = getNameHtml( name, moduleName );
3562 rerunTrigger = document.createElement( "a" );
3563 rerunTrigger.innerHTML = "Rerun";
3564 rerunTrigger.href = setUrl({ testId: testId });
3566 testBlock = document.createElement( "li" );
3567 testBlock.appendChild( title );
3568 testBlock.appendChild( rerunTrigger );
3569 testBlock.id = "qunit-test-output-" + testId;
3571 assertList = document.createElement( "ol" );
3572 assertList.className = "qunit-assert-list";
3574 testBlock.appendChild( assertList );
3576 tests.appendChild( testBlock );
3579 // HTML Reporter initialization and load
3580 QUnit.begin(function( details ) {
3581 var qunit = id( "qunit" );
3583 // Fixture is the only one necessary to run without the #qunit element
3584 storeFixture();
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>";
3595 appendHeader();
3596 appendBanner();
3597 appendTestResults();
3598 appendUserAgent();
3599 appendToolbar();
3600 appendTestsList( details.modules );
3601 toolbarModuleFilter();
3603 if ( qunit && config.hidepassed ) {
3604 addClass( qunit.lastChild, "hidepass" );
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( "" );
3625 if ( banner ) {
3626 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
3629 if ( tests ) {
3630 id( "qunit-testresult" ).innerHTML = html;
3633 if ( config.altertitle && defined.document && document.title ) {
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( " " );
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 );
3653 // scroll back to top to show results
3654 if ( config.scrolltop && window.scrollTo ) {
3655 window.scrollTo( 0, 0 );
3659 function getNameHtml( name, module ) {
3660 var nameHtml = "";
3662 if ( module ) {
3663 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
3666 nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
3668 return nameHtml;
3671 QUnit.testStart(function( details ) {
3672 var running, testBlock, bad;
3674 testBlock = id( "qunit-test-output-" + details.testId );
3675 if ( testBlock ) {
3676 testBlock.className = "running";
3677 } else {
3679 // Report later registered tests
3680 appendTest( details.name, details.testId, details.module );
3683 running = id( "qunit-testresult" );
3684 if ( running ) {
3685 bad = QUnit.config.reorder && defined.sessionStorage &&
3686 +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
3688 running.innerHTML = ( bad ?
3689 "Rerunning previously failed test: <br />" :
3690 "Running: <br />" ) +
3691 getNameHtml( details.name, details.module );
3696 QUnit.log(function( details ) {
3697 var assertList, assertLi,
3698 message, expected, actual,
3699 testItem = id( "qunit-test-output-" + details.testId );
3701 if ( !testItem ) {
3702 return;
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>";
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>";
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>";
3735 if ( details.source ) {
3736 message += "<tr class='test-source'><th>Source: </th><td><pre>" +
3737 escapeText( details.source ) + "</pre></td></tr>";
3740 message += "</table>";
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>";
3750 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3752 assertLi = document.createElement( "li" );
3753 assertLi.className = details.result ? "pass" : "fail";
3754 assertLi.innerHTML = message;
3755 assertList.appendChild( assertLi );
3758 QUnit.testDone(function( details ) {
3759 var testTitle, time, testItem, assertList,
3760 good, bad, testCounts, skipped,
3761 tests = id( "qunit-tests" );
3763 if ( !tests ) {
3764 return;
3767 testItem = id( "qunit-test-output-" + details.testId );
3769 assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
3771 good = details.passed;
3772 bad = details.failed;
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 );
3783 if ( bad === 0 ) {
3784 addClass( assertList, "qunit-collapsed" );
3787 // testItem.firstChild is the test name
3788 testTitle = testItem.firstChild;
3790 testCounts = bad ?
3791 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
3794 testTitle.innerHTML += " <b class='counts'>(" + testCounts +
3795 details.assertions.length + ")</b>";
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" );
3808 testItem.className = bad ? "fail" : "pass";
3810 time = document.createElement( "span" );
3811 time.className = "runtime";
3812 time.innerHTML = details.runtime + " ms";
3813 testItem.insertBefore( time, assertList );
3817 if ( defined.document ) {
3818 if ( document.readyState === "complete" ) {
3819 QUnit.load();
3820 } else {
3821 addEvent( window, "load", QUnit.load );
3823 } else {
3824 config.pageLoaded = true;
3825 config.autorun = true;
3828 })();