Upgrade QUnit from v1.22.0 to v1.23.1
authorTimo Tijhof <krinklemail@gmail.com>
Tue, 29 Nov 2016 03:17:26 +0000 (19:17 -0800)
committerTimo Tijhof <krinklemail@gmail.com>
Tue, 29 Nov 2016 03:18:32 +0000 (19:18 -0800)
Source
 https://code.jquery.com/qunit/qunit-1.23.1.js
 https://code.jquery.com/qunit/qunit-1.23.1.css

Changelog
 https://github.com/qunitjs/qunit/blob/1.23.1/History.md

Change-Id: I03b4e1de699ef9f33a8db3dec605c8e32f823a93

RELEASE-NOTES-1.29
resources/lib/qunitjs/qunit.css
resources/lib/qunitjs/qunit.js

index 23c34df..60521d3 100644 (file)
@@ -22,6 +22,7 @@ production.
 === External library changes in 1.29 ===
 
 ==== Upgraded external libraries ====
+* Updated QUnit from v1.22.0 to v1.23.1.
 
 ==== New external libraries ====
 
index 8c78b67..ae68fc4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 1.22.0
+ * QUnit 1.23.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-02-23T15:57Z
+ * Date: 2016-04-12T17:29Z
  */
 
 /** Font Family and Sizes */
index 84873ae..5df0822 100644 (file)
@@ -1,15 +1,15 @@
 /*!
- * QUnit 1.22.0
+ * QUnit 1.23.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2016-02-23T15:57Z
+ * Date: 2016-04-12T17:29Z
  */
 
-(function( global ) {
+( function( global ) {
 
 var QUnit = {};
 
@@ -27,7 +27,7 @@ var window = global.window;
 var defined = {
        document: window && window.document !== undefined,
        setTimeout: setTimeout !== undefined,
-       sessionStorage: (function() {
+       sessionStorage: ( function() {
                var x = "qunit-test-string";
                try {
                        sessionStorage.setItem( x, x );
@@ -46,7 +46,7 @@ var runStarted = false;
 var toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty;
 
-// returns a new Array with the elements that are in a but not in b
+// Returns a new Array with the elements that are in a but not in b
 function diff( a, b ) {
        var i, j,
                result = a.slice();
@@ -63,7 +63,7 @@ function diff( a, b ) {
        return result;
 }
 
-// from jquery.js
+// From jquery.js
 function inArray( elem, array ) {
        if ( array.indexOf ) {
                return array.indexOf( elem );
@@ -157,32 +157,6 @@ function is( type, obj ) {
        return QUnit.objectType( obj ) === type;
 }
 
-var getUrlParams = function() {
-       var i, param, name, value;
-       var urlParams = {};
-       var location = window.location;
-       var params = location.search.slice( 1 ).split( "&" );
-       var length = params.length;
-
-       for ( i = 0; i < length; i++ ) {
-               if ( params[ i ] ) {
-                       param = params[ i ].split( "=" );
-                       name = decodeURIComponent( param[ 0 ] );
-
-                       // allow just a key to turn on a flag, e.g., test.html?noglobals
-                       value = param.length === 1 ||
-                               decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
-                       if ( urlParams[ name ] ) {
-                               urlParams[ name ] = [].concat( urlParams[ name ], value );
-                       } else {
-                               urlParams[ name ] = value;
-                       }
-               }
-       }
-
-       return urlParams;
-};
-
 // Doesn't support IE6 to IE9, it will return undefined on these browsers
 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
 function extractStacktrace( e, offset ) {
@@ -212,12 +186,12 @@ function extractStacktrace( e, offset ) {
        // Support: Safari <=6 only
        } else if ( e.sourceURL ) {
 
-               // exclude useless self-reference for generated Error objects
+               // Exclude useless self-reference for generated Error objects
                if ( /qunit.js$/.test( e.sourceURL ) ) {
                        return;
                }
 
-               // for actual exceptions, this is useful
+               // For actual exceptions, this is useful
                return e.sourceURL + ":" + e.line;
        }
 }
@@ -244,53 +218,35 @@ function sourceFromStacktrace( offset ) {
  * `config` initialized at top of scope
  */
 var config = {
+
        // The queue of tests to run
        queue: [],
 
-       // block until document ready
+       // Block until document ready
        blocking: true,
 
-       // by default, run previously failed tests first
+       // By default, run previously failed tests first
        // very useful in combination with "Hide passed tests" checked
        reorder: true,
 
-       // by default, modify document.title when suite is done
+       // By default, modify document.title when suite is done
        altertitle: true,
 
        // HTML Reporter: collapse every test except the first failing test
        // If false, all failing tests will be expanded
        collapse: true,
 
-       // by default, scroll to top of the page when suite is done
+       // By default, scroll to top of the page when suite is done
        scrolltop: true,
 
-       // depth up-to which object will be dumped
+       // Depth up-to which object will be dumped
        maxDepth: 5,
 
-       // when enabled, all tests must call expect()
+       // When enabled, all tests must call expect()
        requireExpects: false,
 
-       // add checkboxes that are persisted in the query-string
-       // when enabled, the id is set to `true` as a `QUnit.config` property
-       urlConfig: [
-               {
-                       id: "hidepassed",
-                       label: "Hide passed tests",
-                       tooltip: "Only show tests and assertions that fail. Stored as query-strings."
-               },
-               {
-                       id: "noglobals",
-                       label: "Check for Globals",
-                       tooltip: "Enabling this will test if any test introduces new properties on the " +
-                               "global object (`window` in Browsers). Stored as query-strings."
-               },
-               {
-                       id: "notrycatch",
-                       label: "No try-catch",
-                       tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
-                               "exceptions in IE reasonable. Stored as query-strings."
-               }
-       ],
+       // Placeholder for user-configurable form-exposed URL parameters
+       urlConfig: [],
 
        // Set of all modules.
        modules: [],
@@ -307,27 +263,9 @@ var config = {
        callbacks: {}
 };
 
-var urlParams = defined.document ? getUrlParams() : {};
-
 // Push a loose unnamed module to the modules collection
 config.modules.push( config.currentModule );
 
-if ( urlParams.filter === true ) {
-       delete urlParams.filter;
-}
-
-// String search anywhere in moduleName+testName
-config.filter = urlParams.filter;
-
-config.testId = [];
-if ( urlParams.testId ) {
-       // Ensure that urlParams.testId is an array
-       urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
-       for (var i = 0; i < urlParams.testId.length; i++ ) {
-               config.testId.push( urlParams.testId[ i ] );
-       }
-}
-
 var loggingCallbacks = {};
 
 // Register logging callbacks
@@ -431,7 +369,7 @@ function verifyLoggingCallbacks() {
                                }
                                QUnit.pushFailure( error, filePath + ":" + linerNr );
                        } else {
-                               QUnit.test( "global failure", extend(function() {
+                               QUnit.test( "global failure", extend( function() {
                                        QUnit.pushFailure( error, filePath + ":" + linerNr );
                                }, { validTest: true } ) );
                        }
@@ -440,25 +378,23 @@ function verifyLoggingCallbacks() {
 
                return ret;
        };
-} )();
-
-QUnit.urlParams = urlParams;
+}() );
 
 // Figure out if we're running the tests from a server or not
 QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
 
 // Expose the current QUnit version
-QUnit.version = "1.22.0";
+QUnit.version = "1.23.1";
 
 extend( QUnit, {
 
-       // call on start of module test to prepend name to all tests
+       // Call on start of module test to prepend name to all tests
        module: function( name, testEnvironment, executeNow ) {
                var module, moduleFns;
                var currentModule = config.currentModule;
 
                if ( arguments.length === 2 ) {
-                       if ( testEnvironment instanceof Function ) {
+                       if ( objectType( testEnvironment ) === "function" ) {
                                executeNow = testEnvironment;
                                testEnvironment = undefined;
                        }
@@ -482,7 +418,7 @@ extend( QUnit, {
                        afterEach: setHook( module, "afterEach" )
                };
 
-               if ( executeNow instanceof Function ) {
+               if ( objectType( executeNow ) === "function" ) {
                        config.moduleStack.push( module );
                        setCurrentModule( module );
                        executeNow.call( module.testEnvironment, moduleFns );
@@ -500,7 +436,8 @@ extend( QUnit, {
                        var module = {
                                name: moduleName,
                                parentModule: parentModule,
-                               tests: []
+                               tests: [],
+                               moduleId: generateHash( moduleName )
                        };
 
                        var env = {};
@@ -573,7 +510,7 @@ extend( QUnit, {
                                return;
                        }
 
-                       // throw an Error if start is called more often than stop
+                       // Throw an Error if start is called more often than stop
                        if ( config.current.semaphore < 0 ) {
                                config.current.semaphore = 0;
 
@@ -634,7 +571,7 @@ extend( QUnit, {
                offset = ( offset || 0 ) + 2;
                return sourceFromStacktrace( offset );
        }
-});
+} );
 
 registerLoggingCallbacks( QUnit );
 
@@ -657,17 +594,17 @@ function begin() {
 
                // Avoid unnecessary information by not logging modules' test environments
                for ( i = 0, l = config.modules.length; i < l; i++ ) {
-                       modulesLog.push({
+                       modulesLog.push( {
                                name: config.modules[ i ].name,
                                tests: config.modules[ i ].tests
-                       });
+                       } );
                }
 
                // The test run is officially beginning now
                runLoggingCallbacks( "begin", {
                        totalTests: Test.count,
                        modules: modulesLog
-               });
+               } );
        }
 
        config.blocking = false;
@@ -706,7 +643,7 @@ function pauseProcessing() {
 
        if ( config.testTimeout && defined.setTimeout ) {
                clearTimeout( config.timeout );
-               config.timeout = setTimeout(function() {
+               config.timeout = setTimeout( function() {
                        if ( config.current ) {
                                config.current.semaphore = 0;
                                QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
@@ -723,7 +660,7 @@ function resumeProcessing() {
 
        // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
        if ( defined.setTimeout ) {
-               setTimeout(function() {
+               setTimeout( function() {
                        if ( config.current && config.current.semaphore > 0 ) {
                                return;
                        }
@@ -752,7 +689,7 @@ function done() {
                        passed: config.moduleStats.all - config.moduleStats.bad,
                        total: config.moduleStats.all,
                        runtime: now() - config.moduleStats.started
-               });
+               } );
        }
        delete config.previousModule;
 
@@ -764,7 +701,7 @@ function done() {
                passed: passed,
                total: config.stats.all,
                runtime: runtime
-       });
+       } );
 }
 
 function setHook( module, hookName ) {
@@ -779,6 +716,7 @@ function setHook( module, hookName ) {
 
 var focused = false;
 var priorityCount = 0;
+var unitSampler;
 
 function Test( settings ) {
        var i, l;
@@ -801,10 +739,10 @@ function Test( settings ) {
 
        this.testId = generateHash( this.module.name, this.testName );
 
-       this.module.tests.push({
+       this.module.tests.push( {
                name: this.testName,
                testId: this.testId
-       });
+       } );
 
        if ( settings.skip ) {
 
@@ -840,14 +778,14 @@ Test.prototype = {
                                        passed: config.moduleStats.all - config.moduleStats.bad,
                                        total: config.moduleStats.all,
                                        runtime: now() - config.moduleStats.started
-                               });
+                               } );
                        }
                        config.previousModule = this.module;
                        config.moduleStats = { all: 0, bad: 0, started: now() };
                        runLoggingCallbacks( "moduleStart", {
                                name: this.module.name,
                                tests: this.module.tests
-                       });
+                       } );
                }
 
                config.current = this;
@@ -863,7 +801,7 @@ Test.prototype = {
                        name: this.testName,
                        module: this.module.name,
                        testId: this.testId
-               });
+               } );
 
                if ( !config.pollution ) {
                        saveGlobal();
@@ -892,7 +830,7 @@ Test.prototype = {
                        this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
                                this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
 
-                       // else next test will carry the responsibility
+                       // Else next test will carry the responsibility
                        saveGlobal();
 
                        // Restart the tests if they're blocking
@@ -1001,7 +939,7 @@ Test.prototype = {
 
                        // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
                        duration: this.runtime
-               });
+               } );
 
                // QUnit.reset() is deprecated and will be replaced for a new
                // fixture reset function on QUnit 2.0/2.1.
@@ -1021,8 +959,8 @@ Test.prototype = {
 
                function run() {
 
-                       // each of these can by async
-                       synchronize([
+                       // Each of these can by async
+                       synchronize( [
                                function() {
                                        test.before();
                                },
@@ -1040,19 +978,19 @@ Test.prototype = {
                                function() {
                                        test.finish();
                                }
-                       ]);
+                       ] );
                }
 
                // Prioritize previously failed tests, detected from sessionStorage
                priority = QUnit.config.reorder && defined.sessionStorage &&
                                +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
 
-               return synchronize( run, priority );
+               return synchronize( run, priority, config.seed );
        },
 
        pushResult: function( resultInfo ) {
 
-               // resultInfo = { result, actual, expected, message, negative }
+               // Destructure of resultInfo = { result, actual, expected, message, negative }
                var source,
                        details = {
                                module: this.module.name,
@@ -1076,10 +1014,10 @@ Test.prototype = {
 
                runLoggingCallbacks( "log", details );
 
-               this.assertions.push({
+               this.assertions.push( {
                        result: !!resultInfo.result,
                        message: resultInfo.message
-               });
+               } );
        },
 
        pushFailure: function( message, source, actual ) {
@@ -1104,10 +1042,10 @@ Test.prototype = {
 
                runLoggingCallbacks( "log", details );
 
-               this.assertions.push({
+               this.assertions.push( {
                        result: false,
                        message: message
-               });
+               } );
        },
 
        resolvePromise: function( promise, phase ) {
@@ -1126,7 +1064,7 @@ Test.prototype = {
                                                        " " + test.testName + ": " + ( error.message || error );
                                                test.pushFailure( message, extractStacktrace( error, 0 ) );
 
-                                               // else next test will carry the responsibility
+                                               // Else next test will carry the responsibility
                                                saveGlobal();
 
                                                // Unblock
@@ -1140,30 +1078,43 @@ Test.prototype = {
        valid: function() {
                var filter = config.filter,
                        regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
-                       module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+                       module = config.module && config.module.toLowerCase(),
                        fullName = ( this.module.name + ": " + this.testName );
 
-               function testInModuleChain( testModule ) {
+               function moduleChainNameMatch( testModule ) {
                        var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
                        if ( testModuleName === module ) {
                                return true;
                        } else if ( testModule.parentModule ) {
-                               return testInModuleChain( testModule.parentModule );
+                               return moduleChainNameMatch( testModule.parentModule );
                        } else {
                                return false;
                        }
                }
 
+               function moduleChainIdMatch( testModule ) {
+                       return inArray( testModule.moduleId, config.moduleId ) > -1 ||
+                               testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
+               }
+
                // Internally-generated tests are always valid
                if ( this.callback && this.callback.validTest ) {
                        return true;
                }
 
-               if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+               if ( config.moduleId && config.moduleId.length > 0 &&
+                       !moduleChainIdMatch( this.module ) ) {
+
+                       return false;
+               }
+
+               if ( config.testId && config.testId.length > 0 &&
+                       inArray( this.testId, config.testId ) < 0 ) {
+
                        return false;
                }
 
-               if ( module && !testInModuleChain( this.module ) ) {
+               if ( module && !moduleChainNameMatch( this.module ) ) {
                        return false;
                }
 
@@ -1172,7 +1123,7 @@ Test.prototype = {
                }
 
                return regexFilter ?
-                       this.regexFilter( !!regexFilter[1], regexFilter[2], regexFilter[3], fullName ) :
+                       this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
                        this.stringFilter( filter, fullName );
        },
 
@@ -1260,8 +1211,9 @@ function generateHash( module, testName ) {
        return hex.slice( -8 );
 }
 
-function synchronize( callback, priority ) {
-       var last = !priority;
+function synchronize( callback, priority, seed ) {
+       var last = !priority,
+               index;
 
        if ( QUnit.objectType( callback ) === "array" ) {
                while ( callback.length ) {
@@ -1272,6 +1224,14 @@ function synchronize( callback, priority ) {
 
        if ( priority ) {
                config.queue.splice( priorityCount++, 0, callback );
+       } else if ( seed ) {
+               if ( !unitSampler ) {
+                       unitSampler = unitSamplerGenerator( seed );
+               }
+
+               // Insert into a random position after all priority items
+               index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
+               config.queue.splice( priorityCount + index, 0, callback );
        } else {
                config.queue.push( callback );
        }
@@ -1281,6 +1241,25 @@ function synchronize( callback, priority ) {
        }
 }
 
+function unitSamplerGenerator( seed ) {
+
+       // 32-bit xorshift, requires only a nonzero seed
+       // http://excamera.com/sphinx/article-xorshift.html
+       var sample = parseInt( generateHash( seed ), 16 ) || -1;
+       return function() {
+               sample ^= sample << 13;
+               sample ^= sample >>> 17;
+               sample ^= sample << 5;
+
+               // ECMAScript has no unsigned number type
+               if ( sample < 0 ) {
+                       sample += 0x100000000;
+               }
+
+               return sample / 0x100000000;
+       };
+}
+
 function saveGlobal() {
        config.pollution = [];
 
@@ -1288,7 +1267,7 @@ function saveGlobal() {
                for ( var key in global ) {
                        if ( hasOwn.call( global, key ) ) {
 
-                               // in Opera sometimes DOM element ids show up here, ignore them
+                               // In Opera sometimes DOM element ids show up here, ignore them
                                if ( /^qunit-test-output/.test( key ) ) {
                                        continue;
                                }
@@ -1337,12 +1316,12 @@ function test( testName, expected, callback, async ) {
                expected = null;
        }
 
-       newTest = new Test({
+       newTest = new Test( {
                testName: testName,
                expected: expected,
                async: async,
                callback: callback
-       });
+       } );
 
        newTest.queue();
 }
@@ -1351,10 +1330,10 @@ function test( testName, expected, callback, async ) {
 function skip( testName ) {
        if ( focused )  { return; }
 
-       var test = new Test({
+       var test = new Test( {
                testName: testName,
                skip: true
-       });
+       } );
 
        test.queue();
 }
@@ -1373,12 +1352,12 @@ function only( testName, expected, callback, async ) {
                expected = null;
        }
 
-       newTest = new Test({
+       newTest = new Test( {
                testName: testName,
                expected: expected,
                async: async,
                callback: callback
-       });
+       } );
 
        newTest.queue();
 }
@@ -1448,7 +1427,7 @@ QUnit.assert = Assert.prototype = {
 
        pushResult: function( resultInfo ) {
 
-               // resultInfo = { result, actual, expected, message, negative }
+               // Destructure of resultInfo = { result, actual, expected, message, negative }
                var assert = this,
                        currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
 
@@ -1594,7 +1573,7 @@ QUnit.assert = Assert.prototype = {
                currentTest.ignoreGlobalErrors = true;
                try {
                        block.call( currentTest.testEnvironment );
-               } catch (e) {
+               } catch ( e ) {
                        actual = e;
                }
                currentTest.ignoreGlobalErrors = false;
@@ -1602,30 +1581,30 @@ QUnit.assert = Assert.prototype = {
                if ( actual ) {
                        expectedType = QUnit.objectType( expected );
 
-                       // we don't want to validate thrown error
+                       // We don't want to validate thrown error
                        if ( !expected ) {
                                ok = true;
                                expectedOutput = null;
 
-                       // expected is a regexp
+                       // Expected is a regexp
                        } else if ( expectedType === "regexp" ) {
                                ok = expected.test( errorString( actual ) );
 
-                       // expected is a string
+                       // Expected is a string
                        } else if ( expectedType === "string" ) {
                                ok = expected === errorString( actual );
 
-                       // expected is a constructor, maybe an Error constructor
+                       // Expected is a constructor, maybe an Error constructor
                        } else if ( expectedType === "function" && actual instanceof expected ) {
                                ok = true;
 
-                       // expected is an Error object
+                       // Expected is an Error object
                        } else if ( expectedType === "object" ) {
                                ok = actual instanceof expected.constructor &&
                                        actual.name === expected.name &&
                                        actual.message === expected.message;
 
-                       // expected is a validation function which returns true if validation passed
+                       // Expected is a validation function which returns true if validation passed
                        } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
                                expectedOutput = null;
                                ok = true;
@@ -1643,10 +1622,10 @@ QUnit.assert = Assert.prototype = {
 
 // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
 // Known to us are: Closure Compiler, Narwhal
-(function() {
+( function() {
        /*jshint sub:true */
-       Assert.prototype.raises = Assert.prototype[ "throws" ];
-}());
+       Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
+}() );
 
 function errorString( error ) {
        var name, message,
@@ -1670,7 +1649,7 @@ function errorString( error ) {
 
 // Test for equality any JavaScript type.
 // Author: Philippe Rathé <prathe@gmail.com>
-QUnit.equiv = (function() {
+QUnit.equiv = ( function() {
 
        // Stack to decide between skip/abort functions
        var callers = [];
@@ -1766,7 +1745,8 @@ QUnit.equiv = (function() {
 
                        len = a.length;
                        if ( len !== b.length ) {
-                               // safe and faster
+
+                               // Safe and faster
                                return false;
                        }
 
@@ -1800,33 +1780,53 @@ QUnit.equiv = (function() {
                },
 
                "set": function( b, a ) {
-                       var aArray, bArray;
-
-                       aArray = [];
-                       a.forEach( function( v ) {
-                               aArray.push( v );
-                       });
-                       bArray = [];
-                       b.forEach( function( v ) {
-                               bArray.push( v );
-                       });
-
-                       return innerEquiv( bArray, aArray );
+                       var innerEq,
+                               outerEq = true;
+
+                       if ( a.size !== b.size ) {
+                               return false;
+                       }
+
+                       a.forEach( function( aVal ) {
+                               innerEq = false;
+
+                               b.forEach( function( bVal ) {
+                                       if ( innerEquiv( bVal, aVal ) ) {
+                                               innerEq = true;
+                                       }
+                               } );
+
+                               if ( !innerEq ) {
+                                       outerEq = false;
+                               }
+                       } );
+
+                       return outerEq;
                },
 
                "map": function( b, a ) {
-                       var aArray, bArray;
-
-                       aArray = [];
-                       a.forEach( function( v, k ) {
-                               aArray.push( [ k, v ] );
-                       });
-                       bArray = [];
-                       b.forEach( function( v, k ) {
-                               bArray.push( [ k, v ] );
-                       });
-
-                       return innerEquiv( bArray, aArray );
+                       var innerEq,
+                               outerEq = true;
+
+                       if ( a.size !== b.size ) {
+                               return false;
+                       }
+
+                       a.forEach( function( aVal, aKey ) {
+                               innerEq = false;
+
+                               b.forEach( function( bVal, bKey ) {
+                                       if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
+                                               innerEq = true;
+                                       }
+                               } );
+
+                               if ( !innerEq ) {
+                                       outerEq = false;
+                               }
+                       } );
+
+                       return outerEq;
                },
 
                "object": function( b, a ) {
@@ -1908,11 +1908,11 @@ QUnit.equiv = (function() {
        }
 
        return innerEquiv;
-}());
+}() );
 
 // Based on jsDump by Ariel Flesler
 // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
-QUnit.dump = (function() {
+QUnit.dump = ( function() {
        function quote( str ) {
                return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
        }
@@ -1950,7 +1950,7 @@ QUnit.dump = (function() {
        var reName = /^function (\w+)/,
                dump = {
 
-                       // objType is used mostly internally, you can fix a (custom) type in advance
+                       // The objType is used mostly internally, you can fix a (custom) type in advance
                        parse: function( obj, objType, stack ) {
                                stack = stack || [];
                                var res, parser, parserType,
@@ -1994,7 +1994,7 @@ QUnit.dump = (function() {
                                        type = "node";
                                } else if (
 
-                                       // native arrays
+                                       // Native arrays
                                        toString.call( obj ) === "[object Array]" ||
 
                                        // NodeList objects
@@ -2010,10 +2010,12 @@ QUnit.dump = (function() {
                                }
                                return type;
                        },
+
                        separator: function() {
                                return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
                        },
-                       // extra can be a number, shortcut for increasing-calling-decreasing
+
+                       // Extra can be a number, shortcut for increasing-calling-decreasing
                        indent: function( extra ) {
                                if ( !this.multiline ) {
                                        return "";
@@ -2033,11 +2035,11 @@ QUnit.dump = (function() {
                        setParser: function( name, parser ) {
                                this.parsers[ name ] = parser;
                        },
+
                        // The next 3 are exposed so you can use them
                        quote: quote,
                        literal: literal,
                        join: join,
-                       //
                        depth: 1,
                        maxDepth: QUnit.config.maxDepth,
 
@@ -2054,13 +2056,13 @@ QUnit.dump = (function() {
                                "function": function( fn ) {
                                        var ret = "function",
 
-                                               // functions never have name in IE
+                                               // Functions never have name in IE
                                                name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
 
                                        if ( name ) {
                                                ret += " " + name;
                                        }
-                                       ret += "( ";
+                                       ret += "(";
 
                                        ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
                                        return join( ret, dump.parse( fn, "functionCode" ), "}" );
@@ -2131,7 +2133,7 @@ QUnit.dump = (function() {
                                        return ret + open + "/" + tag + close;
                                },
 
-                               // function calls it internally, it's the arguments part of the function
+                               // Function calls it internally, it's the arguments part of the function
                                functionArgs: function( fn ) {
                                        var args,
                                                l = fn.length;
@@ -2148,11 +2150,14 @@ QUnit.dump = (function() {
                                        }
                                        return " " + args.join( ", " ) + " ";
                                },
-                               // object calls it internally, the key part of an item in a map
+
+                               // Object calls it internally, the key part of an item in a map
                                key: quote,
-                               // function calls it internally, it's the content of the function
+
+                               // Function calls it internally, it's the content of the function
                                functionCode: "[code]",
-                               // node calls it internally, it's a html attribute value
+
+                               // Node calls it internally, it's a html attribute value
                                attribute: quote,
                                string: quote,
                                date: quote,
@@ -2160,23 +2165,26 @@ QUnit.dump = (function() {
                                number: literal,
                                "boolean": literal
                        },
-                       // if true, entities are escaped ( <, >, \t, space and \n )
+
+                       // If true, entities are escaped ( <, >, \t, space and \n )
                        HTML: false,
-                       // indentation unit
+
+                       // Indentation unit
                        indentChar: "  ",
-                       // if true, items in a collection, are separated by a \n, else just a space.
+
+                       // If true, items in a collection, are separated by a \n, else just a space.
                        multiline: true
                };
 
        return dump;
-}());
+}() );
 
-// back compat
+// Back compat
 QUnit.jsDump = QUnit.dump;
 
 // Deprecated
 // Extend assert methods to QUnit for Backwards compatibility
-(function() {
+( function() {
        var i,
                assertions = Assert.prototype;
 
@@ -2190,12 +2198,12 @@ QUnit.jsDump = QUnit.dump;
        for ( i in assertions ) {
                QUnit[ i ] = applyCurrent( assertions[ i ] );
        }
-})();
+}() );
 
 // For browser, export only select globals
 if ( defined.document ) {
 
-       (function() {
+       ( function() {
                var i, l,
                        keys = [
                                "test",
@@ -2221,7 +2229,7 @@ if ( defined.document ) {
                for ( i = 0, l = keys.length; i < l; i++ ) {
                        window[ keys[ i ] ] = QUnit[ keys[ i ] ];
                }
-       })();
+       }() );
 
        window.QUnit = QUnit;
 }
@@ -2246,1959 +2254,2081 @@ if ( typeof define === "function" && define.amd ) {
        QUnit.config.autostart = false;
 }
 
-/*
- * This file is a modified version of google-diff-match-patch's JavaScript implementation
- * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
- * modifications are licensed as more fully set forth in LICENSE.txt.
- *
- * The original source of google-diff-match-patch is attributable and licensed as follows:
- *
- * Copyright 2006 Google Inc.
- * https://code.google.com/p/google-diff-match-patch/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * More Info:
- *  https://code.google.com/p/google-diff-match-patch/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- */
-QUnit.diff = ( function() {
-       function DiffMatchPatch() {
-       }
+// Get a reference to the global object, like window in browsers
+}( ( function() {
+       return this;
+}() ) ) );
 
-       //  DIFF FUNCTIONS
+( function() {
 
-       /**
-        * The data structure representing a diff is an array of tuples:
-        * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
-        * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
-        */
-       var DIFF_DELETE = -1,
-               DIFF_INSERT = 1,
-               DIFF_EQUAL = 0;
+// Only interact with URLs via window.location
+var location = typeof window !== "undefined" && window.location;
+if ( !location ) {
+       return;
+}
 
-       /**
-        * Find the differences between two texts.  Simplifies the problem by stripping
-        * any common prefix or suffix off the texts before diffing.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean=} optChecklines Optional speedup flag. If present and false,
-        *     then don't run a line-level diff first to identify the changed areas.
-        *     Defaults to true, which does a faster, slightly less optimal diff.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
-               var deadline, checklines, commonlength,
-                       commonprefix, commonsuffix, diffs;
+var urlParams = getUrlParams();
 
-               // The diff must be complete in up to 1 second.
-               deadline = ( new Date() ).getTime() + 1000;
+QUnit.urlParams = urlParams;
 
-               // Check for null inputs.
-               if ( text1 === null || text2 === null ) {
-                       throw new Error( "Null input. (DiffMain)" );
-               }
+// Match module/test by inclusion in an array
+QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
+QUnit.config.testId = [].concat( urlParams.testId || [] );
 
-               // Check for equality (speedup).
-               if ( text1 === text2 ) {
-                       if ( text1 ) {
-                               return [
-                                       [ DIFF_EQUAL, text1 ]
-                               ];
-                       }
-                       return [];
-               }
+// Exact case-insensitive match of the module name
+QUnit.config.module = urlParams.module;
 
-               if ( typeof optChecklines === "undefined" ) {
-                       optChecklines = true;
-               }
+// Regular expression or case-insenstive substring match against "moduleName: testName"
+QUnit.config.filter = urlParams.filter;
 
-               checklines = optChecklines;
+// Test order randomization
+if ( urlParams.seed === true ) {
 
-               // Trim off common prefix (speedup).
-               commonlength = this.diffCommonPrefix( text1, text2 );
-               commonprefix = text1.substring( 0, commonlength );
-               text1 = text1.substring( commonlength );
-               text2 = text2.substring( commonlength );
+       // Generate a random seed if the option is specified without a value
+       QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
+} else if ( urlParams.seed ) {
+       QUnit.config.seed = urlParams.seed;
+}
 
-               // Trim off common suffix (speedup).
-               commonlength = this.diffCommonSuffix( text1, text2 );
-               commonsuffix = text1.substring( text1.length - commonlength );
-               text1 = text1.substring( 0, text1.length - commonlength );
-               text2 = text2.substring( 0, text2.length - commonlength );
+// Add URL-parameter-mapped config values with UI form rendering data
+QUnit.config.urlConfig.push(
+       {
+               id: "hidepassed",
+               label: "Hide passed tests",
+               tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+       },
+       {
+               id: "noglobals",
+               label: "Check for Globals",
+               tooltip: "Enabling this will test if any test introduces new properties on the " +
+                       "global object (`window` in Browsers). Stored as query-strings."
+       },
+       {
+               id: "notrycatch",
+               label: "No try-catch",
+               tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+                       "exceptions in IE reasonable. Stored as query-strings."
+       }
+);
 
-               // Compute the diff on the middle block.
-               diffs = this.diffCompute( text1, text2, checklines, deadline );
+QUnit.begin( function() {
+       var i, option,
+               urlConfig = QUnit.config.urlConfig;
 
-               // Restore the prefix and suffix.
-               if ( commonprefix ) {
-                       diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
-               }
-               if ( commonsuffix ) {
-                       diffs.push( [ DIFF_EQUAL, commonsuffix ] );
-               }
-               this.diffCleanupMerge( diffs );
-               return diffs;
-       };
+       for ( i = 0; i < urlConfig.length; i++ ) {
 
-       /**
-        * Reduce the number of edits by eliminating operationally trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, preIns, preDel, postIns, postDel;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-               // Is there an insertion operation before the last equality.
-               preIns = false;
-               // Is there a deletion operation before the last equality.
-               preDel = false;
-               // Is there an insertion operation after the last equality.
-               postIns = false;
-               // Is there a deletion operation after the last equality.
-               postDel = false;
-               while ( pointer < diffs.length ) {
+               // Options can be either strings or objects with nonempty "id" properties
+               option = QUnit.config.urlConfig[ i ];
+               if ( typeof option !== "string" ) {
+                       option = option.id;
+               }
 
-                       // Equality found.
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
-                               if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
+               if ( QUnit.config[ option ] === undefined ) {
+                       QUnit.config[ option ] = urlParams[ option ];
+               }
+       }
+} );
 
-                                       // Candidate found.
-                                       equalities[ equalitiesLength++ ] = pointer;
-                                       preIns = postIns;
-                                       preDel = postDel;
-                                       lastequality = diffs[ pointer ][ 1 ];
-                               } else {
+function getUrlParams() {
+       var i, param, name, value;
+       var urlParams = {};
+       var params = location.search.slice( 1 ).split( "&" );
+       var length = params.length;
 
-                                       // Not a candidate, and can never become one.
-                                       equalitiesLength = 0;
-                                       lastequality = null;
-                               }
-                               postIns = postDel = false;
+       for ( i = 0; i < length; i++ ) {
+               if ( params[ i ] ) {
+                       param = params[ i ].split( "=" );
+                       name = decodeURIComponent( param[ 0 ] );
 
-                       // An insertion or deletion.
+                       // Allow just a key to turn on a flag, e.g., test.html?noglobals
+                       value = param.length === 1 ||
+                               decodeURIComponent( param.slice( 1 ).join( "=" ) ) ;
+                       if ( urlParams[ name ] ) {
+                               urlParams[ name ] = [].concat( urlParams[ name ], value );
                        } else {
+                               urlParams[ name ] = value;
+                       }
+               }
+       }
 
-                               if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
-                                       postDel = true;
-                               } else {
-                                       postIns = true;
-                               }
+       return urlParams;
+}
 
-                               /*
-                                * Five types to be split:
-                                * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
-                                * <ins>A</ins>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<ins>C</ins>
-                                * <ins>A</del>X<ins>C</ins><del>D</del>
-                                * <ins>A</ins><del>B</del>X<del>C</del>
-                                */
-                               if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
-                                               ( ( lastequality.length < 2 ) &&
-                                               ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
+// Don't load the HTML Reporter on non-browser environments
+if ( typeof window === "undefined" || !window.document ) {
+       return;
+}
 
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+       var config = QUnit.config;
 
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
-                                       equalitiesLength--; // Throw away the equality we just deleted;
-                                       lastequality = null;
-                                       if ( preIns && preDel ) {
-                                               // No changes made which could affect previous entry, keep going.
-                                               postIns = postDel = true;
-                                               equalitiesLength = 0;
-                                       } else {
-                                               equalitiesLength--; // Throw away the previous equality.
-                                               pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
-                                               postIns = postDel = false;
-                                       }
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
+       config.stats = { all: 0, bad: 0 };
+       config.moduleStats = { all: 0, bad: 0 };
+       config.started = 0;
+       config.updateRate = 1000;
+       config.blocking = false;
+       config.autostart = true;
+       config.autorun = false;
+       config.filter = "";
+       config.queue = [];
 
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
-               }
-       };
+       appendInterface();
+};
 
-       /**
-        * Convert a diff array into a pretty HTML report.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {integer} string to be beautified.
-        * @return {string} HTML representation.
-        */
-       DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
-               var op, data, x,
-                       html = [];
-               for ( x = 0; x < diffs.length; x++ ) {
-                       op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
-                       data = diffs[ x ][ 1 ]; // Text of change.
-                       switch ( op ) {
-                       case DIFF_INSERT:
-                               html[ x ] = "<ins>" + data + "</ins>";
-                               break;
-                       case DIFF_DELETE:
-                               html[ x ] = "<del>" + data + "</del>";
-                               break;
-                       case DIFF_EQUAL:
-                               html[ x ] = "<span>" + data + "</span>";
-                               break;
+var config = QUnit.config,
+       document = window.document,
+       collapseNext = false,
+       hasOwn = Object.prototype.hasOwnProperty,
+       unfilteredUrl = setUrl( { filter: undefined, module: undefined,
+               moduleId: undefined, testId: undefined } ),
+       defined = {
+               sessionStorage: ( function() {
+                       var x = "qunit-test-string";
+                       try {
+                               sessionStorage.setItem( x, x );
+                               sessionStorage.removeItem( x );
+                               return true;
+                       } catch ( e ) {
+                               return false;
                        }
-               }
-               return html.join( "" );
-       };
+               }() )
+       },
+       modulesList = [];
 
-       /**
-        * Determine the common prefix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the start of each
-        *     string.
-        */
-       DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerstart;
-               // Quick check for common null cases.
-               if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
-                       return 0;
-               }
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerstart = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( pointerstart, pointermid ) ===
-                                       text2.substring( pointerstart, pointermid ) ) {
-                               pointermin = pointermid;
-                               pointerstart = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
-               }
-               return pointermid;
-       };
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+       if ( !s ) {
+               return "";
+       }
+       s = s + "";
 
-       /**
-        * Determine the common suffix of two strings.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of each string.
-        */
-       DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
-               var pointermid, pointermax, pointermin, pointerend;
-               // Quick check for common null cases.
-               if ( !text1 ||
-                               !text2 ||
-                               text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
-                       return 0;
-               }
-               // Binary search.
-               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
-               pointermin = 0;
-               pointermax = Math.min( text1.length, text2.length );
-               pointermid = pointermax;
-               pointerend = 0;
-               while ( pointermin < pointermid ) {
-                       if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
-                                       text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
-                               pointermin = pointermid;
-                               pointerend = pointermin;
-                       } else {
-                               pointermax = pointermid;
-                       }
-                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+       // Both single quotes and double quotes (for attributes)
+       return s.replace( /['"<>&]/g, function( s ) {
+               switch ( s ) {
+               case "'":
+                       return "&#039;";
+               case "\"":
+                       return "&quot;";
+               case "<":
+                       return "&lt;";
+               case ">":
+                       return "&gt;";
+               case "&":
+                       return "&amp;";
                }
-               return pointermid;
-       };
-
-       /**
-        * Find the differences between two texts.  Assumes that the texts do not
-        * have any common prefix or suffix.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {boolean} checklines Speedup flag.  If false, then don't run a
-        *     line-level diff first to identify the changed areas.
-        *     If true, then run a faster, slightly less optimal diff.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
-               var diffs, longtext, shorttext, i, hm,
-                       text1A, text2A, text1B, text2B,
-                       midCommon, diffsA, diffsB;
+       } );
+}
 
-               if ( !text1 ) {
-                       // Just add some text (speedup).
-                       return [
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+       if ( elem.addEventListener ) {
 
-               if ( !text2 ) {
-                       // Just delete some text (speedup).
-                       return [
-                               [ DIFF_DELETE, text1 ]
-                       ];
-               }
+               // Standards-based browsers
+               elem.addEventListener( type, fn, false );
+       } else if ( elem.attachEvent ) {
 
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               i = longtext.indexOf( shorttext );
-               if ( i !== -1 ) {
-                       // Shorter text is inside the longer text (speedup).
-                       diffs = [
-                               [ DIFF_INSERT, longtext.substring( 0, i ) ],
-                               [ DIFF_EQUAL, shorttext ],
-                               [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
-                       ];
-                       // Swap insertions for deletions if diff is reversed.
-                       if ( text1.length > text2.length ) {
-                               diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
+               // Support: IE <9
+               elem.attachEvent( "on" + type, function() {
+                       var event = window.event;
+                       if ( !event.target ) {
+                               event.target = event.srcElement || document;
                        }
-                       return diffs;
-               }
 
-               if ( shorttext.length === 1 ) {
-                       // Single character string.
-                       // After the previous speedup, the character can't be an equality.
-                       return [
-                               [ DIFF_DELETE, text1 ],
-                               [ DIFF_INSERT, text2 ]
-                       ];
-               }
+                       fn.call( elem, event );
+               } );
+       }
+}
 
-               // Check to see if the problem can be split in two.
-               hm = this.diffHalfMatch( text1, text2 );
-               if ( hm ) {
-                       // A half-match was found, sort out the return data.
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-                       midCommon = hm[ 4 ];
-                       // Send both pairs off for separate processing.
-                       diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
-                       diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
-                       // Merge the results.
-                       return diffsA.concat( [
-                               [ DIFF_EQUAL, midCommon ]
-                       ], diffsB );
-               }
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+       var i = elems.length;
+       while ( i-- ) {
+               addEvent( elems[ i ], type, fn );
+       }
+}
 
-               if ( checklines && text1.length > 100 && text2.length > 100 ) {
-                       return this.diffLineMode( text1, text2, deadline );
-               }
+function hasClass( elem, name ) {
+       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
 
-               return this.diffBisect( text1, text2, deadline );
-       };
+function addClass( elem, name ) {
+       if ( !hasClass( elem, name ) ) {
+               elem.className += ( elem.className ? " " : "" ) + name;
+       }
+}
 
-       /**
-        * Do the two texts share a substring which is at least half the length of the
-        * longer text?
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {Array.<string>} Five element Array, containing the prefix of
-        *     text1, the suffix of text1, the prefix of text2, the suffix of
-        *     text2 and the common middle.  Or null if there was no match.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
-               var longtext, shorttext, dmp,
-                       text1A, text2B, text2A, text1B, midCommon,
-                       hm1, hm2, hm;
+function toggleClass( elem, name, force ) {
+       if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) {
+               addClass( elem, name );
+       } else {
+               removeClass( elem, name );
+       }
+}
 
-               longtext = text1.length > text2.length ? text1 : text2;
-               shorttext = text1.length > text2.length ? text2 : text1;
-               if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
-                       return null; // Pointless.
+function removeClass( elem, name ) {
+       var set = " " + elem.className + " ";
+
+       // Class name may appear multiple times
+       while ( set.indexOf( " " + name + " " ) >= 0 ) {
+               set = set.replace( " " + name + " ", " " );
+       }
+
+       // Trim for prettiness
+       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+       return document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+       var i, j, val,
+               escaped, escapedTooltip,
+               selection = false,
+               urlConfig = config.urlConfig,
+               urlConfigHtml = "";
+
+       for ( i = 0; i < urlConfig.length; i++ ) {
+
+               // Options can be either strings or objects with nonempty "id" properties
+               val = config.urlConfig[ i ];
+               if ( typeof val === "string" ) {
+                       val = {
+                               id: val,
+                               label: val
+                       };
                }
-               dmp = this; // 'this' becomes 'window' in a closure.
 
-               /**
-                * Does a substring of shorttext exist within longtext such that the substring
-                * is at least half the length of longtext?
-                * Closure, but does not reference any external variables.
-                * @param {string} longtext Longer string.
-                * @param {string} shorttext Shorter string.
-                * @param {number} i Start index of quarter length substring within longtext.
-                * @return {Array.<string>} Five element Array, containing the prefix of
-                *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
-                *     of shorttext and the common middle.  Or null if there was no match.
-                * @private
-                */
-               function diffHalfMatchI( longtext, shorttext, i ) {
-                       var seed, j, bestCommon, prefixLength, suffixLength,
-                               bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
-                       // Start with a 1/4 length substring at position i as a seed.
-                       seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
-                       j = -1;
-                       bestCommon = "";
-                       while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
-                               prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
-                                       shorttext.substring( j ) );
-                               suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
-                                       shorttext.substring( 0, j ) );
-                               if ( bestCommon.length < suffixLength + prefixLength ) {
-                                       bestCommon = shorttext.substring( j - suffixLength, j ) +
-                                               shorttext.substring( j, j + prefixLength );
-                                       bestLongtextA = longtext.substring( 0, i - suffixLength );
-                                       bestLongtextB = longtext.substring( i + prefixLength );
-                                       bestShorttextA = shorttext.substring( 0, j - suffixLength );
-                                       bestShorttextB = shorttext.substring( j + prefixLength );
+               escaped = escapeText( val.id );
+               escapedTooltip = escapeText( val.tooltip );
+
+               if ( !val.value || typeof val.value === "string" ) {
+                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' type='checkbox'" +
+                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+                               ( config[ val.id ] ? " checked='checked'" : "" ) +
+                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
+               } else {
+                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
+                               "' title='" + escapedTooltip + "'>" + val.label +
+                               ": </label><select id='qunit-urlconfig-" + escaped +
+                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+                       if ( QUnit.is( "array", val.value ) ) {
+                               for ( j = 0; j < val.value.length; j++ ) {
+                                       escaped = escapeText( val.value[ j ] );
+                                       urlConfigHtml += "<option value='" + escaped + "'" +
+                                               ( config[ val.id ] === val.value[ j ] ?
+                                                       ( selection = true ) && " selected='selected'" : "" ) +
+                                               ">" + escaped + "</option>";
                                }
-                       }
-                       if ( bestCommon.length * 2 >= longtext.length ) {
-                               return [ bestLongtextA, bestLongtextB,
-                                       bestShorttextA, bestShorttextB, bestCommon
-                               ];
                        } else {
-                               return null;
+                               for ( j in val.value ) {
+                                       if ( hasOwn.call( val.value, j ) ) {
+                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+                                                       ( config[ val.id ] === j ?
+                                                               ( selection = true ) && " selected='selected'" : "" ) +
+                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
+                                       }
+                               }
+                       }
+                       if ( config[ val.id ] && !selection ) {
+                               escaped = escapeText( config[ val.id ] );
+                               urlConfigHtml += "<option value='" + escaped +
+                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
                        }
+                       urlConfigHtml += "</select>";
                }
+       }
 
-               // First check if the second quarter is the seed for a half-match.
-               hm1 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 4 ) );
-               // Check again based on the third quarter.
-               hm2 = diffHalfMatchI( longtext, shorttext,
-                       Math.ceil( longtext.length / 2 ) );
-               if ( !hm1 && !hm2 ) {
-                       return null;
-               } else if ( !hm2 ) {
-                       hm = hm1;
-               } else if ( !hm1 ) {
-                       hm = hm2;
-               } else {
-                       // Both matched.  Select the longest.
-                       hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
-               }
+       return urlConfigHtml;
+}
 
-               // A half-match was found, sort out the return data.
-               text1A, text1B, text2A, text2B;
-               if ( text1.length > text2.length ) {
-                       text1A = hm[ 0 ];
-                       text1B = hm[ 1 ];
-                       text2A = hm[ 2 ];
-                       text2B = hm[ 3 ];
-               } else {
-                       text2A = hm[ 0 ];
-                       text2B = hm[ 1 ];
-                       text1A = hm[ 2 ];
-                       text1B = hm[ 3 ];
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+       var updatedUrl, value, tests,
+               field = this,
+               params = {};
+
+       // Detect if field is a select menu or a checkbox
+       if ( "selectedIndex" in field ) {
+               value = field.options[ field.selectedIndex ].value || undefined;
+       } else {
+               value = field.checked ? ( field.defaultValue || true ) : undefined;
+       }
+
+       params[ field.name ] = value;
+       updatedUrl = setUrl( params );
+
+       // Check if we can apply the change without a page refresh
+       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+               QUnit.urlParams[ field.name ] = value;
+               config[ field.name ] = value || false;
+               tests = id( "qunit-tests" );
+               if ( tests ) {
+                       toggleClass( tests, "hidepass", value || false );
                }
-               midCommon = hm[ 4 ];
-               return [ text1A, text1B, text2A, text2B, midCommon ];
-       };
+               window.history.replaceState( null, "", updatedUrl );
+       } else {
+               window.location = updatedUrl;
+       }
+}
 
-       /**
-        * Do a quick line-level diff on both strings, then rediff the parts for
-        * greater accuracy.
-        * This speedup can produce non-minimal diffs.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time when the diff should be complete by.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
-               var a, diffs, linearray, pointer, countInsert,
-                       countDelete, textInsert, textDelete, j;
-               // Scan the text on a line-by-line basis first.
-               a = this.diffLinesToChars( text1, text2 );
-               text1 = a.chars1;
-               text2 = a.chars2;
-               linearray = a.lineArray;
+function setUrl( params ) {
+       var key, arrValue, i,
+               querystring = "?",
+               location = window.location;
 
-               diffs = this.DiffMain( text1, text2, false, deadline );
+       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
 
-               // Convert the diff back to original text.
-               this.diffCharsToLines( diffs, linearray );
-               // Eliminate freak matches (e.g. blank lines)
-               this.diffCleanupSemantic( diffs );
+       for ( key in params ) {
 
-               // Rediff any replacement blocks, this time character-by-character.
-               // Add a dummy entry at the end.
-               diffs.push( [ DIFF_EQUAL, "" ] );
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               break;
-                       case DIFF_EQUAL:
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete >= 1 && countInsert >= 1 ) {
-                                       // Delete the offending records and add the merged ones.
-                                       diffs.splice( pointer - countDelete - countInsert,
-                                               countDelete + countInsert );
-                                       pointer = pointer - countDelete - countInsert;
-                                       a = this.DiffMain( textDelete, textInsert, false, deadline );
-                                       for ( j = a.length - 1; j >= 0; j-- ) {
-                                               diffs.splice( pointer, 0, a[ j ] );
-                                       }
-                                       pointer = pointer + a.length;
+               // Skip inherited or undefined properties
+               if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
+
+                       // Output a parameter for each value of this key (but usually just one)
+                       arrValue = [].concat( params[ key ] );
+                       for ( i = 0; i < arrValue.length; i++ ) {
+                               querystring += encodeURIComponent( key );
+                               if ( arrValue[ i ] !== true ) {
+                                       querystring += "=" + encodeURIComponent( arrValue[ i ] );
                                }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
+                               querystring += "&";
                        }
-                       pointer++;
                }
-               diffs.pop(); // Remove the dummy entry at the end.
+       }
+       return location.protocol + "//" + location.host +
+               location.pathname + querystring.slice( 0, -1 );
+}
 
-               return diffs;
-       };
+function applyUrlParams() {
+       var selectedModule,
+               modulesList = id( "qunit-modulefilter" ),
+               filter = id( "qunit-filter-input" ).value;
 
-       /**
-        * Find the 'middle snake' of a diff, split the problem in two
-        * and return the recursively constructed diff.
-        * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
-               var text1Length, text2Length, maxD, vOffset, vLength,
-                       v1, v2, x, delta, front, k1start, k1end, k2start,
-                       k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-               maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
-               vOffset = maxD;
-               vLength = 2 * maxD;
-               v1 = new Array( vLength );
-               v2 = new Array( vLength );
-               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
-               // integers and undefined.
-               for ( x = 0; x < vLength; x++ ) {
-                       v1[ x ] = -1;
-                       v2[ x ] = -1;
+       selectedModule = modulesList ?
+               decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
+               undefined;
+
+       window.location = setUrl( {
+               module: ( selectedModule === "" ) ? undefined : selectedModule,
+               filter: ( filter === "" ) ? undefined : filter,
+
+               // Remove moduleId and testId filters
+               moduleId: undefined,
+               testId: undefined
+       } );
+}
+
+function toolbarUrlConfigContainer() {
+       var urlConfigContainer = document.createElement( "span" );
+
+       urlConfigContainer.innerHTML = getUrlConfigHtml();
+       addClass( urlConfigContainer, "qunit-url-config" );
+
+       // For oldIE support:
+       // * Add handlers to the individual elements instead of the container
+       // * Use "click" instead of "change" for checkboxes
+       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+       return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+       var filter = document.createElement( "form" ),
+               label = document.createElement( "label" ),
+               input = document.createElement( "input" ),
+               button = document.createElement( "button" );
+
+       addClass( filter, "qunit-filter" );
+
+       label.innerHTML = "Filter: ";
+
+       input.type = "text";
+       input.value = config.filter || "";
+       input.name = "filter";
+       input.id = "qunit-filter-input";
+
+       button.innerHTML = "Go";
+
+       label.appendChild( input );
+
+       filter.appendChild( label );
+       filter.appendChild( button );
+       addEvent( filter, "submit", function( ev ) {
+               applyUrlParams();
+
+               if ( ev && ev.preventDefault ) {
+                       ev.preventDefault();
                }
-               v1[ vOffset + 1 ] = 0;
-               v2[ vOffset + 1 ] = 0;
-               delta = text1Length - text2Length;
-               // If the total number of characters is odd, then the front path will collide
-               // with the reverse path.
-               front = ( delta % 2 !== 0 );
-               // Offsets for start and end of k loop.
-               // Prevents mapping of space beyond the grid.
-               k1start = 0;
-               k1end = 0;
-               k2start = 0;
-               k2end = 0;
-               for ( d = 0; d < maxD; d++ ) {
-                       // Bail out if deadline is reached.
-                       if ( ( new Date() ).getTime() > deadline ) {
-                               break;
-                       }
 
-                       // Walk the front path one step.
-                       for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
-                               k1Offset = vOffset + k1;
-                               if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
-                                       x1 = v1[ k1Offset + 1 ];
-                               } else {
-                                       x1 = v1[ k1Offset - 1 ] + 1;
-                               }
-                               y1 = x1 - k1;
-                               while ( x1 < text1Length && y1 < text2Length &&
-                                       text1.charAt( x1 ) === text2.charAt( y1 ) ) {
-                                       x1++;
-                                       y1++;
-                               }
-                               v1[ k1Offset ] = x1;
-                               if ( x1 > text1Length ) {
-                                       // Ran off the right of the graph.
-                                       k1end += 2;
-                               } else if ( y1 > text2Length ) {
-                                       // Ran off the bottom of the graph.
-                                       k1start += 2;
-                               } else if ( front ) {
-                                       k2Offset = vOffset + delta - k1;
-                                       if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - v2[ k2Offset ];
-                                               if ( x1 >= x2 ) {
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
+               return false;
+       } );
+
+       return filter;
+}
+
+function toolbarModuleFilterHtml() {
+       var i,
+               moduleFilterHtml = "";
+
+       if ( !modulesList.length ) {
+               return false;
+       }
+
+       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
+               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
+               ">< All Modules ></option>";
+
+       for ( i = 0; i < modulesList.length; i++ ) {
+               moduleFilterHtml += "<option value='" +
+                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+                       ">" + escapeText( modulesList[ i ] ) + "</option>";
+       }
+       moduleFilterHtml += "</select>";
+
+       return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+       var toolbar = id( "qunit-testrunner-toolbar" ),
+               moduleFilter = document.createElement( "span" ),
+               moduleFilterHtml = toolbarModuleFilterHtml();
+
+       if ( !toolbar || !moduleFilterHtml ) {
+               return false;
+       }
+
+       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+       moduleFilter.innerHTML = moduleFilterHtml;
+
+       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+       toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+       var toolbar = id( "qunit-testrunner-toolbar" );
+
+       if ( toolbar ) {
+               toolbar.appendChild( toolbarUrlConfigContainer() );
+               toolbar.appendChild( toolbarLooseFilter() );
+               toolbarModuleFilter();
+       }
+}
+
+function appendHeader() {
+       var header = id( "qunit-header" );
+
+       if ( header ) {
+               header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
+                       "</a> ";
+       }
+}
+
+function appendBanner() {
+       var banner = id( "qunit-banner" );
+
+       if ( banner ) {
+               banner.className = "";
+       }
+}
+
+function appendTestResults() {
+       var tests = id( "qunit-tests" ),
+               result = id( "qunit-testresult" );
+
+       if ( result ) {
+               result.parentNode.removeChild( result );
+       }
+
+       if ( tests ) {
+               tests.innerHTML = "";
+               result = document.createElement( "p" );
+               result.id = "qunit-testresult";
+               result.className = "result";
+               tests.parentNode.insertBefore( result, tests );
+               result.innerHTML = "Running...<br />&#160;";
+       }
+}
+
+function storeFixture() {
+       var fixture = id( "qunit-fixture" );
+       if ( fixture ) {
+               config.fixture = fixture.innerHTML;
+       }
+}
+
+function appendFilteredTest() {
+       var testId = QUnit.config.testId;
+       if ( !testId || testId.length <= 0 ) {
+               return "";
+       }
+       return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
+               escapeText( testId.join( ", " ) ) +
+               " <a id='qunit-clearFilter' href='" +
+               escapeText( unfilteredUrl ) +
+               "'>Run all tests</a></div>";
+}
+
+function appendUserAgent() {
+       var userAgent = id( "qunit-userAgent" );
+
+       if ( userAgent ) {
+               userAgent.innerHTML = "";
+               userAgent.appendChild(
+                       document.createTextNode(
+                               "QUnit " + QUnit.version + "; " + navigator.userAgent
+                       )
+               );
+       }
+}
 
-                       // Walk the reverse path one step.
-                       for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
-                               k2Offset = vOffset + k2;
-                               if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
-                                       x2 = v2[ k2Offset + 1 ];
-                               } else {
-                                       x2 = v2[ k2Offset - 1 ] + 1;
-                               }
-                               y2 = x2 - k2;
-                               while ( x2 < text1Length && y2 < text2Length &&
-                                       text1.charAt( text1Length - x2 - 1 ) ===
-                                       text2.charAt( text2Length - y2 - 1 ) ) {
-                                       x2++;
-                                       y2++;
-                               }
-                               v2[ k2Offset ] = x2;
-                               if ( x2 > text1Length ) {
-                                       // Ran off the left of the graph.
-                                       k2end += 2;
-                               } else if ( y2 > text2Length ) {
-                                       // Ran off the top of the graph.
-                                       k2start += 2;
-                               } else if ( !front ) {
-                                       k1Offset = vOffset + delta - k2;
-                                       if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
-                                               x1 = v1[ k1Offset ];
-                                               y1 = vOffset + x1 - k1Offset;
-                                               // Mirror x2 onto top-left coordinate system.
-                                               x2 = text1Length - x2;
-                                               if ( x1 >= x2 ) {
-                                                       // Overlap detected.
-                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
-                                               }
-                                       }
-                               }
-                       }
+function appendInterface() {
+       var qunit = id( "qunit" );
+
+       if ( qunit ) {
+               qunit.innerHTML =
+                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+                       "<h2 id='qunit-banner'></h2>" +
+                       "<div id='qunit-testrunner-toolbar'></div>" +
+                       appendFilteredTest() +
+                       "<h2 id='qunit-userAgent'></h2>" +
+                       "<ol id='qunit-tests'></ol>";
+       }
+
+       appendHeader();
+       appendBanner();
+       appendTestResults();
+       appendUserAgent();
+       appendToolbar();
+}
+
+function appendTestsList( modules ) {
+       var i, l, x, z, test, moduleObj;
+
+       for ( i = 0, l = modules.length; i < l; i++ ) {
+               moduleObj = modules[ i ];
+
+               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+                       test = moduleObj.tests[ x ];
+
+                       appendTest( test.name, test.testId, moduleObj.name );
                }
-               // Diff took too long and hit the deadline or
-               // number of diffs equals number of characters, no commonality at all.
-               return [
-                       [ DIFF_DELETE, text1 ],
-                       [ DIFF_INSERT, text2 ]
-               ];
-       };
+       }
+}
 
-       /**
-        * Given the location of the 'middle snake', split the diff in two parts
-        * and recurse.
-        * @param {string} text1 Old string to be diffed.
-        * @param {string} text2 New string to be diffed.
-        * @param {number} x Index of split point in text1.
-        * @param {number} y Index of split point in text2.
-        * @param {number} deadline Time at which to bail if not yet complete.
-        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
-               var text1a, text1b, text2a, text2b, diffs, diffsb;
-               text1a = text1.substring( 0, x );
-               text2a = text2.substring( 0, y );
-               text1b = text1.substring( x );
-               text2b = text2.substring( y );
+function appendTest( name, testId, moduleName ) {
+       var title, rerunTrigger, testBlock, assertList,
+               tests = id( "qunit-tests" );
 
-               // Compute both diffs serially.
-               diffs = this.DiffMain( text1a, text2a, false, deadline );
-               diffsb = this.DiffMain( text1b, text2b, false, deadline );
+       if ( !tests ) {
+               return;
+       }
 
-               return diffs.concat( diffsb );
-       };
+       title = document.createElement( "strong" );
+       title.innerHTML = getNameHtml( name, moduleName );
 
-       /**
-        * Reduce the number of edits by eliminating semantically trivial equalities.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
-               var changes, equalities, equalitiesLength, lastequality,
-                       pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
-                       lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
-               changes = false;
-               equalities = []; // Stack of indices where equalities are found.
-               equalitiesLength = 0; // Keeping our own length var is faster in JS.
-               /** @type {?string} */
-               lastequality = null;
-               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
-               pointer = 0; // Index of current position.
-               // Number of characters that changed prior to the equality.
-               lengthInsertions1 = 0;
-               lengthDeletions1 = 0;
-               // Number of characters that changed after the equality.
-               lengthInsertions2 = 0;
-               lengthDeletions2 = 0;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
-                               equalities[ equalitiesLength++ ] = pointer;
-                               lengthInsertions1 = lengthInsertions2;
-                               lengthDeletions1 = lengthDeletions2;
-                               lengthInsertions2 = 0;
-                               lengthDeletions2 = 0;
-                               lastequality = diffs[ pointer ][ 1 ];
-                       } else { // An insertion or deletion.
-                               if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                                       lengthInsertions2 += diffs[ pointer ][ 1 ].length;
-                               } else {
-                                       lengthDeletions2 += diffs[ pointer ][ 1 ].length;
-                               }
-                               // Eliminate an equality that is smaller or equal to the edits on both
-                               // sides of it.
-                               if ( lastequality && ( lastequality.length <=
-                                               Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
-                                               ( lastequality.length <= Math.max( lengthInsertions2,
-                                                       lengthDeletions2 ) ) ) {
+       rerunTrigger = document.createElement( "a" );
+       rerunTrigger.innerHTML = "Rerun";
+       rerunTrigger.href = setUrl( { testId: testId } );
 
-                                       // Duplicate record.
-                                       diffs.splice(
-                                               equalities[ equalitiesLength - 1 ],
-                                               0,
-                                               [ DIFF_DELETE, lastequality ]
-                                       );
+       testBlock = document.createElement( "li" );
+       testBlock.appendChild( title );
+       testBlock.appendChild( rerunTrigger );
+       testBlock.id = "qunit-test-output-" + testId;
 
-                                       // Change second copy to insert.
-                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+       assertList = document.createElement( "ol" );
+       assertList.className = "qunit-assert-list";
 
-                                       // Throw away the equality we just deleted.
-                                       equalitiesLength--;
+       testBlock.appendChild( assertList );
 
-                                       // Throw away the previous equality (it needs to be reevaluated).
-                                       equalitiesLength--;
-                                       pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+       tests.appendChild( testBlock );
+}
 
-                                       // Reset the counters.
-                                       lengthInsertions1 = 0;
-                                       lengthDeletions1 = 0;
-                                       lengthInsertions2 = 0;
-                                       lengthDeletions2 = 0;
-                                       lastequality = null;
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
+// HTML Reporter initialization and load
+QUnit.begin( function( details ) {
+       var i, moduleObj, tests;
 
-               // Normalize the diff.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
+       // Sort modules by name for the picker
+       for ( i = 0; i < details.modules.length; i++ ) {
+               moduleObj = details.modules[ i ];
+               if ( moduleObj.name ) {
+                       modulesList.push( moduleObj.name );
                }
+       }
+       modulesList.sort( function( a, b ) {
+               return a.localeCompare( b );
+       } );
 
-               // Find any overlaps between deletions and insertions.
-               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
-               //   -> <del>abc</del>xxx<ins>def</ins>
-               // e.g: <del>xxxabc</del><ins>defxxx</ins>
-               //   -> <ins>def</ins>xxx<del>abc</del>
-               // Only extract an overlap if it is as big as the edit ahead or behind it.
-               pointer = 1;
-               while ( pointer < diffs.length ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
-                                       diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
-                               deletion = diffs[ pointer - 1 ][ 1 ];
-                               insertion = diffs[ pointer ][ 1 ];
-                               overlapLength1 = this.diffCommonOverlap( deletion, insertion );
-                               overlapLength2 = this.diffCommonOverlap( insertion, deletion );
-                               if ( overlapLength1 >= overlapLength2 ) {
-                                       if ( overlapLength1 >= deletion.length / 2 ||
-                                                       overlapLength1 >= insertion.length / 2 ) {
-                                               // Overlap found.  Insert an equality and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
-                                               );
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       deletion.substring( 0, deletion.length - overlapLength1 );
-                                               diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
-                                               pointer++;
-                                       }
-                               } else {
-                                       if ( overlapLength2 >= deletion.length / 2 ||
-                                                       overlapLength2 >= insertion.length / 2 ) {
+       // Capture fixture HTML from the page
+       storeFixture();
 
-                                               // Reverse overlap found.
-                                               // Insert an equality and swap and trim the surrounding edits.
-                                               diffs.splice(
-                                                       pointer,
-                                                       0,
-                                                       [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
-                                               );
+       // Initialize QUnit elements
+       appendInterface();
+       appendTestsList( details.modules );
+       tests = id( "qunit-tests" );
+       if ( tests && config.hidepassed ) {
+               addClass( tests, "hidepass" );
+       }
+} );
+
+QUnit.done( function( details ) {
+       var i, key,
+               banner = id( "qunit-banner" ),
+               tests = id( "qunit-tests" ),
+               html = [
+                       "Tests completed in ",
+                       details.runtime,
+                       " milliseconds.<br />",
+                       "<span class='passed'>",
+                       details.passed,
+                       "</span> assertions of <span class='total'>",
+                       details.total,
+                       "</span> passed, <span class='failed'>",
+                       details.failed,
+                       "</span> failed."
+               ].join( "" );
+
+       if ( banner ) {
+               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+       }
+
+       if ( tests ) {
+               id( "qunit-testresult" ).innerHTML = html;
+       }
+
+       if ( config.altertitle && document.title ) {
 
-                                               diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
-                                               diffs[ pointer - 1 ][ 1 ] =
-                                                       insertion.substring( 0, insertion.length - overlapLength2 );
-                                               diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
-                                               diffs[ pointer + 1 ][ 1 ] =
-                                                       deletion.substring( overlapLength2 );
-                                               pointer++;
-                                       }
-                               }
-                               pointer++;
+               // Show âœ– for good, âœ” for bad suite result in title
+               // use escape sequences in case file gets loaded with non-utf-8-charset
+               document.title = [
+                       ( details.failed ? "\u2716" : "\u2714" ),
+                       document.title.replace( /^[\u2714\u2716] /i, "" )
+               ].join( " " );
+       }
+
+       // Clear own sessionStorage items if all tests passed
+       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+               for ( i = 0; i < sessionStorage.length; i++ ) {
+                       key = sessionStorage.key( i++ );
+                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
+                               sessionStorage.removeItem( key );
                        }
-                       pointer++;
                }
-       };
+       }
 
-       /**
-        * Determine if the suffix of one string is the prefix of another.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {number} The number of characters common to the end of the first
-        *     string and the start of the second string.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
-               var text1Length, text2Length, textLength,
-                       best, length, pattern, found;
-               // Cache the text lengths to prevent multiple calls.
-               text1Length = text1.length;
-               text2Length = text2.length;
-               // Eliminate the null case.
-               if ( text1Length === 0 || text2Length === 0 ) {
-                       return 0;
-               }
-               // Truncate the longer string.
-               if ( text1Length > text2Length ) {
-                       text1 = text1.substring( text1Length - text2Length );
-               } else if ( text1Length < text2Length ) {
-                       text2 = text2.substring( 0, text1Length );
-               }
-               textLength = Math.min( text1Length, text2Length );
-               // Quick check for the worst case.
-               if ( text1 === text2 ) {
-                       return textLength;
-               }
+       // Scroll back to top to show results
+       if ( config.scrolltop && window.scrollTo ) {
+               window.scrollTo( 0, 0 );
+       }
+} );
 
-               // Start by looking for a single character match
-               // and increase length until no match is found.
-               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
-               best = 0;
-               length = 1;
-               while ( true ) {
-                       pattern = text1.substring( textLength - length );
-                       found = text2.indexOf( pattern );
-                       if ( found === -1 ) {
-                               return best;
-                       }
-                       length += found;
-                       if ( found === 0 || text1.substring( textLength - length ) ===
-                                       text2.substring( 0, length ) ) {
-                               best = length;
-                               length++;
-                       }
-               }
-       };
+function getNameHtml( name, module ) {
+       var nameHtml = "";
 
-       /**
-        * Split two texts into an array of strings.  Reduce the texts to a string of
-        * hashes where each Unicode character represents one line.
-        * @param {string} text1 First string.
-        * @param {string} text2 Second string.
-        * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
-        *     An object containing the encoded text1, the encoded text2 and
-        *     the array of unique strings.
-        *     The zeroth element of the array of unique strings is intentionally blank.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
-               var lineArray, lineHash, chars1, chars2;
-               lineArray = []; // e.g. lineArray[4] === 'Hello\n'
-               lineHash = {}; // e.g. lineHash['Hello\n'] === 4
+       if ( module ) {
+               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
+       }
 
-               // '\x00' is a valid character, but various debuggers don't like it.
-               // So we'll insert a junk entry to avoid generating a null character.
-               lineArray[ 0 ] = "";
+       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
 
-               /**
-                * Split a text into an array of strings.  Reduce the texts to a string of
-                * hashes where each Unicode character represents one line.
-                * Modifies linearray and linehash through being a closure.
-                * @param {string} text String to encode.
-                * @return {string} Encoded string.
-                * @private
-                */
-               function diffLinesToCharsMunge( text ) {
-                       var chars, lineStart, lineEnd, lineArrayLength, line;
-                       chars = "";
-                       // Walk the text, pulling out a substring for each line.
-                       // text.split('\n') would would temporarily double our memory footprint.
-                       // Modifying text would create many large strings to garbage collect.
-                       lineStart = 0;
-                       lineEnd = -1;
-                       // Keeping our own length variable is faster than looking it up.
-                       lineArrayLength = lineArray.length;
-                       while ( lineEnd < text.length - 1 ) {
-                               lineEnd = text.indexOf( "\n", lineStart );
-                               if ( lineEnd === -1 ) {
-                                       lineEnd = text.length - 1;
-                               }
-                               line = text.substring( lineStart, lineEnd + 1 );
-                               lineStart = lineEnd + 1;
+       return nameHtml;
+}
 
-                               if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
-                                                       ( lineHash[ line ] !== undefined ) ) {
-                                       chars += String.fromCharCode( lineHash[ line ] );
-                               } else {
-                                       chars += String.fromCharCode( lineArrayLength );
-                                       lineHash[ line ] = lineArrayLength;
-                                       lineArray[ lineArrayLength++ ] = line;
-                               }
-                       }
-                       return chars;
-               }
+QUnit.testStart( function( details ) {
+       var running, testBlock, bad;
 
-               chars1 = diffLinesToCharsMunge( text1 );
-               chars2 = diffLinesToCharsMunge( text2 );
-               return {
-                       chars1: chars1,
-                       chars2: chars2,
-                       lineArray: lineArray
-               };
-       };
+       testBlock = id( "qunit-test-output-" + details.testId );
+       if ( testBlock ) {
+               testBlock.className = "running";
+       } else {
 
-       /**
-        * Rehydrate the text in a diff from a string of line hashes to real lines of
-        * text.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        * @param {!Array.<string>} lineArray Array of unique strings.
-        * @private
-        */
-       DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
-               var x, chars, text, y;
-               for ( x = 0; x < diffs.length; x++ ) {
-                       chars = diffs[ x ][ 1 ];
-                       text = [];
-                       for ( y = 0; y < chars.length; y++ ) {
-                               text[ y ] = lineArray[ chars.charCodeAt( y ) ];
-                       }
-                       diffs[ x ][ 1 ] = text.join( "" );
-               }
-       };
+               // Report later registered tests
+               appendTest( details.name, details.testId, details.module );
+       }
 
-       /**
-        * Reorder and merge like edit sections.  Merge equalities.
-        * Any edit section can move as long as it doesn't cross an equality.
-        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
-        */
-       DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
-               var pointer, countDelete, countInsert, textInsert, textDelete,
-                       commonlength, changes, diffPointer, position;
-               diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
-               pointer = 0;
-               countDelete = 0;
-               countInsert = 0;
-               textDelete = "";
-               textInsert = "";
-               commonlength;
-               while ( pointer < diffs.length ) {
-                       switch ( diffs[ pointer ][ 0 ] ) {
-                       case DIFF_INSERT:
-                               countInsert++;
-                               textInsert += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_DELETE:
-                               countDelete++;
-                               textDelete += diffs[ pointer ][ 1 ];
-                               pointer++;
-                               break;
-                       case DIFF_EQUAL:
-                               // Upon reaching an equality, check for prior redundancies.
-                               if ( countDelete + countInsert > 1 ) {
-                                       if ( countDelete !== 0 && countInsert !== 0 ) {
-                                               // Factor out any common prefixes.
-                                               commonlength = this.diffCommonPrefix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       if ( ( pointer - countDelete - countInsert ) > 0 &&
-                                                                       diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
-                                                                       DIFF_EQUAL ) {
-                                                               diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
-                                                                       textInsert.substring( 0, commonlength );
-                                                       } else {
-                                                               diffs.splice( 0, 0, [ DIFF_EQUAL,
-                                                                       textInsert.substring( 0, commonlength )
-                                                               ] );
-                                                               pointer++;
-                                                       }
-                                                       textInsert = textInsert.substring( commonlength );
-                                                       textDelete = textDelete.substring( commonlength );
-                                               }
-                                               // Factor out any common suffixies.
-                                               commonlength = this.diffCommonSuffix( textInsert, textDelete );
-                                               if ( commonlength !== 0 ) {
-                                                       diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
-                                                                       commonlength ) + diffs[ pointer ][ 1 ];
-                                                       textInsert = textInsert.substring( 0, textInsert.length -
-                                                               commonlength );
-                                                       textDelete = textDelete.substring( 0, textDelete.length -
-                                                               commonlength );
-                                               }
-                                       }
-                                       // Delete the offending records and add the merged ones.
-                                       if ( countDelete === 0 ) {
-                                               diffs.splice( pointer - countInsert,
-                                                       countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
-                                       } else if ( countInsert === 0 ) {
-                                               diffs.splice( pointer - countDelete,
-                                                       countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
-                                       } else {
-                                               diffs.splice(
-                                                       pointer - countDelete - countInsert,
-                                                       countDelete + countInsert,
-                                                       [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
-                                               );
-                                       }
-                                       pointer = pointer - countDelete - countInsert +
-                                               ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
-                               } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
+       running = id( "qunit-testresult" );
+       if ( running ) {
+               bad = QUnit.config.reorder && defined.sessionStorage &&
+                       +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+
+               running.innerHTML = ( bad ?
+                       "Rerunning previously failed test: <br />" :
+                       "Running: <br />" ) +
+                       getNameHtml( details.name, details.module );
+       }
 
-                                       // Merge this equality with the previous one.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
-                                       diffs.splice( pointer, 1 );
-                               } else {
-                                       pointer++;
-                               }
-                               countInsert = 0;
-                               countDelete = 0;
-                               textDelete = "";
-                               textInsert = "";
-                               break;
-                       }
-               }
-               if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
-                       diffs.pop(); // Remove the dummy entry at the end.
-               }
+} );
 
-               // Second pass: look for single edits surrounded on both sides by equalities
-               // which can be shifted sideways to eliminate an equality.
-               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
-               changes = false;
-               pointer = 1;
+function stripHtml( string ) {
 
-               // Intentionally ignore the first and last element (don't need checking).
-               while ( pointer < diffs.length - 1 ) {
-                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
-                                       diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
+       // Strip tags, html entity and whitespaces
+       return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
+}
 
-                               diffPointer = diffs[ pointer ][ 1 ];
-                               position = diffPointer.substring(
-                                       diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
-                               );
+QUnit.log( function( details ) {
+       var assertList, assertLi,
+               message, expected, actual, diff,
+               showDiff = false,
+               testItem = id( "qunit-test-output-" + details.testId );
 
-                               // This is a single edit surrounded by equalities.
-                               if ( position === diffs[ pointer - 1 ][ 1 ] ) {
+       if ( !testItem ) {
+               return;
+       }
 
-                                       // Shift the edit over the previous equality.
-                                       diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
-                                               diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
-                                                       diffs[ pointer - 1 ][ 1 ].length );
-                                       diffs[ pointer + 1 ][ 1 ] =
-                                               diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer - 1, 1 );
-                                       changes = true;
-                               } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
-                                               diffs[ pointer + 1 ][ 1 ] ) {
+       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+       message = "<span class='test-message'>" + message + "</span>";
+       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
 
-                                       // Shift the edit over the next equality.
-                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
-                                       diffs[ pointer ][ 1 ] =
-                                               diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
-                                               diffs[ pointer + 1 ][ 1 ];
-                                       diffs.splice( pointer + 1, 1 );
-                                       changes = true;
-                               }
-                       }
-                       pointer++;
-               }
-               // If shifts were made, the diff needs reordering and another shift sweep.
-               if ( changes ) {
-                       this.diffCleanupMerge( diffs );
+       // The pushFailure doesn't provide details.expected
+       // when it calls, it's implicit to also not show expected and diff stuff
+       // Also, we need to check details.expected existence, as it can exist and be undefined
+       if ( !details.result && hasOwn.call( details, "expected" ) ) {
+               if ( details.negative ) {
+                       expected = "NOT " + QUnit.dump.parse( details.expected );
+               } else {
+                       expected = QUnit.dump.parse( details.expected );
                }
-       };
 
-       return function( o, n ) {
-               var diff, output, text;
-               diff = new DiffMatchPatch();
-               output = diff.DiffMain( o, n );
-               diff.diffCleanupEfficiency( output );
-               text = diff.diffPrettyHtml( output );
+               actual = QUnit.dump.parse( details.actual );
+               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
+                       escapeText( expected ) +
+                       "</pre></td></tr>";
 
-               return text;
-       };
-}() );
+               if ( actual !== expected ) {
 
-// Get a reference to the global object, like window in browsers
-}( (function() {
-       return this;
-})() ));
+                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
+                               escapeText( actual ) + "</pre></td></tr>";
 
-(function() {
+                       // Don't show diff if actual or expected are booleans
+                       if ( !( /^(true|false)$/.test( actual ) ) &&
+                                       !( /^(true|false)$/.test( expected ) ) ) {
+                               diff = QUnit.diff( expected, actual );
+                               showDiff = stripHtml( diff ).length !==
+                                       stripHtml( expected ).length +
+                                       stripHtml( actual ).length;
+                       }
 
-// Don't load the HTML Reporter on non-Browser environments
-if ( typeof window === "undefined" || !window.document ) {
-       return;
-}
+                       // Don't show diff if expected and actual are totally different
+                       if ( showDiff ) {
+                               message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
+                                       diff + "</pre></td></tr>";
+                       }
+               } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
+                               expected.indexOf( "[object Object]" ) !== -1 ) {
+                       message += "<tr class='test-message'><th>Message: </th><td>" +
+                               "Diff suppressed as the depth of object is more than current max depth (" +
+                               QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
+                               " run with a higher max depth or <a href='" +
+                               escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
+                               "Rerun</a> without max depth.</p></td></tr>";
+               } else {
+                       message += "<tr class='test-message'><th>Message: </th><td>" +
+                               "Diff suppressed as the expected and actual results have an equivalent" +
+                               " serialization</td></tr>";
+               }
 
-// Deprecated QUnit.init - Ref #530
-// Re-initialize the configuration options
-QUnit.init = function() {
-       var tests, banner, result, qunit,
-               config = QUnit.config;
+               if ( details.source ) {
+                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
+                               escapeText( details.source ) + "</pre></td></tr>";
+               }
 
-       config.stats = { all: 0, bad: 0 };
-       config.moduleStats = { all: 0, bad: 0 };
-       config.started = 0;
-       config.updateRate = 1000;
-       config.blocking = false;
-       config.autostart = true;
-       config.autorun = false;
-       config.filter = "";
-       config.queue = [];
+               message += "</table>";
 
-       // Return on non-browser environments
-       // This is necessary to not break on node tests
-       if ( typeof window === "undefined" ) {
-               return;
+       // This occurs when pushFailure is set and we have an extracted stack trace
+       } else if ( !details.result && details.source ) {
+               message += "<table>" +
+                       "<tr class='test-source'><th>Source: </th><td><pre>" +
+                       escapeText( details.source ) + "</pre></td></tr>" +
+                       "</table>";
        }
 
-       qunit = id( "qunit" );
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
 
-       tests = id( "qunit-tests" );
-       banner = id( "qunit-banner" );
-       result = id( "qunit-testresult" );
+       assertLi = document.createElement( "li" );
+       assertLi.className = details.result ? "pass" : "fail";
+       assertLi.innerHTML = message;
+       assertList.appendChild( assertLi );
+} );
 
-       if ( tests ) {
-               tests.innerHTML = "";
-       }
+QUnit.testDone( function( details ) {
+       var testTitle, time, testItem, assertList,
+               good, bad, testCounts, skipped, sourceName,
+               tests = id( "qunit-tests" );
 
-       if ( banner ) {
-               banner.className = "";
+       if ( !tests ) {
+               return;
        }
 
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
+       testItem = id( "qunit-test-output-" + details.testId );
 
-       if ( tests ) {
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
+       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+       good = details.passed;
+       bad = details.failed;
+
+       // Store result when possible
+       if ( config.reorder && defined.sessionStorage ) {
+               if ( bad ) {
+                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+               } else {
+                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+               }
        }
-};
 
-var config = QUnit.config,
-       collapseNext = false,
-       hasOwn = Object.prototype.hasOwnProperty,
-       defined = {
-               document: window.document !== undefined,
-               sessionStorage: (function() {
-                       var x = "qunit-test-string";
-                       try {
-                               sessionStorage.setItem( x, x );
-                               sessionStorage.removeItem( x );
-                               return true;
-                       } catch ( e ) {
-                               return false;
-                       }
-               }())
-       },
-       modulesList = [];
+       if ( bad === 0 ) {
 
-/**
-* Escape text for attribute or text content.
-*/
-function escapeText( s ) {
-       if ( !s ) {
-               return "";
+               // Collapse the passing tests
+               addClass( assertList, "qunit-collapsed" );
+       } else if ( bad && config.collapse && !collapseNext ) {
+
+               // Skip collapsing the first failing test
+               collapseNext = true;
+       } else {
+
+               // Collapse remaining tests
+               addClass( assertList, "qunit-collapsed" );
        }
-       s = s + "";
 
-       // Both single quotes and double quotes (for attributes)
-       return s.replace( /['"<>&]/g, function( s ) {
-               switch ( s ) {
-               case "'":
-                       return "&#039;";
-               case "\"":
-                       return "&quot;";
-               case "<":
-                       return "&lt;";
-               case ">":
-                       return "&gt;";
-               case "&":
-                       return "&amp;";
-               }
-       });
-}
+       // The testItem.firstChild is the test name
+       testTitle = testItem.firstChild;
 
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
-       if ( elem.addEventListener ) {
+       testCounts = bad ?
+               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
+               "";
 
-               // Standards-based browsers
-               elem.addEventListener( type, fn, false );
-       } else if ( elem.attachEvent ) {
+       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
+               details.assertions.length + ")</b>";
+
+       if ( details.skipped ) {
+               testItem.className = "skipped";
+               skipped = document.createElement( "em" );
+               skipped.className = "qunit-skipped-label";
+               skipped.innerHTML = "skipped";
+               testItem.insertBefore( skipped, testTitle );
+       } else {
+               addEvent( testTitle, "click", function() {
+                       toggleClass( assertList, "qunit-collapsed" );
+               } );
 
-               // support: IE <9
-               elem.attachEvent( "on" + type, function() {
-                       var event = window.event;
-                       if ( !event.target ) {
-                               event.target = event.srcElement || document;
-                       }
+               testItem.className = bad ? "fail" : "pass";
 
-                       fn.call( elem, event );
-               });
+               time = document.createElement( "span" );
+               time.className = "runtime";
+               time.innerHTML = details.runtime + " ms";
+               testItem.insertBefore( time, assertList );
        }
-}
 
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
-       var i = elems.length;
-       while ( i-- ) {
-               addEvent( elems[ i ], type, fn );
+       // Show the source of the test when showing assertions
+       if ( details.source ) {
+               sourceName = document.createElement( "p" );
+               sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
+               addClass( sourceName, "qunit-source" );
+               if ( bad === 0 ) {
+                       addClass( sourceName, "qunit-collapsed" );
+               }
+               addEvent( testTitle, "click", function() {
+                       toggleClass( sourceName, "qunit-collapsed" );
+               } );
+               testItem.appendChild( sourceName );
        }
-}
+} );
 
-function hasClass( elem, name ) {
-       return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
-}
+// Avoid readyState issue with phantomjs
+// Ref: #818
+var notPhantom = ( function( p ) {
+       return !( p && p.version && p.version.major > 0 );
+} )( window.phantom );
 
-function addClass( elem, name ) {
-       if ( !hasClass( elem, name ) ) {
-               elem.className += ( elem.className ? " " : "" ) + name;
-       }
+if ( notPhantom && document.readyState === "complete" ) {
+       QUnit.load();
+} else {
+       addEvent( window, "load", QUnit.load );
 }
 
-function toggleClass( elem, name ) {
-       if ( hasClass( elem, name ) ) {
-               removeClass( elem, name );
-       } else {
-               addClass( elem, name );
+/*
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
+ *
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * https://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More Info:
+ *  https://code.google.com/p/google-diff-match-patch/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ */
+QUnit.diff = ( function() {
+       function DiffMatchPatch() {
        }
-}
 
-function removeClass( elem, name ) {
-       var set = " " + elem.className + " ";
-
-       // Class name may appear multiple times
-       while ( set.indexOf( " " + name + " " ) >= 0 ) {
-               set = set.replace( " " + name + " ", " " );
-       }
+       //  DIFF FUNCTIONS
 
-       // trim for prettiness
-       elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
-}
+       /**
+        * The data structure representing a diff is an array of tuples:
+        * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+        * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+        */
+       var DIFF_DELETE = -1,
+               DIFF_INSERT = 1,
+               DIFF_EQUAL = 0;
 
-function id( name ) {
-       return defined.document && document.getElementById && document.getElementById( name );
-}
+       /**
+        * Find the differences between two texts.  Simplifies the problem by stripping
+        * any common prefix or suffix off the texts before diffing.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+        *     then don't run a line-level diff first to identify the changed areas.
+        *     Defaults to true, which does a faster, slightly less optimal diff.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
+               var deadline, checklines, commonlength,
+                       commonprefix, commonsuffix, diffs;
 
-function getUrlConfigHtml() {
-       var i, j, val,
-               escaped, escapedTooltip,
-               selection = false,
-               len = config.urlConfig.length,
-               urlConfigHtml = "";
+               // The diff must be complete in up to 1 second.
+               deadline = ( new Date() ).getTime() + 1000;
 
-       for ( i = 0; i < len; i++ ) {
-               val = config.urlConfig[ i ];
-               if ( typeof val === "string" ) {
-                       val = {
-                               id: val,
-                               label: val
-                       };
+               // Check for null inputs.
+               if ( text1 === null || text2 === null ) {
+                       throw new Error( "Null input. (DiffMain)" );
                }
 
-               escaped = escapeText( val.id );
-               escapedTooltip = escapeText( val.tooltip );
-
-               if ( config[ val.id ] === undefined ) {
-                       config[ val.id ] = QUnit.urlParams[ val.id ];
+               // Check for equality (speedup).
+               if ( text1 === text2 ) {
+                       if ( text1 ) {
+                               return [
+                                       [ DIFF_EQUAL, text1 ]
+                               ];
+                       }
+                       return [];
                }
 
-               if ( !val.value || typeof val.value === "string" ) {
-                       urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' type='checkbox'" +
-                               ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
-                               ( config[ val.id ] ? " checked='checked'" : "" ) +
-                               " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label + "</label>";
-               } else {
-                       urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
-                               "' title='" + escapedTooltip + "'>" + val.label +
-                               ": </label><select id='qunit-urlconfig-" + escaped +
-                               "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
-
-                       if ( QUnit.is( "array", val.value ) ) {
-                               for ( j = 0; j < val.value.length; j++ ) {
-                                       escaped = escapeText( val.value[ j ] );
-                                       urlConfigHtml += "<option value='" + escaped + "'" +
-                                               ( config[ val.id ] === val.value[ j ] ?
-                                                       ( selection = true ) && " selected='selected'" : "" ) +
-                                               ">" + escaped + "</option>";
-                               }
-                       } else {
-                               for ( j in val.value ) {
-                                       if ( hasOwn.call( val.value, j ) ) {
-                                               urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
-                                                       ( config[ val.id ] === j ?
-                                                               ( selection = true ) && " selected='selected'" : "" ) +
-                                                       ">" + escapeText( val.value[ j ] ) + "</option>";
-                                       }
-                               }
-                       }
-                       if ( config[ val.id ] && !selection ) {
-                               escaped = escapeText( config[ val.id ] );
-                               urlConfigHtml += "<option value='" + escaped +
-                                       "' selected='selected' disabled='disabled'>" + escaped + "</option>";
-                       }
-                       urlConfigHtml += "</select>";
+               if ( typeof optChecklines === "undefined" ) {
+                       optChecklines = true;
                }
-       }
 
-       return urlConfigHtml;
-}
+               checklines = optChecklines;
 
-// Handle "click" events on toolbar checkboxes and "change" for select menus.
-// Updates the URL with the new state of `config.urlConfig` values.
-function toolbarChanged() {
-       var updatedUrl, value,
-               field = this,
-               params = {};
+               // Trim off common prefix (speedup).
+               commonlength = this.diffCommonPrefix( text1, text2 );
+               commonprefix = text1.substring( 0, commonlength );
+               text1 = text1.substring( commonlength );
+               text2 = text2.substring( commonlength );
 
-       // Detect if field is a select menu or a checkbox
-       if ( "selectedIndex" in field ) {
-               value = field.options[ field.selectedIndex ].value || undefined;
-       } else {
-               value = field.checked ? ( field.defaultValue || true ) : undefined;
-       }
+               // Trim off common suffix (speedup).
+               commonlength = this.diffCommonSuffix( text1, text2 );
+               commonsuffix = text1.substring( text1.length - commonlength );
+               text1 = text1.substring( 0, text1.length - commonlength );
+               text2 = text2.substring( 0, text2.length - commonlength );
 
-       params[ field.name ] = value;
-       updatedUrl = setUrl( params );
+               // Compute the diff on the middle block.
+               diffs = this.diffCompute( text1, text2, checklines, deadline );
 
-       if ( "hidepassed" === field.name && "replaceState" in window.history ) {
-               config[ field.name ] = value || false;
-               if ( value ) {
-                       addClass( id( "qunit-tests" ), "hidepass" );
-               } else {
-                       removeClass( id( "qunit-tests" ), "hidepass" );
+               // Restore the prefix and suffix.
+               if ( commonprefix ) {
+                       diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
                }
+               if ( commonsuffix ) {
+                       diffs.push( [ DIFF_EQUAL, commonsuffix ] );
+               }
+               this.diffCleanupMerge( diffs );
+               return diffs;
+       };
 
-               // It is not necessary to refresh the whole page
-               window.history.replaceState( null, "", updatedUrl );
-       } else {
-               window.location = updatedUrl;
-       }
-}
-
-function setUrl( params ) {
-       var key,
-               querystring = "?";
+       /**
+        * Reduce the number of edits by eliminating operationally trivial equalities.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
+               var changes, equalities, equalitiesLength, lastequality,
+                       pointer, preIns, preDel, postIns, postDel;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
 
-       params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
 
-       for ( key in params ) {
-               if ( hasOwn.call( params, key ) ) {
-                       if ( params[ key ] === undefined ) {
-                               continue;
-                       }
-                       querystring += encodeURIComponent( key );
-                       if ( params[ key ] !== true ) {
-                               querystring += "=" + encodeURIComponent( params[ key ] );
-                       }
-                       querystring += "&";
-               }
-       }
-       return location.protocol + "//" + location.host +
-               location.pathname + querystring.slice( 0, -1 );
-}
+               // Is there an insertion operation before the last equality.
+               preIns = false;
 
-function applyUrlParams() {
-       var selectedModule,
-               modulesList = id( "qunit-modulefilter" ),
-               filter = id( "qunit-filter-input" ).value;
+               // Is there a deletion operation before the last equality.
+               preDel = false;
 
-       selectedModule = modulesList ?
-               decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
-               undefined;
+               // Is there an insertion operation after the last equality.
+               postIns = false;
 
-       window.location = setUrl({
-               module: ( selectedModule === "" ) ? undefined : selectedModule,
-               filter: ( filter === "" ) ? undefined : filter,
+               // Is there a deletion operation after the last equality.
+               postDel = false;
+               while ( pointer < diffs.length ) {
 
-               // Remove testId filter
-               testId: undefined
-       });
-}
+                       // Equality found.
+                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
+                               if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
 
-function toolbarUrlConfigContainer() {
-       var urlConfigContainer = document.createElement( "span" );
+                                       // Candidate found.
+                                       equalities[ equalitiesLength++ ] = pointer;
+                                       preIns = postIns;
+                                       preDel = postDel;
+                                       lastequality = diffs[ pointer ][ 1 ];
+                               } else {
 
-       urlConfigContainer.innerHTML = getUrlConfigHtml();
-       addClass( urlConfigContainer, "qunit-url-config" );
+                                       // Not a candidate, and can never become one.
+                                       equalitiesLength = 0;
+                                       lastequality = null;
+                               }
+                               postIns = postDel = false;
 
-       // For oldIE support:
-       // * Add handlers to the individual elements instead of the container
-       // * Use "click" instead of "change" for checkboxes
-       addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
-       addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+                       // An insertion or deletion.
+                       } else {
 
-       return urlConfigContainer;
-}
+                               if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
+                                       postDel = true;
+                               } else {
+                                       postIns = true;
+                               }
 
-function toolbarLooseFilter() {
-       var filter = document.createElement( "form" ),
-               label = document.createElement( "label" ),
-               input = document.createElement( "input" ),
-               button = document.createElement( "button" );
+                               /*
+                                * Five types to be split:
+                                * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+                                * <ins>A</ins>X<ins>C</ins><del>D</del>
+                                * <ins>A</ins><del>B</del>X<ins>C</ins>
+                                * <ins>A</del>X<ins>C</ins><del>D</del>
+                                * <ins>A</ins><del>B</del>X<del>C</del>
+                                */
+                               if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
+                                               ( ( lastequality.length < 2 ) &&
+                                               ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
 
-       addClass( filter, "qunit-filter" );
+                                       // Duplicate record.
+                                       diffs.splice(
+                                               equalities[ equalitiesLength - 1 ],
+                                               0,
+                                               [ DIFF_DELETE, lastequality ]
+                                       );
 
-       label.innerHTML = "Filter: ";
+                                       // Change second copy to insert.
+                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+                                       equalitiesLength--; // Throw away the equality we just deleted;
+                                       lastequality = null;
+                                       if ( preIns && preDel ) {
 
-       input.type = "text";
-       input.value = config.filter || "";
-       input.name = "filter";
-       input.id = "qunit-filter-input";
+                                               // No changes made which could affect previous entry, keep going.
+                                               postIns = postDel = true;
+                                               equalitiesLength = 0;
+                                       } else {
+                                               equalitiesLength--; // Throw away the previous equality.
+                                               pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+                                               postIns = postDel = false;
+                                       }
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
 
-       button.innerHTML = "Go";
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
+               }
+       };
 
-       label.appendChild( input );
+       /**
+        * Convert a diff array into a pretty HTML report.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        * @param {integer} string to be beautified.
+        * @return {string} HTML representation.
+        */
+       DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
+               var op, data, x,
+                       html = [];
+               for ( x = 0; x < diffs.length; x++ ) {
+                       op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
+                       data = diffs[ x ][ 1 ]; // Text of change.
+                       switch ( op ) {
+                       case DIFF_INSERT:
+                               html[ x ] = "<ins>" + escapeText( data ) + "</ins>";
+                               break;
+                       case DIFF_DELETE:
+                               html[ x ] = "<del>" + escapeText( data ) + "</del>";
+                               break;
+                       case DIFF_EQUAL:
+                               html[ x ] = "<span>" + escapeText( data ) + "</span>";
+                               break;
+                       }
+               }
+               return html.join( "" );
+       };
 
-       filter.appendChild( label );
-       filter.appendChild( button );
-       addEvent( filter, "submit", function( ev ) {
-               applyUrlParams();
+       /**
+        * Determine the common prefix of two strings.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the start of each
+        *     string.
+        */
+       DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
+               var pointermid, pointermax, pointermin, pointerstart;
 
-               if ( ev && ev.preventDefault ) {
-                       ev.preventDefault();
+               // Quick check for common null cases.
+               if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
+                       return 0;
                }
 
-               return false;
-       });
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min( text1.length, text2.length );
+               pointermid = pointermax;
+               pointerstart = 0;
+               while ( pointermin < pointermid ) {
+                       if ( text1.substring( pointerstart, pointermid ) ===
+                                       text2.substring( pointerstart, pointermid ) ) {
+                               pointermin = pointermid;
+                               pointerstart = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+               }
+               return pointermid;
+       };
 
-       return filter;
-}
+       /**
+        * Determine the common suffix of two strings.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the end of each string.
+        */
+       DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
+               var pointermid, pointermax, pointermin, pointerend;
 
-function toolbarModuleFilterHtml() {
-       var i,
-               moduleFilterHtml = "";
+               // Quick check for common null cases.
+               if ( !text1 ||
+                               !text2 ||
+                               text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
+                       return 0;
+               }
 
-       if ( !modulesList.length ) {
-               return false;
-       }
+               // Binary search.
+               // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+               pointermin = 0;
+               pointermax = Math.min( text1.length, text2.length );
+               pointermid = pointermax;
+               pointerend = 0;
+               while ( pointermin < pointermid ) {
+                       if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
+                                       text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
+                               pointermin = pointermid;
+                               pointerend = pointermin;
+                       } else {
+                               pointermax = pointermid;
+                       }
+                       pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+               }
+               return pointermid;
+       };
 
-       modulesList.sort(function( a, b ) {
-               return a.localeCompare( b );
-       });
+       /**
+        * Find the differences between two texts.  Assumes that the texts do not
+        * have any common prefix or suffix.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {boolean} checklines Speedup flag.  If false, then don't run a
+        *     line-level diff first to identify the changed areas.
+        *     If true, then run a faster, slightly less optimal diff.
+        * @param {number} deadline Time when the diff should be complete by.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
+               var diffs, longtext, shorttext, i, hm,
+                       text1A, text2A, text1B, text2B,
+                       midCommon, diffsA, diffsB;
 
-       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
-               "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-               ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
-               ">< All Modules ></option>";
+               if ( !text1 ) {
 
-       for ( i = 0; i < modulesList.length; i++ ) {
-               moduleFilterHtml += "<option value='" +
-                       escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
-                       ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
-                       ">" + escapeText( modulesList[ i ] ) + "</option>";
-       }
-       moduleFilterHtml += "</select>";
+                       // Just add some text (speedup).
+                       return [
+                               [ DIFF_INSERT, text2 ]
+                       ];
+               }
 
-       return moduleFilterHtml;
-}
+               if ( !text2 ) {
 
-function toolbarModuleFilter() {
-       var toolbar = id( "qunit-testrunner-toolbar" ),
-               moduleFilter = document.createElement( "span" ),
-               moduleFilterHtml = toolbarModuleFilterHtml();
+                       // Just delete some text (speedup).
+                       return [
+                               [ DIFF_DELETE, text1 ]
+                       ];
+               }
+
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               i = longtext.indexOf( shorttext );
+               if ( i !== -1 ) {
+
+                       // Shorter text is inside the longer text (speedup).
+                       diffs = [
+                               [ DIFF_INSERT, longtext.substring( 0, i ) ],
+                               [ DIFF_EQUAL, shorttext ],
+                               [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
+                       ];
 
-       if ( !toolbar || !moduleFilterHtml ) {
-               return false;
-       }
+                       // Swap insertions for deletions if diff is reversed.
+                       if ( text1.length > text2.length ) {
+                               diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
+                       }
+                       return diffs;
+               }
 
-       moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
-       moduleFilter.innerHTML = moduleFilterHtml;
+               if ( shorttext.length === 1 ) {
 
-       addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+                       // Single character string.
+                       // After the previous speedup, the character can't be an equality.
+                       return [
+                               [ DIFF_DELETE, text1 ],
+                               [ DIFF_INSERT, text2 ]
+                       ];
+               }
 
-       toolbar.appendChild( moduleFilter );
-}
+               // Check to see if the problem can be split in two.
+               hm = this.diffHalfMatch( text1, text2 );
+               if ( hm ) {
 
-function appendToolbar() {
-       var toolbar = id( "qunit-testrunner-toolbar" );
+                       // A half-match was found, sort out the return data.
+                       text1A = hm[ 0 ];
+                       text1B = hm[ 1 ];
+                       text2A = hm[ 2 ];
+                       text2B = hm[ 3 ];
+                       midCommon = hm[ 4 ];
 
-       if ( toolbar ) {
-               toolbar.appendChild( toolbarUrlConfigContainer() );
-               toolbar.appendChild( toolbarLooseFilter() );
-       }
-}
+                       // Send both pairs off for separate processing.
+                       diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
+                       diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
 
-function appendHeader() {
-       var header = id( "qunit-header" );
+                       // Merge the results.
+                       return diffsA.concat( [
+                               [ DIFF_EQUAL, midCommon ]
+                       ], diffsB );
+               }
 
-       if ( header ) {
-               header.innerHTML = "<a href='" +
-                       escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
-                       "'>" + header.innerHTML + "</a> ";
-       }
-}
+               if ( checklines && text1.length > 100 && text2.length > 100 ) {
+                       return this.diffLineMode( text1, text2, deadline );
+               }
 
-function appendBanner() {
-       var banner = id( "qunit-banner" );
+               return this.diffBisect( text1, text2, deadline );
+       };
 
-       if ( banner ) {
-               banner.className = "";
-       }
-}
+       /**
+        * Do the two texts share a substring which is at least half the length of the
+        * longer text?
+        * This speedup can produce non-minimal diffs.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {Array.<string>} Five element Array, containing the prefix of
+        *     text1, the suffix of text1, the prefix of text2, the suffix of
+        *     text2 and the common middle.  Or null if there was no match.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
+               var longtext, shorttext, dmp,
+                       text1A, text2B, text2A, text1B, midCommon,
+                       hm1, hm2, hm;
 
-function appendTestResults() {
-       var tests = id( "qunit-tests" ),
-               result = id( "qunit-testresult" );
+               longtext = text1.length > text2.length ? text1 : text2;
+               shorttext = text1.length > text2.length ? text2 : text1;
+               if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
+                       return null; // Pointless.
+               }
+               dmp = this; // 'this' becomes 'window' in a closure.
 
-       if ( result ) {
-               result.parentNode.removeChild( result );
-       }
+               /**
+                * Does a substring of shorttext exist within longtext such that the substring
+                * is at least half the length of longtext?
+                * Closure, but does not reference any external variables.
+                * @param {string} longtext Longer string.
+                * @param {string} shorttext Shorter string.
+                * @param {number} i Start index of quarter length substring within longtext.
+                * @return {Array.<string>} Five element Array, containing the prefix of
+                *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
+                *     of shorttext and the common middle.  Or null if there was no match.
+                * @private
+                */
+               function diffHalfMatchI( longtext, shorttext, i ) {
+                       var seed, j, bestCommon, prefixLength, suffixLength,
+                               bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
 
-       if ( tests ) {
-               tests.innerHTML = "";
-               result = document.createElement( "p" );
-               result.id = "qunit-testresult";
-               result.className = "result";
-               tests.parentNode.insertBefore( result, tests );
-               result.innerHTML = "Running...<br />&#160;";
-       }
-}
+                       // Start with a 1/4 length substring at position i as a seed.
+                       seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
+                       j = -1;
+                       bestCommon = "";
+                       while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
+                               prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
+                                       shorttext.substring( j ) );
+                               suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
+                                       shorttext.substring( 0, j ) );
+                               if ( bestCommon.length < suffixLength + prefixLength ) {
+                                       bestCommon = shorttext.substring( j - suffixLength, j ) +
+                                               shorttext.substring( j, j + prefixLength );
+                                       bestLongtextA = longtext.substring( 0, i - suffixLength );
+                                       bestLongtextB = longtext.substring( i + prefixLength );
+                                       bestShorttextA = shorttext.substring( 0, j - suffixLength );
+                                       bestShorttextB = shorttext.substring( j + prefixLength );
+                               }
+                       }
+                       if ( bestCommon.length * 2 >= longtext.length ) {
+                               return [ bestLongtextA, bestLongtextB,
+                                       bestShorttextA, bestShorttextB, bestCommon
+                               ];
+                       } else {
+                               return null;
+                       }
+               }
 
-function storeFixture() {
-       var fixture = id( "qunit-fixture" );
-       if ( fixture ) {
-               config.fixture = fixture.innerHTML;
-       }
-}
+               // First check if the second quarter is the seed for a half-match.
+               hm1 = diffHalfMatchI( longtext, shorttext,
+                       Math.ceil( longtext.length / 4 ) );
 
-function appendFilteredTest() {
-       var testId = QUnit.config.testId;
-       if ( !testId || testId.length <= 0 ) {
-               return "";
-       }
-       return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
-               escapeText( testId.join(", ") ) +
-               " <a id='qunit-clearFilter' href='" +
-               escapeText( setUrl( { filter: undefined, module: undefined, testId: undefined } ) ) +
-               "'>" + "Run all tests" + "</a></div>";
-}
+               // Check again based on the third quarter.
+               hm2 = diffHalfMatchI( longtext, shorttext,
+                       Math.ceil( longtext.length / 2 ) );
+               if ( !hm1 && !hm2 ) {
+                       return null;
+               } else if ( !hm2 ) {
+                       hm = hm1;
+               } else if ( !hm1 ) {
+                       hm = hm2;
+               } else {
 
-function appendUserAgent() {
-       var userAgent = id( "qunit-userAgent" );
+                       // Both matched.  Select the longest.
+                       hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
+               }
 
-       if ( userAgent ) {
-               userAgent.innerHTML = "";
-               userAgent.appendChild(
-                       document.createTextNode(
-                               "QUnit " + QUnit.version + "; " + navigator.userAgent
-                       )
-               );
-       }
-}
+               // A half-match was found, sort out the return data.
+               text1A, text1B, text2A, text2B;
+               if ( text1.length > text2.length ) {
+                       text1A = hm[ 0 ];
+                       text1B = hm[ 1 ];
+                       text2A = hm[ 2 ];
+                       text2B = hm[ 3 ];
+               } else {
+                       text2A = hm[ 0 ];
+                       text2B = hm[ 1 ];
+                       text1A = hm[ 2 ];
+                       text1B = hm[ 3 ];
+               }
+               midCommon = hm[ 4 ];
+               return [ text1A, text1B, text2A, text2B, midCommon ];
+       };
 
-function appendTestsList( modules ) {
-       var i, l, x, z, test, moduleObj;
+       /**
+        * Do a quick line-level diff on both strings, then rediff the parts for
+        * greater accuracy.
+        * This speedup can produce non-minimal diffs.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} deadline Time when the diff should be complete by.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
+               var a, diffs, linearray, pointer, countInsert,
+                       countDelete, textInsert, textDelete, j;
 
-       for ( i = 0, l = modules.length; i < l; i++ ) {
-               moduleObj = modules[ i ];
+               // Scan the text on a line-by-line basis first.
+               a = this.diffLinesToChars( text1, text2 );
+               text1 = a.chars1;
+               text2 = a.chars2;
+               linearray = a.lineArray;
 
-               if ( moduleObj.name ) {
-                       modulesList.push( moduleObj.name );
-               }
+               diffs = this.DiffMain( text1, text2, false, deadline );
 
-               for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
-                       test = moduleObj.tests[ x ];
+               // Convert the diff back to original text.
+               this.diffCharsToLines( diffs, linearray );
 
-                       appendTest( test.name, test.testId, moduleObj.name );
-               }
-       }
-}
+               // Eliminate freak matches (e.g. blank lines)
+               this.diffCleanupSemantic( diffs );
 
-function appendTest( name, testId, moduleName ) {
-       var title, rerunTrigger, testBlock, assertList,
-               tests = id( "qunit-tests" );
+               // Rediff any replacement blocks, this time character-by-character.
+               // Add a dummy entry at the end.
+               diffs.push( [ DIFF_EQUAL, "" ] );
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+               while ( pointer < diffs.length ) {
+                       switch ( diffs[ pointer ][ 0 ] ) {
+                       case DIFF_INSERT:
+                               countInsert++;
+                               textInsert += diffs[ pointer ][ 1 ];
+                               break;
+                       case DIFF_DELETE:
+                               countDelete++;
+                               textDelete += diffs[ pointer ][ 1 ];
+                               break;
+                       case DIFF_EQUAL:
+
+                               // Upon reaching an equality, check for prior redundancies.
+                               if ( countDelete >= 1 && countInsert >= 1 ) {
 
-       if ( !tests ) {
-               return;
-       }
+                                       // Delete the offending records and add the merged ones.
+                                       diffs.splice( pointer - countDelete - countInsert,
+                                               countDelete + countInsert );
+                                       pointer = pointer - countDelete - countInsert;
+                                       a = this.DiffMain( textDelete, textInsert, false, deadline );
+                                       for ( j = a.length - 1; j >= 0; j-- ) {
+                                               diffs.splice( pointer, 0, a[ j ] );
+                                       }
+                                       pointer = pointer + a.length;
+                               }
+                               countInsert = 0;
+                               countDelete = 0;
+                               textDelete = "";
+                               textInsert = "";
+                               break;
+                       }
+                       pointer++;
+               }
+               diffs.pop(); // Remove the dummy entry at the end.
 
-       title = document.createElement( "strong" );
-       title.innerHTML = getNameHtml( name, moduleName );
+               return diffs;
+       };
 
-       rerunTrigger = document.createElement( "a" );
-       rerunTrigger.innerHTML = "Rerun";
-       rerunTrigger.href = setUrl({ testId: testId });
+       /**
+        * Find the 'middle snake' of a diff, split the problem in two
+        * and return the recursively constructed diff.
+        * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} deadline Time at which to bail if not yet complete.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
+               var text1Length, text2Length, maxD, vOffset, vLength,
+                       v1, v2, x, delta, front, k1start, k1end, k2start,
+                       k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
 
-       testBlock = document.createElement( "li" );
-       testBlock.appendChild( title );
-       testBlock.appendChild( rerunTrigger );
-       testBlock.id = "qunit-test-output-" + testId;
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+               maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
+               vOffset = maxD;
+               vLength = 2 * maxD;
+               v1 = new Array( vLength );
+               v2 = new Array( vLength );
 
-       assertList = document.createElement( "ol" );
-       assertList.className = "qunit-assert-list";
+               // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+               // integers and undefined.
+               for ( x = 0; x < vLength; x++ ) {
+                       v1[ x ] = -1;
+                       v2[ x ] = -1;
+               }
+               v1[ vOffset + 1 ] = 0;
+               v2[ vOffset + 1 ] = 0;
+               delta = text1Length - text2Length;
 
-       testBlock.appendChild( assertList );
+               // If the total number of characters is odd, then the front path will collide
+               // with the reverse path.
+               front = ( delta % 2 !== 0 );
 
-       tests.appendChild( testBlock );
-}
+               // Offsets for start and end of k loop.
+               // Prevents mapping of space beyond the grid.
+               k1start = 0;
+               k1end = 0;
+               k2start = 0;
+               k2end = 0;
+               for ( d = 0; d < maxD; d++ ) {
 
-// HTML Reporter initialization and load
-QUnit.begin(function( details ) {
-       var qunit = id( "qunit" );
+                       // Bail out if deadline is reached.
+                       if ( ( new Date() ).getTime() > deadline ) {
+                               break;
+                       }
 
-       // Fixture is the only one necessary to run without the #qunit element
-       storeFixture();
+                       // Walk the front path one step.
+                       for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
+                               k1Offset = vOffset + k1;
+                               if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
+                                       x1 = v1[ k1Offset + 1 ];
+                               } else {
+                                       x1 = v1[ k1Offset - 1 ] + 1;
+                               }
+                               y1 = x1 - k1;
+                               while ( x1 < text1Length && y1 < text2Length &&
+                                       text1.charAt( x1 ) === text2.charAt( y1 ) ) {
+                                       x1++;
+                                       y1++;
+                               }
+                               v1[ k1Offset ] = x1;
+                               if ( x1 > text1Length ) {
 
-       if ( qunit ) {
-               qunit.innerHTML =
-                       "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-                       "<h2 id='qunit-banner'></h2>" +
-                       "<div id='qunit-testrunner-toolbar'></div>" +
-                       appendFilteredTest() +
-                       "<h2 id='qunit-userAgent'></h2>" +
-                       "<ol id='qunit-tests'></ol>";
-       }
+                                       // Ran off the right of the graph.
+                                       k1end += 2;
+                               } else if ( y1 > text2Length ) {
 
-       appendHeader();
-       appendBanner();
-       appendTestResults();
-       appendUserAgent();
-       appendToolbar();
-       appendTestsList( details.modules );
-       toolbarModuleFilter();
+                                       // Ran off the bottom of the graph.
+                                       k1start += 2;
+                               } else if ( front ) {
+                                       k2Offset = vOffset + delta - k1;
+                                       if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
 
-       if ( qunit && config.hidepassed ) {
-               addClass( qunit.lastChild, "hidepass" );
-       }
-});
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - v2[ k2Offset ];
+                                               if ( x1 >= x2 ) {
 
-QUnit.done(function( details ) {
-       var i, key,
-               banner = id( "qunit-banner" ),
-               tests = id( "qunit-tests" ),
-               html = [
-                       "Tests completed in ",
-                       details.runtime,
-                       " milliseconds.<br />",
-                       "<span class='passed'>",
-                       details.passed,
-                       "</span> assertions of <span class='total'>",
-                       details.total,
-                       "</span> passed, <span class='failed'>",
-                       details.failed,
-                       "</span> failed."
-               ].join( "" );
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
+                                               }
+                                       }
+                               }
+                       }
 
-       if ( banner ) {
-               banner.className = details.failed ? "qunit-fail" : "qunit-pass";
-       }
+                       // Walk the reverse path one step.
+                       for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
+                               k2Offset = vOffset + k2;
+                               if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
+                                       x2 = v2[ k2Offset + 1 ];
+                               } else {
+                                       x2 = v2[ k2Offset - 1 ] + 1;
+                               }
+                               y2 = x2 - k2;
+                               while ( x2 < text1Length && y2 < text2Length &&
+                                       text1.charAt( text1Length - x2 - 1 ) ===
+                                       text2.charAt( text2Length - y2 - 1 ) ) {
+                                       x2++;
+                                       y2++;
+                               }
+                               v2[ k2Offset ] = x2;
+                               if ( x2 > text1Length ) {
 
-       if ( tests ) {
-               id( "qunit-testresult" ).innerHTML = html;
-       }
+                                       // Ran off the left of the graph.
+                                       k2end += 2;
+                               } else if ( y2 > text2Length ) {
 
-       if ( config.altertitle && defined.document && document.title ) {
+                                       // Ran off the top of the graph.
+                                       k2start += 2;
+                               } else if ( !front ) {
+                                       k1Offset = vOffset + delta - k2;
+                                       if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
+                                               x1 = v1[ k1Offset ];
+                                               y1 = vOffset + x1 - k1Offset;
 
-               // show âœ– for good, âœ” for bad suite result in title
-               // use escape sequences in case file gets loaded with non-utf-8-charset
-               document.title = [
-                       ( details.failed ? "\u2716" : "\u2714" ),
-                       document.title.replace( /^[\u2714\u2716] /i, "" )
-               ].join( " " );
-       }
+                                               // Mirror x2 onto top-left coordinate system.
+                                               x2 = text1Length - x2;
+                                               if ( x1 >= x2 ) {
 
-       // clear own sessionStorage items if all tests passed
-       if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
-               for ( i = 0; i < sessionStorage.length; i++ ) {
-                       key = sessionStorage.key( i++ );
-                       if ( key.indexOf( "qunit-test-" ) === 0 ) {
-                               sessionStorage.removeItem( key );
+                                                       // Overlap detected.
+                                                       return this.diffBisectSplit( text1, text2, x1, y1, deadline );
+                                               }
+                                       }
+                               }
                        }
                }
-       }
-
-       // scroll back to top to show results
-       if ( config.scrolltop && window.scrollTo ) {
-               window.scrollTo( 0, 0 );
-       }
-});
 
-function getNameHtml( name, module ) {
-       var nameHtml = "";
+               // Diff took too long and hit the deadline or
+               // number of diffs equals number of characters, no commonality at all.
+               return [
+                       [ DIFF_DELETE, text1 ],
+                       [ DIFF_INSERT, text2 ]
+               ];
+       };
 
-       if ( module ) {
-               nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
-       }
+       /**
+        * Given the location of the 'middle snake', split the diff in two parts
+        * and recurse.
+        * @param {string} text1 Old string to be diffed.
+        * @param {string} text2 New string to be diffed.
+        * @param {number} x Index of split point in text1.
+        * @param {number} y Index of split point in text2.
+        * @param {number} deadline Time at which to bail if not yet complete.
+        * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
+               var text1a, text1b, text2a, text2b, diffs, diffsb;
+               text1a = text1.substring( 0, x );
+               text2a = text2.substring( 0, y );
+               text1b = text1.substring( x );
+               text2b = text2.substring( y );
 
-       nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
+               // Compute both diffs serially.
+               diffs = this.DiffMain( text1a, text2a, false, deadline );
+               diffsb = this.DiffMain( text1b, text2b, false, deadline );
 
-       return nameHtml;
-}
+               return diffs.concat( diffsb );
+       };
 
-QUnit.testStart(function( details ) {
-       var running, testBlock, bad;
+       /**
+        * Reduce the number of edits by eliminating semantically trivial equalities.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
+               var changes, equalities, equalitiesLength, lastequality,
+                       pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
+                       lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+               changes = false;
+               equalities = []; // Stack of indices where equalities are found.
+               equalitiesLength = 0; // Keeping our own length var is faster in JS.
+               /** @type {?string} */
+               lastequality = null;
 
-       testBlock = id( "qunit-test-output-" + details.testId );
-       if ( testBlock ) {
-               testBlock.className = "running";
-       } else {
+               // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+               pointer = 0; // Index of current position.
 
-               // Report later registered tests
-               appendTest( details.name, details.testId, details.module );
-       }
+               // Number of characters that changed prior to the equality.
+               lengthInsertions1 = 0;
+               lengthDeletions1 = 0;
 
-       running = id( "qunit-testresult" );
-       if ( running ) {
-               bad = QUnit.config.reorder && defined.sessionStorage &&
-                       +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+               // Number of characters that changed after the equality.
+               lengthInsertions2 = 0;
+               lengthDeletions2 = 0;
+               while ( pointer < diffs.length ) {
+                       if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
+                               equalities[ equalitiesLength++ ] = pointer;
+                               lengthInsertions1 = lengthInsertions2;
+                               lengthDeletions1 = lengthDeletions2;
+                               lengthInsertions2 = 0;
+                               lengthDeletions2 = 0;
+                               lastequality = diffs[ pointer ][ 1 ];
+                       } else { // An insertion or deletion.
+                               if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
+                                       lengthInsertions2 += diffs[ pointer ][ 1 ].length;
+                               } else {
+                                       lengthDeletions2 += diffs[ pointer ][ 1 ].length;
+                               }
 
-               running.innerHTML = ( bad ?
-                       "Rerunning previously failed test: <br />" :
-                       "Running: <br />" ) +
-                       getNameHtml( details.name, details.module );
-       }
+                               // Eliminate an equality that is smaller or equal to the edits on both
+                               // sides of it.
+                               if ( lastequality && ( lastequality.length <=
+                                               Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
+                                               ( lastequality.length <= Math.max( lengthInsertions2,
+                                                       lengthDeletions2 ) ) ) {
 
-});
+                                       // Duplicate record.
+                                       diffs.splice(
+                                               equalities[ equalitiesLength - 1 ],
+                                               0,
+                                               [ DIFF_DELETE, lastequality ]
+                                       );
 
-function stripHtml( string ) {
-       // strip tags, html entity and whitespaces
-       return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
-}
+                                       // Change second copy to insert.
+                                       diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
 
-QUnit.log(function( details ) {
-       var assertList, assertLi,
-               message, expected, actual, diff,
-               showDiff = false,
-               testItem = id( "qunit-test-output-" + details.testId );
+                                       // Throw away the equality we just deleted.
+                                       equalitiesLength--;
 
-       if ( !testItem ) {
-               return;
-       }
+                                       // Throw away the previous equality (it needs to be reevaluated).
+                                       equalitiesLength--;
+                                       pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
 
-       message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
-       message = "<span class='test-message'>" + message + "</span>";
-       message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+                                       // Reset the counters.
+                                       lengthInsertions1 = 0;
+                                       lengthDeletions1 = 0;
+                                       lengthInsertions2 = 0;
+                                       lengthDeletions2 = 0;
+                                       lastequality = null;
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
+               }
 
-       // pushFailure doesn't provide details.expected
-       // when it calls, it's implicit to also not show expected and diff stuff
-       // Also, we need to check details.expected existence, as it can exist and be undefined
-       if ( !details.result && hasOwn.call( details, "expected" ) ) {
-               if ( details.negative ) {
-                       expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) );
-               } else {
-                       expected = escapeText( QUnit.dump.parse( details.expected ) );
+               // Normalize the diff.
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
                }
 
-               actual = escapeText( QUnit.dump.parse( details.actual ) );
-               message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
-                       expected +
-                       "</pre></td></tr>";
+               // Find any overlaps between deletions and insertions.
+               // e.g: <del>abcxxx</del><ins>xxxdef</ins>
+               //   -> <del>abc</del>xxx<ins>def</ins>
+               // e.g: <del>xxxabc</del><ins>defxxx</ins>
+               //   -> <ins>def</ins>xxx<del>abc</del>
+               // Only extract an overlap if it is as big as the edit ahead or behind it.
+               pointer = 1;
+               while ( pointer < diffs.length ) {
+                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
+                                       diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
+                               deletion = diffs[ pointer - 1 ][ 1 ];
+                               insertion = diffs[ pointer ][ 1 ];
+                               overlapLength1 = this.diffCommonOverlap( deletion, insertion );
+                               overlapLength2 = this.diffCommonOverlap( insertion, deletion );
+                               if ( overlapLength1 >= overlapLength2 ) {
+                                       if ( overlapLength1 >= deletion.length / 2 ||
+                                                       overlapLength1 >= insertion.length / 2 ) {
 
-               if ( actual !== expected ) {
+                                               // Overlap found.  Insert an equality and trim the surrounding edits.
+                                               diffs.splice(
+                                                       pointer,
+                                                       0,
+                                                       [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
+                                               );
+                                               diffs[ pointer - 1 ][ 1 ] =
+                                                       deletion.substring( 0, deletion.length - overlapLength1 );
+                                               diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
+                                               pointer++;
+                                       }
+                               } else {
+                                       if ( overlapLength2 >= deletion.length / 2 ||
+                                                       overlapLength2 >= insertion.length / 2 ) {
 
-                       message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
-                               actual + "</pre></td></tr>";
+                                               // Reverse overlap found.
+                                               // Insert an equality and swap and trim the surrounding edits.
+                                               diffs.splice(
+                                                       pointer,
+                                                       0,
+                                                       [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
+                                               );
 
-                       // Don't show diff if actual or expected are booleans
-                       if ( !( /^(true|false)$/.test( actual ) ) &&
-                                       !( /^(true|false)$/.test( expected ) ) ) {
-                               diff = QUnit.diff( expected, actual );
-                               showDiff = stripHtml( diff ).length !==
-                                       stripHtml( expected ).length +
-                                       stripHtml( actual ).length;
+                                               diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
+                                               diffs[ pointer - 1 ][ 1 ] =
+                                                       insertion.substring( 0, insertion.length - overlapLength2 );
+                                               diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
+                                               diffs[ pointer + 1 ][ 1 ] =
+                                                       deletion.substring( overlapLength2 );
+                                               pointer++;
+                                       }
+                               }
+                               pointer++;
                        }
+                       pointer++;
+               }
+       };
 
-                       // Don't show diff if expected and actual are totally different
-                       if ( showDiff ) {
-                               message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
-                                       diff + "</pre></td></tr>";
-                       }
-               } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
-                               expected.indexOf( "[object Object]" ) !== -1 ) {
-                       message += "<tr class='test-message'><th>Message: </th><td>" +
-                               "Diff suppressed as the depth of object is more than current max depth (" +
-                               QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
-                               " run with a higher max depth or <a href='" +
-                               escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
-                               "Rerun</a> without max depth.</p></td></tr>";
+       /**
+        * Determine if the suffix of one string is the prefix of another.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {number} The number of characters common to the end of the first
+        *     string and the start of the second string.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
+               var text1Length, text2Length, textLength,
+                       best, length, pattern, found;
+
+               // Cache the text lengths to prevent multiple calls.
+               text1Length = text1.length;
+               text2Length = text2.length;
+
+               // Eliminate the null case.
+               if ( text1Length === 0 || text2Length === 0 ) {
+                       return 0;
                }
 
-               if ( details.source ) {
-                       message += "<tr class='test-source'><th>Source: </th><td><pre>" +
-                               escapeText( details.source ) + "</pre></td></tr>";
+               // Truncate the longer string.
+               if ( text1Length > text2Length ) {
+                       text1 = text1.substring( text1Length - text2Length );
+               } else if ( text1Length < text2Length ) {
+                       text2 = text2.substring( 0, text1Length );
                }
+               textLength = Math.min( text1Length, text2Length );
 
-               message += "</table>";
+               // Quick check for the worst case.
+               if ( text1 === text2 ) {
+                       return textLength;
+               }
 
-       // this occurs when pushFailure is set and we have an extracted stack trace
-       } else if ( !details.result && details.source ) {
-               message += "<table>" +
-                       "<tr class='test-source'><th>Source: </th><td><pre>" +
-                       escapeText( details.source ) + "</pre></td></tr>" +
-                       "</table>";
-       }
+               // Start by looking for a single character match
+               // and increase length until no match is found.
+               // Performance analysis: https://neil.fraser.name/news/2010/11/04/
+               best = 0;
+               length = 1;
+               while ( true ) {
+                       pattern = text1.substring( textLength - length );
+                       found = text2.indexOf( pattern );
+                       if ( found === -1 ) {
+                               return best;
+                       }
+                       length += found;
+                       if ( found === 0 || text1.substring( textLength - length ) ===
+                                       text2.substring( 0, length ) ) {
+                               best = length;
+                               length++;
+                       }
+               }
+       };
 
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+       /**
+        * Split two texts into an array of strings.  Reduce the texts to a string of
+        * hashes where each Unicode character represents one line.
+        * @param {string} text1 First string.
+        * @param {string} text2 Second string.
+        * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
+        *     An object containing the encoded text1, the encoded text2 and
+        *     the array of unique strings.
+        *     The zeroth element of the array of unique strings is intentionally blank.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
+               var lineArray, lineHash, chars1, chars2;
+               lineArray = []; // E.g. lineArray[4] === 'Hello\n'
+               lineHash = {};  // E.g. lineHash['Hello\n'] === 4
 
-       assertLi = document.createElement( "li" );
-       assertLi.className = details.result ? "pass" : "fail";
-       assertLi.innerHTML = message;
-       assertList.appendChild( assertLi );
-});
+               // '\x00' is a valid character, but various debuggers don't like it.
+               // So we'll insert a junk entry to avoid generating a null character.
+               lineArray[ 0 ] = "";
 
-QUnit.testDone(function( details ) {
-       var testTitle, time, testItem, assertList,
-               good, bad, testCounts, skipped, sourceName,
-               tests = id( "qunit-tests" );
+               /**
+                * Split a text into an array of strings.  Reduce the texts to a string of
+                * hashes where each Unicode character represents one line.
+                * Modifies linearray and linehash through being a closure.
+                * @param {string} text String to encode.
+                * @return {string} Encoded string.
+                * @private
+                */
+               function diffLinesToCharsMunge( text ) {
+                       var chars, lineStart, lineEnd, lineArrayLength, line;
+                       chars = "";
 
-       if ( !tests ) {
-               return;
-       }
+                       // Walk the text, pulling out a substring for each line.
+                       // text.split('\n') would would temporarily double our memory footprint.
+                       // Modifying text would create many large strings to garbage collect.
+                       lineStart = 0;
+                       lineEnd = -1;
 
-       testItem = id( "qunit-test-output-" + details.testId );
+                       // Keeping our own length variable is faster than looking it up.
+                       lineArrayLength = lineArray.length;
+                       while ( lineEnd < text.length - 1 ) {
+                               lineEnd = text.indexOf( "\n", lineStart );
+                               if ( lineEnd === -1 ) {
+                                       lineEnd = text.length - 1;
+                               }
+                               line = text.substring( lineStart, lineEnd + 1 );
+                               lineStart = lineEnd + 1;
 
-       assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+                               if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
+                                                       ( lineHash[ line ] !== undefined ) ) {
+                                       chars += String.fromCharCode( lineHash[ line ] );
+                               } else {
+                                       chars += String.fromCharCode( lineArrayLength );
+                                       lineHash[ line ] = lineArrayLength;
+                                       lineArray[ lineArrayLength++ ] = line;
+                               }
+                       }
+                       return chars;
+               }
 
-       good = details.passed;
-       bad = details.failed;
+               chars1 = diffLinesToCharsMunge( text1 );
+               chars2 = diffLinesToCharsMunge( text2 );
+               return {
+                       chars1: chars1,
+                       chars2: chars2,
+                       lineArray: lineArray
+               };
+       };
 
-       // store result when possible
-       if ( config.reorder && defined.sessionStorage ) {
-               if ( bad ) {
-                       sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
-               } else {
-                       sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+       /**
+        * Rehydrate the text in a diff from a string of line hashes to real lines of
+        * text.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        * @param {!Array.<string>} lineArray Array of unique strings.
+        * @private
+        */
+       DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
+               var x, chars, text, y;
+               for ( x = 0; x < diffs.length; x++ ) {
+                       chars = diffs[ x ][ 1 ];
+                       text = [];
+                       for ( y = 0; y < chars.length; y++ ) {
+                               text[ y ] = lineArray[ chars.charCodeAt( y ) ];
+                       }
+                       diffs[ x ][ 1 ] = text.join( "" );
                }
-       }
+       };
 
-       if ( bad === 0 ) {
+       /**
+        * Reorder and merge like edit sections.  Merge equalities.
+        * Any edit section can move as long as it doesn't cross an equality.
+        * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+        */
+       DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
+               var pointer, countDelete, countInsert, textInsert, textDelete,
+                       commonlength, changes, diffPointer, position;
+               diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
+               pointer = 0;
+               countDelete = 0;
+               countInsert = 0;
+               textDelete = "";
+               textInsert = "";
+               commonlength;
+               while ( pointer < diffs.length ) {
+                       switch ( diffs[ pointer ][ 0 ] ) {
+                       case DIFF_INSERT:
+                               countInsert++;
+                               textInsert += diffs[ pointer ][ 1 ];
+                               pointer++;
+                               break;
+                       case DIFF_DELETE:
+                               countDelete++;
+                               textDelete += diffs[ pointer ][ 1 ];
+                               pointer++;
+                               break;
+                       case DIFF_EQUAL:
 
-               // Collapse the passing tests
-               addClass( assertList, "qunit-collapsed" );
-       } else if ( bad && config.collapse && !collapseNext ) {
+                               // Upon reaching an equality, check for prior redundancies.
+                               if ( countDelete + countInsert > 1 ) {
+                                       if ( countDelete !== 0 && countInsert !== 0 ) {
 
-               // Skip collapsing the first failing test
-               collapseNext = true;
-       } else {
+                                               // Factor out any common prefixes.
+                                               commonlength = this.diffCommonPrefix( textInsert, textDelete );
+                                               if ( commonlength !== 0 ) {
+                                                       if ( ( pointer - countDelete - countInsert ) > 0 &&
+                                                                       diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
+                                                                       DIFF_EQUAL ) {
+                                                               diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
+                                                                       textInsert.substring( 0, commonlength );
+                                                       } else {
+                                                               diffs.splice( 0, 0, [ DIFF_EQUAL,
+                                                                       textInsert.substring( 0, commonlength )
+                                                               ] );
+                                                               pointer++;
+                                                       }
+                                                       textInsert = textInsert.substring( commonlength );
+                                                       textDelete = textDelete.substring( commonlength );
+                                               }
 
-               // Collapse remaining tests
-               addClass( assertList, "qunit-collapsed" );
-       }
+                                               // Factor out any common suffixies.
+                                               commonlength = this.diffCommonSuffix( textInsert, textDelete );
+                                               if ( commonlength !== 0 ) {
+                                                       diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
+                                                                       commonlength ) + diffs[ pointer ][ 1 ];
+                                                       textInsert = textInsert.substring( 0, textInsert.length -
+                                                               commonlength );
+                                                       textDelete = textDelete.substring( 0, textDelete.length -
+                                                               commonlength );
+                                               }
+                                       }
 
-       // testItem.firstChild is the test name
-       testTitle = testItem.firstChild;
+                                       // Delete the offending records and add the merged ones.
+                                       if ( countDelete === 0 ) {
+                                               diffs.splice( pointer - countInsert,
+                                                       countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
+                                       } else if ( countInsert === 0 ) {
+                                               diffs.splice( pointer - countDelete,
+                                                       countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
+                                       } else {
+                                               diffs.splice(
+                                                       pointer - countDelete - countInsert,
+                                                       countDelete + countInsert,
+                                                       [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
+                                               );
+                                       }
+                                       pointer = pointer - countDelete - countInsert +
+                                               ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
+                               } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
 
-       testCounts = bad ?
-               "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
-               "";
+                                       // Merge this equality with the previous one.
+                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
+                                       diffs.splice( pointer, 1 );
+                               } else {
+                                       pointer++;
+                               }
+                               countInsert = 0;
+                               countDelete = 0;
+                               textDelete = "";
+                               textInsert = "";
+                               break;
+                       }
+               }
+               if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
+                       diffs.pop(); // Remove the dummy entry at the end.
+               }
 
-       testTitle.innerHTML += " <b class='counts'>(" + testCounts +
-               details.assertions.length + ")</b>";
+               // Second pass: look for single edits surrounded on both sides by equalities
+               // which can be shifted sideways to eliminate an equality.
+               // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+               changes = false;
+               pointer = 1;
 
-       if ( details.skipped ) {
-               testItem.className = "skipped";
-               skipped = document.createElement( "em" );
-               skipped.className = "qunit-skipped-label";
-               skipped.innerHTML = "skipped";
-               testItem.insertBefore( skipped, testTitle );
-       } else {
-               addEvent( testTitle, "click", function() {
-                       toggleClass( assertList, "qunit-collapsed" );
-               });
+               // Intentionally ignore the first and last element (don't need checking).
+               while ( pointer < diffs.length - 1 ) {
+                       if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
+                                       diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
 
-               testItem.className = bad ? "fail" : "pass";
+                               diffPointer = diffs[ pointer ][ 1 ];
+                               position = diffPointer.substring(
+                                       diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
+                               );
 
-               time = document.createElement( "span" );
-               time.className = "runtime";
-               time.innerHTML = details.runtime + " ms";
-               testItem.insertBefore( time, assertList );
-       }
+                               // This is a single edit surrounded by equalities.
+                               if ( position === diffs[ pointer - 1 ][ 1 ] ) {
 
-       // Show the source of the test when showing assertions
-       if ( details.source ) {
-               sourceName = document.createElement( "p" );
-               sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
-               addClass( sourceName, "qunit-source" );
-               if ( bad === 0 ) {
-                       addClass( sourceName, "qunit-collapsed" );
+                                       // Shift the edit over the previous equality.
+                                       diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
+                                               diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
+                                                       diffs[ pointer - 1 ][ 1 ].length );
+                                       diffs[ pointer + 1 ][ 1 ] =
+                                               diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
+                                       diffs.splice( pointer - 1, 1 );
+                                       changes = true;
+                               } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
+                                               diffs[ pointer + 1 ][ 1 ] ) {
+
+                                       // Shift the edit over the next equality.
+                                       diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
+                                       diffs[ pointer ][ 1 ] =
+                                               diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
+                                               diffs[ pointer + 1 ][ 1 ];
+                                       diffs.splice( pointer + 1, 1 );
+                                       changes = true;
+                               }
+                       }
+                       pointer++;
                }
-               addEvent( testTitle, "click", function() {
-                       toggleClass( sourceName, "qunit-collapsed" );
-               });
-               testItem.appendChild( sourceName );
-       }
-});
 
-if ( defined.document ) {
+               // If shifts were made, the diff needs reordering and another shift sweep.
+               if ( changes ) {
+                       this.diffCleanupMerge( diffs );
+               }
+       };
 
-       // Avoid readyState issue with phantomjs
-       // Ref: #818
-       var notPhantom = ( function( p ) {
-               return !( p && p.version && p.version.major > 0 );
-       } )( window.phantom );
+       return function( o, n ) {
+               var diff, output, text;
+               diff = new DiffMatchPatch();
+               output = diff.DiffMain( o, n );
+               diff.diffCleanupEfficiency( output );
+               text = diff.diffPrettyHtml( output );
 
-       if ( notPhantom && document.readyState === "complete" ) {
-               QUnit.load();
-       } else {
-               addEvent( window, "load", QUnit.load );
-       }
-} else {
-       config.pageLoaded = true;
-       config.autorun = true;
-}
+               return text;
+       };
+}() );
 
-})();
+}() );