5  * Copyright jQuery Foundation and other contributors
 
   6  * Released under the MIT license
 
   7  * http://jquery.org/license
 
   9  * Date: 2015-01-20T19:39Z
 
  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)
 
  23         now = Date.now || function() {
 
  24                 return new Date().getTime();
 
  26         globalStartCalled = false,
 
  28         setTimeout = window.setTimeout,
 
  29         clearTimeout = window.clearTimeout,
 
  31                 document: window.document !== undefined,
 
  32                 setTimeout: window.setTimeout !== undefined,
 
  33                 sessionStorage: (function() {
 
  34                         var x = "qunit-test-string";
 
  36                                 sessionStorage.setItem( x, x );
 
  37                                 sessionStorage.removeItem( x );
 
  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 ) {
 
  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;
 
  64                         } else if ( message ) {
 
  74          * Makes a clone of an object using only Array or Object as base,
 
  75          * and copies over the own enumerable properties.
 
  78          * @return {Object} New object with only the own properties (recursively).
 
  80         objectValues = function( obj ) {
 
  82                         vals = QUnit.is( "array", obj ) ? [] : {};
 
  84                         if ( hasOwn.call( obj, key ) ) {
 
  86                                 vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
 
  95  * Config object: Maintain internal state
 
  96  * Later exposed as QUnit.config
 
  97  * `config` initialized at top of scope
 
 100         // The queue of tests to run
 
 103         // block until document ready
 
 106         // by default, run previously failed tests first
 
 107         // very useful in combination with "Hide passed tests" checked
 
 110         // by default, modify document.title when suite is done
 
 113         // by default, scroll to top of the page when suite is done
 
 116         // when enabled, all tests must call expect()
 
 117         requireExpects: false,
 
 119         // add checkboxes that are persisted in the query-string
 
 120         // when enabled, the id is set to `true` as a `QUnit.config` property
 
 124                         label: "Hide passed tests",
 
 125                         tooltip: "Only show tests and assertions that fail. Stored as query-strings."
 
 129                         label: "Check for Globals",
 
 130                         tooltip: "Enabling this will test if any test introduces new properties on the " +
 
 131                                 "`window` object. Stored as query-strings."
 
 135                         label: "No try-catch",
 
 136                         tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
 
 137                                 "exceptions in IE reasonable. Stored as query-strings."
 
 141         // Set of all modules.
 
 144         // The first unnamed module
 
 153 // Push a loose unnamed module to the modules collection
 
 154 config.modules.push( config.currentModule );
 
 156 // Initialize more QUnit.config and QUnit.urlParams
 
 159                 location = window.location || { search: "", protocol: "file:" },
 
 160                 params = location.search.slice( 1 ).split( "&" ),
 
 161                 length = params.length,
 
 165                 for ( i = 0; i < length; i++ ) {
 
 166                         current = params[ i ].split( "=" );
 
 167                         current[ 0 ] = decodeURIComponent( current[ 0 ] );
 
 169                         // allow just a key to turn on a flag, e.g., test.html?noglobals
 
 170                         current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
 
 171                         if ( urlParams[ current[ 0 ] ] ) {
 
 172                                 urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
 
 174                                 urlParams[ current[ 0 ] ] = current[ 1 ];
 
 179         if ( urlParams.filter === true ) {
 
 180                 delete urlParams.filter;
 
 183         QUnit.urlParams = urlParams;
 
 185         // String search anywhere in moduleName+testName
 
 186         config.filter = urlParams.filter;
 
 189         if ( urlParams.testId ) {
 
 191                 // Ensure that urlParams.testId is an array
 
 192                 urlParams.testId = [].concat( urlParams.testId );
 
 193                 for ( i = 0; i < urlParams.testId.length; i++ ) {
 
 194                         config.testId.push( urlParams.testId[ i ] );
 
 198         // Figure out if we're running the tests from a server or not
 
 199         QUnit.isLocal = location.protocol === "file:";
 
 202 // Root QUnit object.
 
 203 // `QUnit` initialized at top of scope
 
 206         // call on start of module test to prepend name to all tests
 
 207         module: function( name, testEnvironment ) {
 
 208                 var currentModule = {
 
 210                         testEnvironment: testEnvironment,
 
 214                 // DEPRECATED: handles setup/teardown functions,
 
 215                 // beforeEach and afterEach should be used instead
 
 216                 if ( testEnvironment && testEnvironment.setup ) {
 
 217                         testEnvironment.beforeEach = testEnvironment.setup;
 
 218                         delete testEnvironment.setup;
 
 220                 if ( testEnvironment && testEnvironment.teardown ) {
 
 221                         testEnvironment.afterEach = testEnvironment.teardown;
 
 222                         delete testEnvironment.teardown;
 
 225                 config.modules.push( currentModule );
 
 226                 config.currentModule = currentModule;
 
 229         // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
 
 230         asyncTest: function( testName, expected, callback ) {
 
 231                 if ( arguments.length === 2 ) {
 
 236                 QUnit.test( testName, expected, callback, true );
 
 239         test: function( testName, expected, callback, async ) {
 
 242                 if ( arguments.length === 2 ) {
 
 257         skip: function( testName ) {
 
 258                 var test = new Test({
 
 266         // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
 
 267         // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
 
 268         start: function( count ) {
 
 269                 var globalStartAlreadyCalled = globalStartCalled;
 
 271                 if ( !config.current ) {
 
 272                         globalStartCalled = true;
 
 275                                 throw new Error( "Called start() outside of a test context while already started" );
 
 276                         } else if ( globalStartAlreadyCalled || count > 1 ) {
 
 277                                 throw new Error( "Called start() outside of a test context too many times" );
 
 278                         } else if ( config.autostart ) {
 
 279                                 throw new Error( "Called start() outside of a test context when " +
 
 280                                         "QUnit.config.autostart was true" );
 
 281                         } else if ( !config.pageLoaded ) {
 
 283                                 // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
 
 284                                 config.autostart = true;
 
 289                         // If a test is running, adjust its semaphore
 
 290                         config.current.semaphore -= count || 1;
 
 292                         // Don't start until equal number of stop-calls
 
 293                         if ( config.current.semaphore > 0 ) {
 
 297                         // throw an Error if start is called more often than stop
 
 298                         if ( config.current.semaphore < 0 ) {
 
 299                                 config.current.semaphore = 0;
 
 302                                         "Called start() while already started (test's semaphore was 0 already)",
 
 303                                         sourceFromStacktrace( 2 )
 
 312         // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
 
 313         stop: function( count ) {
 
 315                 // If there isn't a test running, don't allow QUnit.stop() to be called
 
 316                 if ( !config.current ) {
 
 317                         throw new Error( "Called stop() outside of a test context" );
 
 320                 // If a test is running, adjust its semaphore
 
 321                 config.current.semaphore += count || 1;
 
 328         // Safe object type checking
 
 329         is: function( type, obj ) {
 
 330                 return QUnit.objectType( obj ) === type;
 
 333         objectType: function( obj ) {
 
 334                 if ( typeof obj === "undefined" ) {
 
 338                 // Consider: typeof null === object
 
 339                 if ( obj === null ) {
 
 343                 var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
 
 344                         type = match && match[ 1 ] || "";
 
 348                                 if ( isNaN( obj ) ) {
 
 358                                 return type.toLowerCase();
 
 360                 if ( typeof obj === "object" ) {
 
 369                 config.pageLoaded = true;
 
 371                 // Initialize the configuration options
 
 373                         stats: { all: 0, bad: 0 },
 
 374                         moduleStats: { all: 0, bad: 0 },
 
 381                 config.blocking = false;
 
 383                 if ( config.autostart ) {
 
 389 // Register logging callbacks
 
 392                 callbacks = [ "begin", "done", "log", "testStart", "testDone",
 
 393                         "moduleStart", "moduleDone" ];
 
 395         function registerLoggingCallback( key ) {
 
 396                 var loggingCallback = function( callback ) {
 
 397                         if ( QUnit.objectType( callback ) !== "function" ) {
 
 399                                         "QUnit logging methods require a callback function as their first parameters."
 
 403                         config.callbacks[ key ].push( callback );
 
 406                 // DEPRECATED: This will be removed on QUnit 2.0.0+
 
 407                 // Stores the registered functions allowing restoring
 
 408                 // at verifyLoggingCallbacks() if modified
 
 409                 loggingCallbacks[ key ] = loggingCallback;
 
 411                 return loggingCallback;
 
 414         for ( i = 0, l = callbacks.length; i < l; i++ ) {
 
 415                 key = callbacks[ i ];
 
 417                 // Initialize key collection of logging callback
 
 418                 if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
 
 419                         config.callbacks[ key ] = [];
 
 422                 QUnit[ key ] = registerLoggingCallback( key );
 
 426 // `onErrorFnPrev` initialized at top of scope
 
 427 // Preserve other handlers
 
 428 onErrorFnPrev = window.onerror;
 
 430 // Cover uncaught exceptions
 
 431 // Returning true will suppress the default browser handler,
 
 432 // returning false will let it run.
 
 433 window.onerror = function( error, filePath, linerNr ) {
 
 435         if ( onErrorFnPrev ) {
 
 436                 ret = onErrorFnPrev( error, filePath, linerNr );
 
 439         // Treat return value as window.onerror itself does,
 
 440         // Only do our handling if not suppressed.
 
 441         if ( ret !== true ) {
 
 442                 if ( QUnit.config.current ) {
 
 443                         if ( QUnit.config.current.ignoreGlobalErrors ) {
 
 446                         QUnit.pushFailure( error, filePath + ":" + linerNr );
 
 448                         QUnit.test( "global failure", extend(function() {
 
 449                                 QUnit.pushFailure( error, filePath + ":" + linerNr );
 
 450                         }, { validTest: true } ) );
 
 461         config.autorun = true;
 
 463         // Log the last module results
 
 464         if ( config.previousModule ) {
 
 465                 runLoggingCallbacks( "moduleDone", {
 
 466                         name: config.previousModule.name,
 
 467                         tests: config.previousModule.tests,
 
 468                         failed: config.moduleStats.bad,
 
 469                         passed: config.moduleStats.all - config.moduleStats.bad,
 
 470                         total: config.moduleStats.all,
 
 471                         runtime: now() - config.moduleStats.started
 
 474         delete config.previousModule;
 
 476         runtime = now() - config.started;
 
 477         passed = config.stats.all - config.stats.bad;
 
 479         runLoggingCallbacks( "done", {
 
 480                 failed: config.stats.bad,
 
 482                 total: config.stats.all,
 
 487 // Doesn't support IE6 to IE9
 
 488 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
 
 489 function extractStacktrace( e, offset ) {
 
 490         offset = offset === undefined ? 4 : offset;
 
 492         var stack, include, i;
 
 494         if ( e.stacktrace ) {
 
 497                 return e.stacktrace.split( "\n" )[ offset + 3 ];
 
 498         } else if ( e.stack ) {
 
 500                 // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
 
 501                 stack = e.stack.split( "\n" );
 
 502                 if ( /^error$/i.test( stack[ 0 ] ) ) {
 
 507                         for ( i = offset; i < stack.length; i++ ) {
 
 508                                 if ( stack[ i ].indexOf( fileName ) !== -1 ) {
 
 511                                 include.push( stack[ i ] );
 
 513                         if ( include.length ) {
 
 514                                 return include.join( "\n" );
 
 517                 return stack[ offset ];
 
 518         } else if ( e.sourceURL ) {
 
 521                 // exclude useless self-reference for generated Error objects
 
 522                 if ( /qunit.js$/.test( e.sourceURL ) ) {
 
 526                 // for actual exceptions, this is useful
 
 527                 return e.sourceURL + ":" + e.line;
 
 531 function sourceFromStacktrace( offset ) {
 
 537                         // This should already be true in most browsers
 
 541         return extractStacktrace( e, offset );
 
 544 function synchronize( callback, last ) {
 
 545         if ( QUnit.objectType( callback ) === "array" ) {
 
 546                 while ( callback.length ) {
 
 547                         synchronize( callback.shift() );
 
 551         config.queue.push( callback );
 
 553         if ( config.autorun && !config.blocking ) {
 
 558 function process( last ) {
 
 563         config.depth = ( config.depth || 0 ) + 1;
 
 565         while ( config.queue.length && !config.blocking ) {
 
 566                 if ( !defined.setTimeout || config.updateRate <= 0 ||
 
 567                                 ( ( now() - start ) < config.updateRate ) ) {
 
 568                         if ( config.current ) {
 
 570                                 // Reset async tracking for each phase of the Test lifecycle
 
 571                                 config.current.usedAsync = false;
 
 573                         config.queue.shift()();
 
 575                         setTimeout( next, 13 );
 
 580         if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
 
 589         // If the test run hasn't officially begun yet
 
 590         if ( !config.started ) {
 
 592                 // Record the time of the test run's beginning
 
 593                 config.started = now();
 
 595                 verifyLoggingCallbacks();
 
 597                 // Delete the loose unnamed module if unused.
 
 598                 if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
 
 599                         config.modules.shift();
 
 602                 // Avoid unnecessary information by not logging modules' test environments
 
 603                 for ( i = 0, l = config.modules.length; i < l; i++ ) {
 
 605                                 name: config.modules[ i ].name,
 
 606                                 tests: config.modules[ i ].tests
 
 610                 // The test run is officially beginning now
 
 611                 runLoggingCallbacks( "begin", {
 
 612                         totalTests: Test.count,
 
 617         config.blocking = false;
 
 621 function resumeProcessing() {
 
 624         // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
 
 625         if ( defined.setTimeout ) {
 
 626                 setTimeout(function() {
 
 627                         if ( config.current && config.current.semaphore > 0 ) {
 
 630                         if ( config.timeout ) {
 
 631                                 clearTimeout( config.timeout );
 
 641 function pauseProcessing() {
 
 642         config.blocking = true;
 
 644         if ( config.testTimeout && defined.setTimeout ) {
 
 645                 clearTimeout( config.timeout );
 
 646                 config.timeout = setTimeout(function() {
 
 647                         if ( config.current ) {
 
 648                                 config.current.semaphore = 0;
 
 649                                 QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
 
 651                                 throw new Error( "Test timed out" );
 
 654                 }, config.testTimeout );
 
 658 function saveGlobal() {
 
 659         config.pollution = [];
 
 661         if ( config.noglobals ) {
 
 662                 for ( var key in window ) {
 
 663                         if ( hasOwn.call( window, key ) ) {
 
 664                                 // in Opera sometimes DOM element ids show up here, ignore them
 
 665                                 if ( /^qunit-test-output/.test( key ) ) {
 
 668                                 config.pollution.push( key );
 
 674 function checkPollution() {
 
 677                 old = config.pollution;
 
 681         newGlobals = diff( config.pollution, old );
 
 682         if ( newGlobals.length > 0 ) {
 
 683                 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
 
 686         deletedGlobals = diff( old, config.pollution );
 
 687         if ( deletedGlobals.length > 0 ) {
 
 688                 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
 
 692 // returns a new Array with the elements that are in a but not in b
 
 693 function diff( a, b ) {
 
 697         for ( i = 0; i < result.length; i++ ) {
 
 698                 for ( j = 0; j < b.length; j++ ) {
 
 699                         if ( result[ i ] === b[ j ] ) {
 
 700                                 result.splice( i, 1 );
 
 709 function extend( a, b, undefOnly ) {
 
 710         for ( var prop in b ) {
 
 711                 if ( hasOwn.call( b, prop ) ) {
 
 713                         // Avoid "Member not found" error in IE8 caused by messing with window.constructor
 
 714                         if ( !( prop === "constructor" && a === window ) ) {
 
 715                                 if ( b[ prop ] === undefined ) {
 
 717                                 } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
 
 718                                         a[ prop ] = b[ prop ];
 
 727 function runLoggingCallbacks( key, args ) {
 
 730         callbacks = config.callbacks[ key ];
 
 731         for ( i = 0, l = callbacks.length; i < l; i++ ) {
 
 732                 callbacks[ i ]( args );
 
 736 // DEPRECATED: This will be removed on 2.0.0+
 
 737 // This function verifies if the loggingCallbacks were modified by the user
 
 738 // If so, it will restore it, assign the given callback and print a console warning
 
 739 function verifyLoggingCallbacks() {
 
 740         var loggingCallback, userCallback;
 
 742         for ( loggingCallback in loggingCallbacks ) {
 
 743                 if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
 
 745                         userCallback = QUnit[ loggingCallback ];
 
 747                         // Restore the callback function
 
 748                         QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
 
 750                         // Assign the deprecated given callback
 
 751                         QUnit[ loggingCallback ]( userCallback );
 
 753                         if ( window.console && window.console.warn ) {
 
 755                                         "QUnit." + loggingCallback + " was replaced with a new value.\n" +
 
 756                                         "Please, check out the documentation on how to apply logging callbacks.\n" +
 
 757                                         "Reference: http://api.qunitjs.com/category/callbacks/"
 
 765 function inArray( elem, array ) {
 
 766         if ( array.indexOf ) {
 
 767                 return array.indexOf( elem );
 
 770         for ( var i = 0, length = array.length; i < length; i++ ) {
 
 771                 if ( array[ i ] === elem ) {
 
 779 function Test( settings ) {
 
 784         extend( this, settings );
 
 785         this.assertions = [];
 
 787         this.usedAsync = false;
 
 788         this.module = config.currentModule;
 
 789         this.stack = sourceFromStacktrace( 3 );
 
 791         // Register unique strings
 
 792         for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
 
 793                 if ( this.module.tests[ i ].name === this.testName ) {
 
 794                         this.testName += " ";
 
 798         this.testId = generateHash( this.module.name, this.testName );
 
 800         this.module.tests.push({
 
 805         if ( settings.skip ) {
 
 807                 // Skipped tests will fully ignore any sent callback
 
 808                 this.callback = function() {};
 
 812                 this.assert = new Assert( this );
 
 822                         // Emit moduleStart when we're switching from one module to another
 
 823                         this.module !== config.previousModule ||
 
 825                                 // They could be equal (both undefined) but if the previousModule property doesn't
 
 826                                 // yet exist it means this is the first test in a suite that isn't wrapped in a
 
 827                                 // module, in which case we'll just emit a moduleStart event for 'undefined'.
 
 828                                 // Without this, reporters can get testStart before moduleStart  which is a problem.
 
 829                                 !hasOwn.call( config, "previousModule" )
 
 831                         if ( hasOwn.call( config, "previousModule" ) ) {
 
 832                                 runLoggingCallbacks( "moduleDone", {
 
 833                                         name: config.previousModule.name,
 
 834                                         tests: config.previousModule.tests,
 
 835                                         failed: config.moduleStats.bad,
 
 836                                         passed: config.moduleStats.all - config.moduleStats.bad,
 
 837                                         total: config.moduleStats.all,
 
 838                                         runtime: now() - config.moduleStats.started
 
 841                         config.previousModule = this.module;
 
 842                         config.moduleStats = { all: 0, bad: 0, started: now() };
 
 843                         runLoggingCallbacks( "moduleStart", {
 
 844                                 name: this.module.name,
 
 845                                 tests: this.module.tests
 
 849                 config.current = this;
 
 851                 this.testEnvironment = extend( {}, this.module.testEnvironment );
 
 852                 delete this.testEnvironment.beforeEach;
 
 853                 delete this.testEnvironment.afterEach;
 
 855                 this.started = now();
 
 856                 runLoggingCallbacks( "testStart", {
 
 858                         module: this.module.name,
 
 862                 if ( !config.pollution ) {
 
 870                 config.current = this;
 
 876                 this.callbackStarted = now();
 
 878                 if ( config.notrycatch ) {
 
 879                         promise = this.callback.call( this.testEnvironment, this.assert );
 
 880                         this.resolvePromise( promise );
 
 885                         promise = this.callback.call( this.testEnvironment, this.assert );
 
 886                         this.resolvePromise( promise );
 
 888                         this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
 
 889                                 this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
 
 891                         // else next test will carry the responsibility
 
 894                         // Restart the tests if they're blocking
 
 895                         if ( config.blocking ) {
 
 905         queueHook: function( hook, hookName ) {
 
 908                 return function runHook() {
 
 909                         config.current = test;
 
 910                         if ( config.notrycatch ) {
 
 911                                 promise = hook.call( test.testEnvironment, test.assert );
 
 912                                 test.resolvePromise( promise, hookName );
 
 916                                 promise = hook.call( test.testEnvironment, test.assert );
 
 917                                 test.resolvePromise( promise, hookName );
 
 919                                 test.pushFailure( hookName + " failed on " + test.testName + ": " +
 
 920                                         ( error.message || error ), extractStacktrace( error, 0 ) );
 
 925         // Currently only used for module level hooks, can be used to add global level ones
 
 926         hooks: function( handler ) {
 
 929                 // Hooks are ignored on skipped tests
 
 934                 if ( this.module.testEnvironment &&
 
 935                                 QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
 
 936                         hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
 
 943                 config.current = this;
 
 944                 if ( config.requireExpects && this.expected === null ) {
 
 945                         this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
 
 946                                 "not called.", this.stack );
 
 947                 } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
 
 948                         this.pushFailure( "Expected " + this.expected + " assertions, but " +
 
 949                                 this.assertions.length + " were run", this.stack );
 
 950                 } else if ( this.expected === null && !this.assertions.length ) {
 
 951                         this.pushFailure( "Expected at least one assertion, but none were run - call " +
 
 952                                 "expect(0) to accept zero assertions.", this.stack );
 
 958                 this.runtime = now() - this.started;
 
 959                 config.stats.all += this.assertions.length;
 
 960                 config.moduleStats.all += this.assertions.length;
 
 962                 for ( i = 0; i < this.assertions.length; i++ ) {
 
 963                         if ( !this.assertions[ i ].result ) {
 
 966                                 config.moduleStats.bad++;
 
 970                 runLoggingCallbacks( "testDone", {
 
 972                         module: this.module.name,
 
 973                         skipped: !!this.skip,
 
 975                         passed: this.assertions.length - bad,
 
 976                         total: this.assertions.length,
 
 977                         runtime: this.runtime,
 
 980                         assertions: this.assertions,
 
 983                         // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
 
 984                         duration: this.runtime
 
 987                 // QUnit.reset() is deprecated and will be replaced for a new
 
 988                 // fixture reset function on QUnit 2.0/2.1.
 
 989                 // It's still called here for backwards compatibility handling
 
 992                 config.current = undefined;
 
 999                 if ( !this.valid() ) {
 
1005                         // each of these can by async
 
1011                                 test.hooks( "beforeEach" ),
 
1017                                 test.hooks( "afterEach" ).reverse(),
 
1028                 // `bad` initialized at top of scope
 
1029                 // defer when previous test run passed, if storage is available
 
1030                 bad = QUnit.config.reorder && defined.sessionStorage &&
 
1031                                 +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
 
1036                         synchronize( run, true );
 
1040         push: function( result, actual, expected, message ) {
 
1043                                 module: this.module.name,
 
1044                                 name: this.testName,
 
1049                                 testId: this.testId,
 
1050                                 runtime: now() - this.started
 
1054                         source = sourceFromStacktrace();
 
1057                                 details.source = source;
 
1061                 runLoggingCallbacks( "log", details );
 
1063                 this.assertions.push({
 
1069         pushFailure: function( message, source, actual ) {
 
1070                 if ( !this instanceof Test ) {
 
1071                         throw new Error( "pushFailure() assertion outside test context, was " +
 
1072                                 sourceFromStacktrace( 2 ) );
 
1076                                 module: this.module.name,
 
1077                                 name: this.testName,
 
1079                                 message: message || "error",
 
1080                                 actual: actual || null,
 
1081                                 testId: this.testId,
 
1082                                 runtime: now() - this.started
 
1086                         details.source = source;
 
1089                 runLoggingCallbacks( "log", details );
 
1091                 this.assertions.push({
 
1097         resolvePromise: function( promise, phase ) {
 
1100                 if ( promise != null ) {
 
1101                         then = promise.then;
 
1102                         if ( QUnit.objectType( then ) === "function" ) {
 
1108                                                 message = "Promise rejected " +
 
1109                                                         ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
 
1110                                                         " " + test.testName + ": " + ( error.message || error );
 
1111                                                 test.pushFailure( message, extractStacktrace( error, 0 ) );
 
1113                                                 // else next test will carry the responsibility
 
1126                         filter = config.filter,
 
1127                         module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
 
1128                         fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
 
1130                 // Internally-generated tests are always valid
 
1131                 if ( this.callback && this.callback.validTest ) {
 
1135                 if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
 
1139                 if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
 
1147                 include = filter.charAt( 0 ) !== "!";
 
1149                         filter = filter.toLowerCase().slice( 1 );
 
1152                 // If the filter matches, we need to honour include
 
1153                 if ( fullName.indexOf( filter ) !== -1 ) {
 
1157                 // Otherwise, do the opposite
 
1163 // Resets the test setup. Useful for tests that modify the DOM.
 
1165 DEPRECATED: Use multiple tests instead of resetting inside a test.
 
1166 Use testStart or testDone for custom cleanup.
 
1167 This method will throw an error in 2.0, and will be removed in 2.1
 
1169 QUnit.reset = function() {
 
1171         // Return on non-browser environments
 
1172         // This is necessary to not break on node tests
 
1173         if ( typeof window === "undefined" ) {
 
1177         var fixture = defined.document && document.getElementById &&
 
1178                         document.getElementById( "qunit-fixture" );
 
1181                 fixture.innerHTML = config.fixture;
 
1185 QUnit.pushFailure = function() {
 
1186         if ( !QUnit.config.current ) {
 
1187                 throw new Error( "pushFailure() assertion outside test context, in " +
 
1188                         sourceFromStacktrace( 2 ) );
 
1191         // Gets current test obj
 
1192         var currentTest = QUnit.config.current;
 
1194         return currentTest.pushFailure.apply( currentTest, arguments );
 
1197 // Based on Java's String.hashCode, a simple but not
 
1198 // rigorously collision resistant hashing function
 
1199 function generateHash( module, testName ) {
 
1203                 str = module + "\x1C" + testName,
 
1206         for ( ; i < len; i++ ) {
 
1207                 hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
 
1211         // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
 
1212         // strictly necessary but increases user understanding that the id is a SHA-like hash
 
1213         hex = ( 0x100000000 + hash ).toString( 16 );
 
1214         if ( hex.length < 8 ) {
 
1215                 hex = "0000000" + hex;
 
1218         return hex.slice( -8 );
 
1221 function Assert( testContext ) {
 
1222         this.test = testContext;
 
1226 QUnit.assert = Assert.prototype = {
 
1228         // Specify the number of expected assertions to guarantee that failed test
 
1229         // (no assertions are run at all) don't slip through.
 
1230         expect: function( asserts ) {
 
1231                 if ( arguments.length === 1 ) {
 
1232                         this.test.expected = asserts;
 
1234                         return this.test.expected;
 
1238         // Increment this Test's semaphore counter, then return a single-use function that
 
1239         // decrements that counter a maximum of once.
 
1241                 var test = this.test,
 
1244                 test.semaphore += 1;
 
1245                 test.usedAsync = true;
 
1248                 return function done() {
 
1250                                 test.semaphore -= 1;
 
1254                                 test.pushFailure( "Called the callback returned from `assert.async` more than once",
 
1255                                         sourceFromStacktrace( 2 ) );
 
1260         // Exports test.push() to the user API
 
1261         push: function( /* result, actual, expected, message */ ) {
 
1263                         currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
 
1265                 // Backwards compatibility fix.
 
1266                 // Allows the direct use of global exported assertions and QUnit.assert.*
 
1267                 // Although, it's use is not recommended as it can leak assertions
 
1268                 // to other tests from async tests, because we only get a reference to the current test,
 
1269                 // not exactly the test where assertion were intended to be called.
 
1270                 if ( !currentTest ) {
 
1271                         throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
 
1274                 if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
 
1275                         currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
 
1276                                 sourceFromStacktrace( 2 ) );
 
1278                         // Allow this assertion to continue running anyway...
 
1281                 if ( !( assert instanceof Assert ) ) {
 
1282                         assert = currentTest.assert;
 
1284                 return assert.test.push.apply( assert.test, arguments );
 
1288          * Asserts rough true-ish result.
 
1291          * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
 
1293         ok: function( result, message ) {
 
1294                 message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
 
1295                         QUnit.dump.parse( result ) );
 
1296                 this.push( !!result, result, true, message );
 
1300          * Assert that the first two arguments are equal, with an optional message.
 
1301          * Prints out both actual and expected values.
 
1304          * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
 
1306         equal: function( actual, expected, message ) {
 
1307                 /*jshint eqeqeq:false */
 
1308                 this.push( expected == actual, actual, expected, message );
 
1315         notEqual: function( actual, expected, message ) {
 
1316                 /*jshint eqeqeq:false */
 
1317                 this.push( expected != actual, actual, expected, message );
 
1324         propEqual: function( actual, expected, message ) {
 
1325                 actual = objectValues( actual );
 
1326                 expected = objectValues( expected );
 
1327                 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
 
1331          * @name notPropEqual
 
1334         notPropEqual: function( actual, expected, message ) {
 
1335                 actual = objectValues( actual );
 
1336                 expected = objectValues( expected );
 
1337                 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
 
1344         deepEqual: function( actual, expected, message ) {
 
1345                 this.push( QUnit.equiv( actual, expected ), actual, expected, message );
 
1349          * @name notDeepEqual
 
1352         notDeepEqual: function( actual, expected, message ) {
 
1353                 this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
 
1360         strictEqual: function( actual, expected, message ) {
 
1361                 this.push( expected === actual, actual, expected, message );
 
1365          * @name notStrictEqual
 
1368         notStrictEqual: function( actual, expected, message ) {
 
1369                 this.push( expected !== actual, actual, expected, message );
 
1372         "throws": function( block, expected, message ) {
 
1373                 var actual, expectedType,
 
1374                         expectedOutput = expected,
 
1377                 // 'expected' is optional unless doing string comparison
 
1378                 if ( message == null && typeof expected === "string" ) {
 
1383                 this.test.ignoreGlobalErrors = true;
 
1385                         block.call( this.test.testEnvironment );
 
1389                 this.test.ignoreGlobalErrors = false;
 
1392                         expectedType = QUnit.objectType( expected );
 
1394                         // we don't want to validate thrown error
 
1397                                 expectedOutput = null;
 
1399                         // expected is a regexp
 
1400                         } else if ( expectedType === "regexp" ) {
 
1401                                 ok = expected.test( errorString( actual ) );
 
1403                         // expected is a string
 
1404                         } else if ( expectedType === "string" ) {
 
1405                                 ok = expected === errorString( actual );
 
1407                         // expected is a constructor, maybe an Error constructor
 
1408                         } else if ( expectedType === "function" && actual instanceof expected ) {
 
1411                         // expected is an Error object
 
1412                         } else if ( expectedType === "object" ) {
 
1413                                 ok = actual instanceof expected.constructor &&
 
1414                                         actual.name === expected.name &&
 
1415                                         actual.message === expected.message;
 
1417                         // expected is a validation function which returns true if validation passed
 
1418                         } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
 
1419                                 expectedOutput = null;
 
1423                         this.push( ok, actual, expectedOutput, message );
 
1425                         this.test.pushFailure( message, null, "No exception was thrown." );
 
1430 // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
 
1431 // Known to us are: Closure Compiler, Narwhal
 
1433         /*jshint sub:true */
 
1434         Assert.prototype.raises = Assert.prototype[ "throws" ];
 
1437 // Test for equality any JavaScript type.
 
1438 // Author: Philippe Rathé <prathe@gmail.com>
 
1439 QUnit.equiv = (function() {
 
1441         // Call the o related callback with the given arguments.
 
1442         function bindCallbacks( o, callbacks, args ) {
 
1443                 var prop = QUnit.objectType( o );
 
1445                         if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
 
1446                                 return callbacks[ prop ].apply( callbacks, args );
 
1448                                 return callbacks[ prop ]; // or undefined
 
1453         // the real equiv function
 
1456                 // stack to decide between skip/abort functions
 
1459                 // stack to avoiding loops from circular referencing
 
1463                 getProto = Object.getPrototypeOf || function( obj ) {
 
1464                         /* jshint camelcase: false, proto: true */
 
1465                         return obj.__proto__;
 
1467                 callbacks = (function() {
 
1469                         // for string, boolean, number and null
 
1470                         function useStrictEquality( b, a ) {
 
1472                                 /*jshint eqeqeq:false */
 
1473                                 if ( b instanceof a.constructor || a instanceof b.constructor ) {
 
1475                                         // to catch short annotation VS 'new' annotation of a
 
1478                                         // var j = new Number(1);
 
1486                                 "string": useStrictEquality,
 
1487                                 "boolean": useStrictEquality,
 
1488                                 "number": useStrictEquality,
 
1489                                 "null": useStrictEquality,
 
1490                                 "undefined": useStrictEquality,
 
1492                                 "nan": function( b ) {
 
1496                                 "date": function( b, a ) {
 
1497                                         return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
 
1500                                 "regexp": function( b, a ) {
 
1501                                         return QUnit.objectType( b ) === "regexp" &&
 
1504                                                 a.source === b.source &&
 
1506                                                 // and its modifiers
 
1507                                                 a.global === b.global &&
 
1510                                                 a.ignoreCase === b.ignoreCase &&
 
1511                                                 a.multiline === b.multiline &&
 
1512                                                 a.sticky === b.sticky;
 
1515                                 // - skip when the property is a method of an instance (OOP)
 
1516                                 // - abort otherwise,
 
1517                                 // initial === would have catch identical references anyway
 
1518                                 "function": function() {
 
1519                                         var caller = callers[ callers.length - 1 ];
 
1520                                         return caller !== Object && typeof caller !== "undefined";
 
1523                                 "array": function( b, a ) {
 
1524                                         var i, j, len, loop, aCircular, bCircular;
 
1526                                         // b could be an object literal here
 
1527                                         if ( QUnit.objectType( b ) !== "array" ) {
 
1532                                         if ( len !== b.length ) {
 
1537                                         // track reference to avoid circular references
 
1540                                         for ( i = 0; i < len; i++ ) {
 
1542                                                 for ( j = 0; j < parents.length; j++ ) {
 
1543                                                         aCircular = parents[ j ] === a[ i ];
 
1544                                                         bCircular = parentsB[ j ] === b[ i ];
 
1545                                                         if ( aCircular || bCircular ) {
 
1546                                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
 
1555                                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
 
1566                                 "object": function( b, a ) {
 
1568                                         /*jshint forin:false */
 
1569                                         var i, j, loop, aCircular, bCircular,
 
1575                                         // comparing constructors is more strict than using
 
1577                                         if ( a.constructor !== b.constructor ) {
 
1579                                                 // Allow objects with no prototype to be equivalent to
 
1580                                                 // objects with Object as their constructor.
 
1581                                                 if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
 
1582                                                         ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
 
1587                                         // stack constructor before traversing properties
 
1588                                         callers.push( a.constructor );
 
1590                                         // track reference to avoid circular references
 
1594                                         // be strict: don't ensure hasOwnProperty and go deep
 
1597                                                 for ( j = 0; j < parents.length; j++ ) {
 
1598                                                         aCircular = parents[ j ] === a[ i ];
 
1599                                                         bCircular = parentsB[ j ] === b[ i ];
 
1600                                                         if ( aCircular || bCircular ) {
 
1601                                                                 if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
 
1609                                                 aProperties.push( i );
 
1610                                                 if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
 
1618                                         callers.pop(); // unstack, we are done
 
1621                                                 bProperties.push( i ); // collect b's properties
 
1624                                         // Ensures identical properties name
 
1625                                         return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
 
1630         innerEquiv = function() { // can take multiple arguments
 
1631                 var args = [].slice.apply( arguments );
 
1632                 if ( args.length < 2 ) {
 
1633                         return true; // end transition
 
1636                 return ( (function( a, b ) {
 
1638                                 return true; // catch the most you can
 
1639                         } else if ( a === null || b === null || typeof a === "undefined" ||
 
1640                                         typeof b === "undefined" ||
 
1641                                         QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
 
1643                                 // don't lose time with error prone cases
 
1646                                 return bindCallbacks( a, callbacks, [ b, a ] );
 
1649                         // apply transition with (1..n) arguments
 
1650                 }( args[ 0 ], args[ 1 ] ) ) &&
 
1651                         innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
 
1657 // Based on jsDump by Ariel Flesler
 
1658 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
 
1659 QUnit.dump = (function() {
 
1660         function quote( str ) {
 
1661                 return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
 
1663         function literal( o ) {
 
1666         function join( pre, arr, post ) {
 
1667                 var s = dump.separator(),
 
1668                         base = dump.indent(),
 
1669                         inner = dump.indent( 1 );
 
1671                         arr = arr.join( "," + s + inner );
 
1676                 return [ pre, inner + arr, base + post ].join( s );
 
1678         function array( arr, stack ) {
 
1680                         ret = new Array( i );
 
1682                 if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
 
1683                         return "[object Array]";
 
1688                         ret[ i ] = this.parse( arr[ i ], undefined, stack );
 
1691                 return join( "[", ret, "]" );
 
1694         var reName = /^function (\w+)/,
 
1697                         // objType is used mostly internally, you can fix a (custom) type in advance
 
1698                         parse: function( obj, objType, stack ) {
 
1699                                 stack = stack || [];
 
1700                                 var res, parser, parserType,
 
1701                                         inStack = inArray( obj, stack );
 
1703                                 if ( inStack !== -1 ) {
 
1704                                         return "recursion(" + ( inStack - stack.length ) + ")";
 
1707                                 objType = objType || this.typeOf( obj  );
 
1708                                 parser = this.parsers[ objType ];
 
1709                                 parserType = typeof parser;
 
1711                                 if ( parserType === "function" ) {
 
1713                                         res = parser.call( this, obj, stack );
 
1717                                 return ( parserType === "string" ) ? parser : this.parsers.error;
 
1719                         typeOf: function( obj ) {
 
1721                                 if ( obj === null ) {
 
1723                                 } else if ( typeof obj === "undefined" ) {
 
1725                                 } else if ( QUnit.is( "regexp", obj ) ) {
 
1727                                 } else if ( QUnit.is( "date", obj ) ) {
 
1729                                 } else if ( QUnit.is( "function", obj ) ) {
 
1731                                 } else if ( obj.setInterval !== undefined &&
 
1732                                                 obj.document !== undefined &&
 
1733                                                 obj.nodeType === undefined ) {
 
1735                                 } else if ( obj.nodeType === 9 ) {
 
1737                                 } else if ( obj.nodeType ) {
 
1742                                         toString.call( obj ) === "[object Array]" ||
 
1745                                         ( typeof obj.length === "number" && obj.item !== undefined &&
 
1746                                         ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
 
1747                                         obj[ 0 ] === undefined ) ) )
 
1750                                 } else if ( obj.constructor === Error.prototype.constructor ) {
 
1757                         separator: function() {
 
1758                                 return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " ";
 
1760                         // extra can be a number, shortcut for increasing-calling-decreasing
 
1761                         indent: function( extra ) {
 
1762                                 if ( !this.multiline ) {
 
1765                                 var chr = this.indentChar;
 
1767                                         chr = chr.replace( /\t/g, "   " ).replace( / /g, " " );
 
1769                                 return new Array( this.depth + ( extra || 0 ) ).join( chr );
 
1772                                 this.depth += a || 1;
 
1774                         down: function( a ) {
 
1775                                 this.depth -= a || 1;
 
1777                         setParser: function( name, parser ) {
 
1778                                 this.parsers[ name ] = parser;
 
1780                         // The next 3 are exposed so you can use them
 
1788                         // This is the list of parsers, to modify them, use dump.setParser
 
1791                                 document: "[Document]",
 
1792                                 error: function( error ) {
 
1793                                         return "Error(\"" + error.message + "\")";
 
1795                                 unknown: "[Unknown]",
 
1797                                 "undefined": "undefined",
 
1798                                 "function": function( fn ) {
 
1799                                         var ret = "function",
 
1801                                                 // functions never have name in IE
 
1802                                                 name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
 
1809                                         ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
 
1810                                         return join( ret, dump.parse( fn, "functionCode" ), "}" );
 
1815                                 object: function( map, stack ) {
 
1816                                         var keys, key, val, i, nonEnumerableProperties,
 
1819                                         if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
 
1820                                                 return "[object Object]";
 
1825                                         for ( key in map ) {
 
1829                                         // Some properties are not always enumerable on Error objects.
 
1830                                         nonEnumerableProperties = [ "message", "name" ];
 
1831                                         for ( i in nonEnumerableProperties ) {
 
1832                                                 key = nonEnumerableProperties[ i ];
 
1833                                                 if ( key in map && !( key in keys ) ) {
 
1838                                         for ( i = 0; i < keys.length; i++ ) {
 
1841                                                 ret.push( dump.parse( key, "key" ) + ": " +
 
1842                                                         dump.parse( val, undefined, stack ) );
 
1845                                         return join( "{", ret, "}" );
 
1847                                 node: function( node ) {
 
1849                                                 open = dump.HTML ? "<" : "<",
 
1850                                                 close = dump.HTML ? ">" : ">",
 
1851                                                 tag = node.nodeName.toLowerCase(),
 
1853                                                 attrs = node.attributes;
 
1856                                                 for ( i = 0, len = attrs.length; i < len; i++ ) {
 
1857                                                         val = attrs[ i ].nodeValue;
 
1859                                                         // IE6 includes all attributes in .attributes, even ones not explicitly
 
1860                                                         // set. Those have values like undefined, null, 0, false, "" or
 
1862                                                         if ( val && val !== "inherit" ) {
 
1863                                                                 ret += " " + attrs[ i ].nodeName + "=" +
 
1864                                                                         dump.parse( val, "attribute" );
 
1870                                         // Show content of TextNode or CDATASection
 
1871                                         if ( node.nodeType === 3 || node.nodeType === 4 ) {
 
1872                                                 ret += node.nodeValue;
 
1875                                         return ret + open + "/" + tag + close;
 
1878                                 // function calls it internally, it's the arguments part of the function
 
1879                                 functionArgs: function( fn ) {
 
1887                                         args = new Array( l );
 
1891                                                 args[ l ] = String.fromCharCode( 97 + l );
 
1893                                         return " " + args.join( ", " ) + " ";
 
1895                                 // object calls it internally, the key part of an item in a map
 
1897                                 // function calls it internally, it's the content of the function
 
1898                                 functionCode: "[code]",
 
1899                                 // node calls it internally, it's an html attribute value
 
1907                         // if true, entities are escaped ( <, >, \t, space and \n )
 
1911                         // if true, items in a collection, are separated by a \n, else just a space.
 
1919 QUnit.jsDump = QUnit.dump;
 
1921 // For browser, export only select globals
 
1922 if ( typeof window !== "undefined" ) {
 
1925         // Extend assert methods to QUnit and Global scope through Backwards compatibility
 
1928                         assertions = Assert.prototype;
 
1930                 function applyCurrent( current ) {
 
1932                                 var assert = new Assert( QUnit.config.current );
 
1933                                 current.apply( assert, arguments );
 
1937                 for ( i in assertions ) {
 
1938                         QUnit[ i ] = applyCurrent( assertions[ i ] );
 
1963                 for ( i = 0, l = keys.length; i < l; i++ ) {
 
1964                         window[ keys[ i ] ] = QUnit[ keys[ i ] ];
 
1968         window.QUnit = QUnit;
 
1972 if ( typeof module !== "undefined" && module && module.exports ) {
 
1973         module.exports = QUnit;
 
1975         // For consistency with CommonJS environments' exports
 
1976         module.exports.QUnit = QUnit;
 
1979 // For CommonJS with exports, but without module.exports, like Rhino
 
1980 if ( typeof exports !== "undefined" && exports ) {
 
1981         exports.QUnit = QUnit;
 
1984 // Get a reference to the global object, like window in browsers
 
1989 /*istanbul ignore next */
 
1990 // jscs:disable maximumLineLength
 
1992  * Javascript Diff Algorithm
 
1993  *  By John Resig (http://ejohn.org/)
 
1994  *  Modified by Chu Alan "sprite"
 
1996  * Released under the MIT license.
 
1999  *  http://ejohn.org/projects/javascript-diff-algorithm/
 
2001  * Usage: QUnit.diff(expected, actual)
 
2003  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
 
2005 QUnit.diff = (function() {
 
2006         var hasOwn = Object.prototype.hasOwnProperty;
 
2008         /*jshint eqeqeq:false, eqnull:true */
 
2009         function diff( o, n ) {
 
2014                 for ( i = 0; i < n.length; i++ ) {
 
2015                         if ( !hasOwn.call( ns, n[ i ] ) ) {
 
2021                         ns[ n[ i ] ].rows.push( i );
 
2024                 for ( i = 0; i < o.length; i++ ) {
 
2025                         if ( !hasOwn.call( os, o[ i ] ) ) {
 
2031                         os[ o[ i ] ].rows.push( i );
 
2035                         if ( hasOwn.call( ns, i ) ) {
 
2036                                 if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
 
2037                                         n[ ns[ i ].rows[ 0 ] ] = {
 
2038                                                 text: n[ ns[ i ].rows[ 0 ] ],
 
2039                                                 row: os[ i ].rows[ 0 ]
 
2041                                         o[ os[ i ].rows[ 0 ] ] = {
 
2042                                                 text: o[ os[ i ].rows[ 0 ] ],
 
2043                                                 row: ns[ i ].rows[ 0 ]
 
2049                 for ( i = 0; i < n.length - 1; i++ ) {
 
2050                         if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
 
2051                                 n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
 
2057                                 o[ n[ i ].row + 1 ] = {
 
2058                                         text: o[ n[ i ].row + 1 ],
 
2064                 for ( i = n.length - 1; i > 0; i-- ) {
 
2065                         if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
 
2066                                 n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
 
2072                                 o[ n[ i ].row - 1 ] = {
 
2073                                         text: o[ n[ i ].row - 1 ],
 
2085         return function( o, n ) {
 
2086                 o = o.replace( /\s+$/, "" );
 
2087                 n = n.replace( /\s+$/, "" );
 
2091                         out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
 
2092                         oSpace = o.match( /\s+/g ),
 
2093                         nSpace = n.match( /\s+/g );
 
2095                 if ( oSpace == null ) {
 
2101                 if ( nSpace == null ) {
 
2107                 if ( out.n.length === 0 ) {
 
2108                         for ( i = 0; i < out.o.length; i++ ) {
 
2109                                 str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
 
2112                         if ( out.n[ 0 ].text == null ) {
 
2113                                 for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
 
2114                                         str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
 
2118                         for ( i = 0; i < out.n.length; i++ ) {
 
2119                                 if ( out.n[ i ].text == null ) {
 
2120                                         str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
 
2123                                         // `pre` initialized at top of scope
 
2126                                         for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
 
2127                                                 pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
 
2129                                         str += " " + out.n[ i ].text + nSpace[ i ] + pre;
 
2141 // Deprecated QUnit.init - Ref #530
 
2142 // Re-initialize the configuration options
 
2143 QUnit.init = function() {
 
2144         var tests, banner, result, qunit,
 
2145                 config = QUnit.config;
 
2147         config.stats = { all: 0, bad: 0 };
 
2148         config.moduleStats = { all: 0, bad: 0 };
 
2150         config.updateRate = 1000;
 
2151         config.blocking = false;
 
2152         config.autostart = true;
 
2153         config.autorun = false;
 
2157         // Return on non-browser environments
 
2158         // This is necessary to not break on node tests
 
2159         if ( typeof window === "undefined" ) {
 
2163         qunit = id( "qunit" );
 
2166                         "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
 
2167                         "<h2 id='qunit-banner'></h2>" +
 
2168                         "<div id='qunit-testrunner-toolbar'></div>" +
 
2169                         "<h2 id='qunit-userAgent'></h2>" +
 
2170                         "<ol id='qunit-tests'></ol>";
 
2173         tests = id( "qunit-tests" );
 
2174         banner = id( "qunit-banner" );
 
2175         result = id( "qunit-testresult" );
 
2178                 tests.innerHTML = "";
 
2182                 banner.className = "";
 
2186                 result.parentNode.removeChild( result );
 
2190                 result = document.createElement( "p" );
 
2191                 result.id = "qunit-testresult";
 
2192                 result.className = "result";
 
2193                 tests.parentNode.insertBefore( result, tests );
 
2194                 result.innerHTML = "Running...<br /> ";
 
2198 // Don't load the HTML Reporter on non-Browser environments
 
2199 if ( typeof window === "undefined" ) {
 
2203 var config = QUnit.config,
 
2204         hasOwn = Object.prototype.hasOwnProperty,
 
2206                 document: window.document !== undefined,
 
2207                 sessionStorage: (function() {
 
2208                         var x = "qunit-test-string";
 
2210                                 sessionStorage.setItem( x, x );
 
2211                                 sessionStorage.removeItem( x );
 
2221 * Escape text for attribute or text content.
 
2223 function escapeText( s ) {
 
2229         // Both single quotes and double quotes (for attributes)
 
2230         return s.replace( /['"<>&]/g, function( s ) {
 
2247  * @param {HTMLElement} elem
 
2248  * @param {string} type
 
2249  * @param {Function} fn
 
2251 function addEvent( elem, type, fn ) {
 
2252         if ( elem.addEventListener ) {
 
2254                 // Standards-based browsers
 
2255                 elem.addEventListener( type, fn, false );
 
2256         } else if ( elem.attachEvent ) {
 
2259                 elem.attachEvent( "on" + type, fn );
 
2264  * @param {Array|NodeList} elems
 
2265  * @param {string} type
 
2266  * @param {Function} fn
 
2268 function addEvents( elems, type, fn ) {
 
2269         var i = elems.length;
 
2271                 addEvent( elems[ i ], type, fn );
 
2275 function hasClass( elem, name ) {
 
2276         return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
 
2279 function addClass( elem, name ) {
 
2280         if ( !hasClass( elem, name ) ) {
 
2281                 elem.className += ( elem.className ? " " : "" ) + name;
 
2285 function toggleClass( elem, name ) {
 
2286         if ( hasClass( elem, name ) ) {
 
2287                 removeClass( elem, name );
 
2289                 addClass( elem, name );
 
2293 function removeClass( elem, name ) {
 
2294         var set = " " + elem.className + " ";
 
2296         // Class name may appear multiple times
 
2297         while ( set.indexOf( " " + name + " " ) >= 0 ) {
 
2298                 set = set.replace( " " + name + " ", " " );
 
2301         // trim for prettiness
 
2302         elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
 
2305 function id( name ) {
 
2306         return defined.document && document.getElementById && document.getElementById( name );
 
2309 function getUrlConfigHtml() {
 
2311                 escaped, escapedTooltip,
 
2313                 len = config.urlConfig.length,
 
2316         for ( i = 0; i < len; i++ ) {
 
2317                 val = config.urlConfig[ i ];
 
2318                 if ( typeof val === "string" ) {
 
2325                 escaped = escapeText( val.id );
 
2326                 escapedTooltip = escapeText( val.tooltip );
 
2328                 if ( config[ val.id ] === undefined ) {
 
2329                         config[ val.id ] = QUnit.urlParams[ val.id ];
 
2332                 if ( !val.value || typeof val.value === "string" ) {
 
2333                         urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
 
2334                                 "' name='" + escaped + "' type='checkbox'" +
 
2335                                 ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
 
2336                                 ( config[ val.id ] ? " checked='checked'" : "" ) +
 
2337                                 " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
 
2338                                 "' title='" + escapedTooltip + "'>" + val.label + "</label>";
 
2340                         urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
 
2341                                 "' title='" + escapedTooltip + "'>" + val.label +
 
2342                                 ": </label><select id='qunit-urlconfig-" + escaped +
 
2343                                 "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
 
2345                         if ( QUnit.is( "array", val.value ) ) {
 
2346                                 for ( j = 0; j < val.value.length; j++ ) {
 
2347                                         escaped = escapeText( val.value[ j ] );
 
2348                                         urlConfigHtml += "<option value='" + escaped + "'" +
 
2349                                                 ( config[ val.id ] === val.value[ j ] ?
 
2350                                                         ( selection = true ) && " selected='selected'" : "" ) +
 
2351                                                 ">" + escaped + "</option>";
 
2354                                 for ( j in val.value ) {
 
2355                                         if ( hasOwn.call( val.value, j ) ) {
 
2356                                                 urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
 
2357                                                         ( config[ val.id ] === j ?
 
2358                                                                 ( selection = true ) && " selected='selected'" : "" ) +
 
2359                                                         ">" + escapeText( val.value[ j ] ) + "</option>";
 
2363                         if ( config[ val.id ] && !selection ) {
 
2364                                 escaped = escapeText( config[ val.id ] );
 
2365                                 urlConfigHtml += "<option value='" + escaped +
 
2366                                         "' selected='selected' disabled='disabled'>" + escaped + "</option>";
 
2368                         urlConfigHtml += "</select>";
 
2372         return urlConfigHtml;
 
2375 // Handle "click" events on toolbar checkboxes and "change" for select menus.
 
2376 // Updates the URL with the new state of `config.urlConfig` values.
 
2377 function toolbarChanged() {
 
2378         var updatedUrl, value,
 
2382         // Detect if field is a select menu or a checkbox
 
2383         if ( "selectedIndex" in field ) {
 
2384                 value = field.options[ field.selectedIndex ].value || undefined;
 
2386                 value = field.checked ? ( field.defaultValue || true ) : undefined;
 
2389         params[ field.name ] = value;
 
2390         updatedUrl = setUrl( params );
 
2392         if ( "hidepassed" === field.name && "replaceState" in window.history ) {
 
2393                 config[ field.name ] = value || false;
 
2395                         addClass( id( "qunit-tests" ), "hidepass" );
 
2397                         removeClass( id( "qunit-tests" ), "hidepass" );
 
2400                 // It is not necessary to refresh the whole page
 
2401                 window.history.replaceState( null, "", updatedUrl );
 
2403                 window.location = updatedUrl;
 
2407 function setUrl( params ) {
 
2411         params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
 
2413         for ( key in params ) {
 
2414                 if ( hasOwn.call( params, key ) ) {
 
2415                         if ( params[ key ] === undefined ) {
 
2418                         querystring += encodeURIComponent( key );
 
2419                         if ( params[ key ] !== true ) {
 
2420                                 querystring += "=" + encodeURIComponent( params[ key ] );
 
2425         return location.protocol + "//" + location.host +
 
2426                 location.pathname + querystring.slice( 0, -1 );
 
2429 function applyUrlParams() {
 
2430         var selectBox = id( "qunit-modulefilter" ),
 
2431                 selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ),
 
2432                 filter = id( "qunit-filter-input" ).value;
 
2434         window.location = setUrl({
 
2435                 module: ( selection === "" ) ? undefined : selection,
 
2436                 filter: ( filter === "" ) ? undefined : filter,
 
2438                 // Remove testId filter
 
2443 function toolbarUrlConfigContainer() {
 
2444         var urlConfigContainer = document.createElement( "span" );
 
2446         urlConfigContainer.innerHTML = getUrlConfigHtml();
 
2447         addClass( urlConfigContainer, "qunit-url-config" );
 
2449         // For oldIE support:
 
2450         // * Add handlers to the individual elements instead of the container
 
2451         // * Use "click" instead of "change" for checkboxes
 
2452         addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
 
2453         addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
 
2455         return urlConfigContainer;
 
2458 function toolbarLooseFilter() {
 
2459         var filter = document.createElement( "form" ),
 
2460                 label = document.createElement( "label" ),
 
2461                 input = document.createElement( "input" ),
 
2462                 button = document.createElement( "button" );
 
2464         addClass( filter, "qunit-filter" );
 
2466         label.innerHTML = "Filter: ";
 
2468         input.type = "text";
 
2469         input.value = config.filter || "";
 
2470         input.name = "filter";
 
2471         input.id = "qunit-filter-input";
 
2473         button.innerHTML = "Go";
 
2475         label.appendChild( input );
 
2477         filter.appendChild( label );
 
2478         filter.appendChild( button );
 
2479         addEvent( filter, "submit", function( ev ) {
 
2482                 if ( ev && ev.preventDefault ) {
 
2483                         ev.preventDefault();
 
2492 function toolbarModuleFilterHtml() {
 
2494                 moduleFilterHtml = "";
 
2496         if ( !modulesList.length ) {
 
2500         modulesList.sort(function( a, b ) {
 
2501                 return a.localeCompare( b );
 
2504         moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
 
2505                 "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
 
2506                 ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
 
2507                 ">< All Modules ></option>";
 
2509         for ( i = 0; i < modulesList.length; i++ ) {
 
2510                 moduleFilterHtml += "<option value='" +
 
2511                         escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
 
2512                         ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
 
2513                         ">" + escapeText( modulesList[ i ] ) + "</option>";
 
2515         moduleFilterHtml += "</select>";
 
2517         return moduleFilterHtml;
 
2520 function toolbarModuleFilter() {
 
2521         var toolbar = id( "qunit-testrunner-toolbar" ),
 
2522                 moduleFilter = document.createElement( "span" ),
 
2523                 moduleFilterHtml = toolbarModuleFilterHtml();
 
2525         if ( !toolbar || !moduleFilterHtml ) {
 
2529         moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
 
2530         moduleFilter.innerHTML = moduleFilterHtml;
 
2532         addEvent( moduleFilter.lastChild, "change", applyUrlParams );
 
2534         toolbar.appendChild( moduleFilter );
 
2537 function appendToolbar() {
 
2538         var toolbar = id( "qunit-testrunner-toolbar" );
 
2541                 toolbar.appendChild( toolbarUrlConfigContainer() );
 
2542                 toolbar.appendChild( toolbarLooseFilter() );
 
2546 function appendHeader() {
 
2547         var header = id( "qunit-header" );
 
2550                 header.innerHTML = "<a href='" +
 
2551                         setUrl({ filter: undefined, module: undefined, testId: undefined }) +
 
2552                         "'>" + header.innerHTML + "</a> ";
 
2556 function appendBanner() {
 
2557         var banner = id( "qunit-banner" );
 
2560                 banner.className = "";
 
2564 function appendTestResults() {
 
2565         var tests = id( "qunit-tests" ),
 
2566                 result = id( "qunit-testresult" );
 
2569                 result.parentNode.removeChild( result );
 
2573                 tests.innerHTML = "";
 
2574                 result = document.createElement( "p" );
 
2575                 result.id = "qunit-testresult";
 
2576                 result.className = "result";
 
2577                 tests.parentNode.insertBefore( result, tests );
 
2578                 result.innerHTML = "Running...<br /> ";
 
2582 function storeFixture() {
 
2583         var fixture = id( "qunit-fixture" );
 
2585                 config.fixture = fixture.innerHTML;
 
2589 function appendUserAgent() {
 
2590         var userAgent = id( "qunit-userAgent" );
 
2592                 userAgent.innerHTML = "";
 
2593                 userAgent.appendChild( document.createTextNode( navigator.userAgent ) );
 
2597 function appendTestsList( modules ) {
 
2598         var i, l, x, z, test, moduleObj;
 
2600         for ( i = 0, l = modules.length; i < l; i++ ) {
 
2601                 moduleObj = modules[ i ];
 
2603                 if ( moduleObj.name ) {
 
2604                         modulesList.push( moduleObj.name );
 
2607                 for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
 
2608                         test = moduleObj.tests[ x ];
 
2610                         appendTest( test.name, test.testId, moduleObj.name );
 
2615 function appendTest( name, testId, moduleName ) {
 
2616         var title, rerunTrigger, testBlock, assertList,
 
2617                 tests = id( "qunit-tests" );
 
2623         title = document.createElement( "strong" );
 
2624         title.innerHTML = getNameHtml( name, moduleName );
 
2626         rerunTrigger = document.createElement( "a" );
 
2627         rerunTrigger.innerHTML = "Rerun";
 
2628         rerunTrigger.href = setUrl({ testId: testId });
 
2630         testBlock = document.createElement( "li" );
 
2631         testBlock.appendChild( title );
 
2632         testBlock.appendChild( rerunTrigger );
 
2633         testBlock.id = "qunit-test-output-" + testId;
 
2635         assertList = document.createElement( "ol" );
 
2636         assertList.className = "qunit-assert-list";
 
2638         testBlock.appendChild( assertList );
 
2640         tests.appendChild( testBlock );
 
2643 // HTML Reporter initialization and load
 
2644 QUnit.begin(function( details ) {
 
2645         var qunit = id( "qunit" );
 
2647         // Fixture is the only one necessary to run without the #qunit element
 
2652                         "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
 
2653                         "<h2 id='qunit-banner'></h2>" +
 
2654                         "<div id='qunit-testrunner-toolbar'></div>" +
 
2655                         "<h2 id='qunit-userAgent'></h2>" +
 
2656                         "<ol id='qunit-tests'></ol>";
 
2661         appendTestResults();
 
2664         appendTestsList( details.modules );
 
2665         toolbarModuleFilter();
 
2667         if ( qunit && config.hidepassed ) {
 
2668                 addClass( qunit.lastChild, "hidepass" );
 
2672 QUnit.done(function( details ) {
 
2674                 banner = id( "qunit-banner" ),
 
2675                 tests = id( "qunit-tests" ),
 
2677                         "Tests completed in ",
 
2679                         " milliseconds.<br />",
 
2680                         "<span class='passed'>",
 
2682                         "</span> assertions of <span class='total'>",
 
2684                         "</span> passed, <span class='failed'>",
 
2690                 banner.className = details.failed ? "qunit-fail" : "qunit-pass";
 
2694                 id( "qunit-testresult" ).innerHTML = html;
 
2697         if ( config.altertitle && defined.document && document.title ) {
 
2699                 // show ✖ for good, ✔ for bad suite result in title
 
2700                 // use escape sequences in case file gets loaded with non-utf-8-charset
 
2702                         ( details.failed ? "\u2716" : "\u2714" ),
 
2703                         document.title.replace( /^[\u2714\u2716] /i, "" )
 
2707         // clear own sessionStorage items if all tests passed
 
2708         if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
 
2709                 for ( i = 0; i < sessionStorage.length; i++ ) {
 
2710                         key = sessionStorage.key( i++ );
 
2711                         if ( key.indexOf( "qunit-test-" ) === 0 ) {
 
2712                                 sessionStorage.removeItem( key );
 
2717         // scroll back to top to show results
 
2718         if ( config.scrolltop && window.scrollTo ) {
 
2719                 window.scrollTo( 0, 0 );
 
2723 function getNameHtml( name, module ) {
 
2727                 nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
 
2730         nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
 
2735 QUnit.testStart(function( details ) {
 
2736         var running, testBlock;
 
2738         testBlock = id( "qunit-test-output-" + details.testId );
 
2740                 testBlock.className = "running";
 
2743                 // Report later registered tests
 
2744                 appendTest( details.name, details.testId, details.module );
 
2747         running = id( "qunit-testresult" );
 
2749                 running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
 
2754 QUnit.log(function( details ) {
 
2755         var assertList, assertLi,
 
2756                 message, expected, actual,
 
2757                 testItem = id( "qunit-test-output-" + details.testId );
 
2763         message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
 
2764         message = "<span class='test-message'>" + message + "</span>";
 
2765         message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
 
2767         // pushFailure doesn't provide details.expected
 
2768         // when it calls, it's implicit to also not show expected and diff stuff
 
2769         // Also, we need to check details.expected existence, as it can exist and be undefined
 
2770         if ( !details.result && hasOwn.call( details, "expected" ) ) {
 
2771                 expected = escapeText( QUnit.dump.parse( details.expected ) );
 
2772                 actual = escapeText( QUnit.dump.parse( details.actual ) );
 
2773                 message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
 
2777                 if ( actual !== expected ) {
 
2778                         message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
 
2779                                 actual + "</pre></td></tr>" +
 
2780                                 "<tr class='test-diff'><th>Diff: </th><td><pre>" +
 
2781                                 QUnit.diff( expected, actual ) + "</pre></td></tr>";
 
2784                 if ( details.source ) {
 
2785                         message += "<tr class='test-source'><th>Source: </th><td><pre>" +
 
2786                                 escapeText( details.source ) + "</pre></td></tr>";
 
2789                 message += "</table>";
 
2791         // this occours when pushFailure is set and we have an extracted stack trace
 
2792         } else if ( !details.result && details.source ) {
 
2793                 message += "<table>" +
 
2794                         "<tr class='test-source'><th>Source: </th><td><pre>" +
 
2795                         escapeText( details.source ) + "</pre></td></tr>" +
 
2799         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
 
2801         assertLi = document.createElement( "li" );
 
2802         assertLi.className = details.result ? "pass" : "fail";
 
2803         assertLi.innerHTML = message;
 
2804         assertList.appendChild( assertLi );
 
2807 QUnit.testDone(function( details ) {
 
2808         var testTitle, time, testItem, assertList,
 
2809                 good, bad, testCounts, skipped,
 
2810                 tests = id( "qunit-tests" );
 
2816         testItem = id( "qunit-test-output-" + details.testId );
 
2818         assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
 
2820         good = details.passed;
 
2821         bad = details.failed;
 
2823         // store result when possible
 
2824         if ( config.reorder && defined.sessionStorage ) {
 
2826                         sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
 
2828                         sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
 
2833                 addClass( assertList, "qunit-collapsed" );
 
2836         // testItem.firstChild is the test name
 
2837         testTitle = testItem.firstChild;
 
2840                 "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
 
2843         testTitle.innerHTML += " <b class='counts'>(" + testCounts +
 
2844                 details.assertions.length + ")</b>";
 
2846         if ( details.skipped ) {
 
2847                 testItem.className = "skipped";
 
2848                 skipped = document.createElement( "em" );
 
2849                 skipped.className = "qunit-skipped-label";
 
2850                 skipped.innerHTML = "skipped";
 
2851                 testItem.insertBefore( skipped, testTitle );
 
2853                 addEvent( testTitle, "click", function() {
 
2854                         toggleClass( assertList, "qunit-collapsed" );
 
2857                 testItem.className = bad ? "fail" : "pass";
 
2859                 time = document.createElement( "span" );
 
2860                 time.className = "runtime";
 
2861                 time.innerHTML = details.runtime + " ms";
 
2862                 testItem.insertBefore( time, assertList );
 
2866 if ( !defined.document || document.readyState === "complete" ) {
 
2867         config.pageLoaded = true;
 
2868         config.autorun = true;
 
2871 if ( defined.document ) {
 
2872         addEvent( window, "load", QUnit.load );