Move LESS function definitions from DefaultSettings to ResourceLoaderLESSFunctions.php
[lhc/web/wiklou.git] / resources / jquery / jquery.qunit.js
index c1570c2..302545f 100644 (file)
@@ -1,35 +1,87 @@
 /**
- * QUnit v1.8.0 - A JavaScript Unit Testing Framework
+ * QUnit v1.11.0 - A JavaScript Unit Testing Framework
  *
- * http://docs.jquery.com/QUnit
+ * http://qunitjs.com
  *
- * Copyright (c) 2012 John Resig, Jörn Zaefferer
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * or GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
  */
 
 (function( window ) {
 
 var QUnit,
+       assert,
        config,
        onErrorFnPrev,
        testId = 0,
        fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty,
+       // Keep a local reference to Date (GH-283)
+       Date = window.Date,
        defined = {
-       setTimeout: typeof window.setTimeout !== "undefined",
-       sessionStorage: (function() {
-               var x = "qunit-test-string";
-               try {
-                       sessionStorage.setItem( x, x );
-                       sessionStorage.removeItem( x );
-                       return true;
-               } catch( e ) {
-                       return false;
+               setTimeout: typeof window.setTimeout !== "undefined",
+               sessionStorage: (function() {
+                       var x = "qunit-test-string";
+                       try {
+                               sessionStorage.setItem( x, x );
+                               sessionStorage.removeItem( x );
+                               return true;
+                       } catch( e ) {
+                               return false;
+                       }
+               }())
+       },
+       /**
+        * Provides a normalized error string, correcting an issue
+        * with IE 7 (and prior) where Error.prototype.toString is
+        * not properly implemented
+        *
+        * Based on http://es5.github.com/#x15.11.4.4
+        *
+        * @param {String|Error} error
+        * @return {String} error message
+        */
+       errorString = function( error ) {
+               var name, message,
+                       errorString = error.toString();
+               if ( errorString.substring( 0, 7 ) === "[object" ) {
+                       name = error.name ? error.name.toString() : "Error";
+                       message = error.message ? error.message.toString() : "";
+                       if ( name && message ) {
+                               return name + ": " + message;
+                       } else if ( name ) {
+                               return name;
+                       } else if ( message ) {
+                               return message;
+                       } else {
+                               return "Error";
+                       }
+               } else {
+                       return errorString;
                }
-       }())
-};
+       },
+       /**
+        * Makes a clone of an object using only Array or Object as base,
+        * and copies over the own enumerable properties.
+        *
+        * @param {Object} obj
+        * @return {Object} New object with only the own properties (recursively).
+        */
+       objectValues = function( obj ) {
+               // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
+               /*jshint newcap: false */
+               var key, val,
+                       vals = QUnit.is( "array", obj ) ? [] : {};
+               for ( key in obj ) {
+                       if ( hasOwn.call( obj, key ) ) {
+                               val = obj[key];
+                               vals[key] = val === Object(val) ? objectValues(val) : val;
+                       }
+               }
+               return vals;
+       };
 
 function Test( settings ) {
        extend( this, settings );
@@ -42,11 +94,11 @@ Test.count = 0;
 Test.prototype = {
        init: function() {
                var a, b, li,
-        tests = id( "qunit-tests" );
+                       tests = id( "qunit-tests" );
 
                if ( tests ) {
                        b = document.createElement( "strong" );
-                       b.innerHTML = this.name;
+                       b.innerHTML = this.nameHtml;
 
                        // `a` initialized at top of scope
                        a = document.createElement( "a" );
@@ -90,6 +142,7 @@ Test.prototype = {
                        teardown: function() {}
                }, this.moduleTestEnvironment );
 
+               this.started = +new Date();
                runLoggingCallbacks( "testStart", QUnit, {
                        name: this.testName,
                        module: this.module
@@ -109,7 +162,7 @@ Test.prototype = {
                try {
                        this.testEnvironment.setup.call( this.testEnvironment );
                } catch( e ) {
-                       QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+                       QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
                }
        },
        run: function() {
@@ -118,22 +171,28 @@ Test.prototype = {
                var running = id( "qunit-testresult" );
 
                if ( running ) {
-                       running.innerHTML = "Running: <br/>" + this.name;
+                       running.innerHTML = "Running: <br/>" + this.nameHtml;
                }
 
                if ( this.async ) {
                        QUnit.stop();
                }
 
+               this.callbackStarted = +new Date();
+
                if ( config.notrycatch ) {
                        this.callback.call( this.testEnvironment, QUnit.assert );
+                       this.callbackRuntime = +new Date() - this.callbackStarted;
                        return;
                }
 
                try {
                        this.callback.call( this.testEnvironment, QUnit.assert );
+                       this.callbackRuntime = +new Date() - this.callbackStarted;
                } catch( e ) {
-                       QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
+                       this.callbackRuntime = +new Date() - this.callbackStarted;
+
+                       QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
                        // else next test will carry the responsibility
                        saveGlobal();
 
@@ -146,38 +205,43 @@ Test.prototype = {
        teardown: function() {
                config.current = this;
                if ( config.notrycatch ) {
+                       if ( typeof this.callbackRuntime === "undefined" ) {
+                               this.callbackRuntime = +new Date() - this.callbackStarted;
+                       }
                        this.testEnvironment.teardown.call( this.testEnvironment );
                        return;
                } else {
                        try {
                                this.testEnvironment.teardown.call( this.testEnvironment );
                        } catch( e ) {
-                               QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+                               QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
                        }
                }
                checkPollution();
        },
        finish: function() {
                config.current = this;
-               if ( config.requireExpects && this.expected == null ) {
+               if ( config.requireExpects && this.expected === null ) {
                        QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
-               } else if ( this.expected != null && this.expected != this.assertions.length ) {
+               } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
                        QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
-               } else if ( this.expected == null && !this.assertions.length ) {
+               } else if ( this.expected === null && !this.assertions.length ) {
                        QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
                }
 
-               var assertion, a, b, i, li, ol,
+               var i, assertion, a, b, time, li, ol,
                        test = this,
                        good = 0,
                        bad = 0,
                        tests = id( "qunit-tests" );
 
+               this.runtime = +new Date() - this.started;
                config.stats.all += this.assertions.length;
                config.moduleStats.all += this.assertions.length;
 
                if ( tests ) {
                        ol = document.createElement( "ol" );
+                       ol.className = "qunit-assert-list";
 
                        for ( i = 0; i < this.assertions.length; i++ ) {
                                assertion = this.assertions[i];
@@ -206,22 +270,22 @@ Test.prototype = {
                        }
 
                        if ( bad === 0 ) {
-                               ol.style.display = "none";
+                               addClass( ol, "qunit-collapsed" );
                        }
 
                        // `b` initialized at top of scope
                        b = document.createElement( "strong" );
-                       b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+                       b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
 
                        addEvent(b, "click", function() {
-                               var next = b.nextSibling.nextSibling,
-                                       display = next.style.display;
-                               next.style.display = display === "none" ? "block" : "none";
+                               var next = b.parentNode.lastChild,
+                                       collapsed = hasClass( next, "qunit-collapsed" );
+                               ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
                        });
 
                        addEvent(b, "dblclick", function( e ) {
                                var target = e && e.target ? e.target : window.event.srcElement;
-                               if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+                               if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
                                        target = target.parentNode;
                                }
                                if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
@@ -229,13 +293,19 @@ Test.prototype = {
                                }
                        });
 
+                       // `time` initialized at top of scope
+                       time = document.createElement( "span" );
+                       time.className = "runtime";
+                       time.innerHTML = this.runtime + " ms";
+
                        // `li` initialized at top of scope
                        li = id( this.id );
                        li.className = bad ? "fail" : "pass";
                        li.removeChild( li.firstChild );
                        a = li.firstChild;
                        li.appendChild( b );
-                       li.appendChild ( a );
+                       li.appendChild( a );
+                       li.appendChild( time );
                        li.appendChild( ol );
 
                } else {
@@ -253,7 +323,8 @@ Test.prototype = {
                        module: this.module,
                        failed: bad,
                        passed: this.assertions.length - bad,
-                       total: this.assertions.length
+                       total: this.assertions.length,
+                       duration: this.runtime
                });
 
                QUnit.reset();
@@ -304,7 +375,8 @@ QUnit = {
        // call on start of module test to prepend name to all tests
        module: function( name, testEnvironment ) {
                config.currentModule = name;
-               config.currentModuleTestEnviroment = testEnvironment;
+               config.currentModuleTestEnvironment = testEnvironment;
+               config.modules[name] = true;
        },
 
        asyncTest: function( testName, expected, callback ) {
@@ -318,7 +390,7 @@ QUnit = {
 
        test: function( testName, expected, callback, async ) {
                var test,
-                       name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
+                       nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
 
                if ( arguments.length === 2 ) {
                        callback = expected;
@@ -326,17 +398,17 @@ QUnit = {
                }
 
                if ( config.currentModule ) {
-                       name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
+                       nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
                }
 
                test = new Test({
-                       name: name,
+                       nameHtml: nameHtml,
                        testName: testName,
                        expected: expected,
                        async: async,
                        callback: callback,
                        module: config.currentModule,
-                       moduleTestEnvironment: config.currentModuleTestEnviroment,
+                       moduleTestEnvironment: config.currentModuleTestEnvironment,
                        stack: sourceFromStacktrace( 2 )
                });
 
@@ -349,10 +421,26 @@ QUnit = {
 
        // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
        expect: function( asserts ) {
-               config.current.expected = asserts;
+               if (arguments.length === 1) {
+                       config.current.expected = asserts;
+               } else {
+                       return config.current.expected;
+               }
        },
 
        start: function( count ) {
+               // QUnit hasn't been initialized yet.
+               // Note: RequireJS (et al) may delay onLoad
+               if ( config.semaphore === undefined ) {
+                       QUnit.begin(function() {
+                               // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
+                               setTimeout(function() {
+                                       QUnit.start( count );
+                               });
+                       });
+                       return;
+               }
+
                config.semaphore -= count || 1;
                // don't start until equal number of stop-calls
                if ( config.semaphore > 0 ) {
@@ -361,6 +449,8 @@ QUnit = {
                // ignore if start is called more often then stop
                if ( config.semaphore < 0 ) {
                        config.semaphore = 0;
+                       QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
+                       return;
                }
                // A slight delay, to avoid any current callbacks
                if ( defined.setTimeout ) {
@@ -396,13 +486,18 @@ QUnit = {
        }
 };
 
+// `assert` initialized at top of scope
 // Asssert helpers
-// All of these must call either QUnit.push() or manually do:
+// All of these must either call QUnit.push() or manually do:
 // - runLoggingCallbacks( "log", .. );
 // - config.current.assertions.push({ .. });
-QUnit.assert = {
+// We attach it to the QUnit object *after* we expose the public API,
+// otherwise `assert` will become a global variable in browsers (#341).
+assert = {
        /**
         * Asserts rough true-ish result.
+        * @name ok
+        * @function
         * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
         */
        ok: function( result, msg ) {
@@ -413,18 +508,20 @@ QUnit.assert = {
 
                var source,
                        details = {
+                               module: config.current.module,
+                               name: config.current.testName,
                                result: result,
                                message: msg
                        };
 
-               msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+               msg = escapeText( msg || (result ? "okay" : "failed" ) );
                msg = "<span class='test-message'>" + msg + "</span>";
 
                if ( !result ) {
                        source = sourceFromStacktrace( 2 );
                        if ( source ) {
                                details.source = source;
-                               msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+                               msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
                        }
                }
                runLoggingCallbacks( "log", QUnit, details );
@@ -437,36 +534,82 @@ QUnit.assert = {
        /**
         * Assert that the first two arguments are equal, with an optional message.
         * Prints out both actual and expected values.
+        * @name equal
+        * @function
         * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
         */
        equal: function( actual, expected, message ) {
+               /*jshint eqeqeq:false */
                QUnit.push( expected == actual, actual, expected, message );
        },
 
+       /**
+        * @name notEqual
+        * @function
+        */
        notEqual: function( actual, expected, message ) {
+               /*jshint eqeqeq:false */
                QUnit.push( expected != actual, actual, expected, message );
        },
 
+       /**
+        * @name propEqual
+        * @function
+        */
+       propEqual: function( actual, expected, message ) {
+               actual = objectValues(actual);
+               expected = objectValues(expected);
+               QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+       },
+
+       /**
+        * @name notPropEqual
+        * @function
+        */
+       notPropEqual: function( actual, expected, message ) {
+               actual = objectValues(actual);
+               expected = objectValues(expected);
+               QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+       },
+
+       /**
+        * @name deepEqual
+        * @function
+        */
        deepEqual: function( actual, expected, message ) {
                QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
        },
 
+       /**
+        * @name notDeepEqual
+        * @function
+        */
        notDeepEqual: function( actual, expected, message ) {
                QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
        },
 
+       /**
+        * @name strictEqual
+        * @function
+        */
        strictEqual: function( actual, expected, message ) {
                QUnit.push( expected === actual, actual, expected, message );
        },
 
+       /**
+        * @name notStrictEqual
+        * @function
+        */
        notStrictEqual: function( actual, expected, message ) {
                QUnit.push( expected !== actual, actual, expected, message );
        },
 
-       raises: function( block, expected, message ) {
+       "throws": function( block, expected, message ) {
                var actual,
+                       expectedOutput = expected,
                        ok = false;
 
+               // 'expected' is optional
                if ( typeof expected === "string" ) {
                        message = expected;
                        expected = null;
@@ -484,28 +627,42 @@ QUnit.assert = {
                        // we don't want to validate thrown error
                        if ( !expected ) {
                                ok = true;
+                               expectedOutput = null;
                        // expected is a regexp
                        } else if ( QUnit.objectType( expected ) === "regexp" ) {
-                               ok = expected.test( actual );
+                               ok = expected.test( errorString( actual ) );
                        // expected is a constructor
                        } else if ( actual instanceof expected ) {
                                ok = true;
                        // expected is a validation function which returns true is validation passed
                        } else if ( expected.call( {}, actual ) === true ) {
+                               expectedOutput = null;
                                ok = true;
                        }
-               }
 
-               QUnit.push( ok, actual, null, message );
+                       QUnit.push( ok, actual, expectedOutput, message );
+               } else {
+                       QUnit.pushFailure( message, null, 'No exception was thrown.' );
+               }
        }
 };
 
-// @deprecated: Kept assertion helpers in root for backwards compatibility
-extend( QUnit, QUnit.assert );
+/**
+ * @deprecate since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility.
+ */
+extend( QUnit, assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept root "raises()" for backwards compatibility.
+ * (Note that we don't introduce assert.raises).
+ */
+QUnit.raises = assert[ "throws" ];
 
 /**
- * @deprecated: Kept for backwards compatibility
- * next step: remove entirely
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
  */
 QUnit.equals = function() {
        QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
@@ -549,7 +706,23 @@ config = {
        // when enabled, all tests must call expect()
        requireExpects: false,
 
-       urlConfig: [ "noglobals", "notrycatch" ],
+       // add checkboxes that are persisted in the query-string
+       // when enabled, the id is set to `true` as a `QUnit.config` property
+       urlConfig: [
+               {
+                       id: "noglobals",
+                       label: "Check for Globals",
+                       tooltip: "Enabling this will test if any test introduces new properties on the `window` object. 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."
+               }
+       ],
+
+       // Set of all modules.
+       modules: {},
 
        // logging callback queues
        begin: [],
@@ -561,6 +734,15 @@ config = {
        moduleDone: []
 };
 
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+       extend( window, QUnit );
+
+       // Expose QUnit object
+       window.QUnit = QUnit;
+}
+
 // Initialize more QUnit.config and QUnit.urlParams
 (function() {
        var i,
@@ -594,18 +776,11 @@ config = {
        QUnit.isLocal = location.protocol === "file:";
 }());
 
-// Export global variables, unless an 'exports' object exists,
-// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
-if ( typeof exports === "undefined" ) {
-       extend( window, QUnit );
-
-       // Expose QUnit object
-       window.QUnit = QUnit;
-}
-
 // Extend QUnit object,
 // these after set here because they should not be exposed as global functions
 extend( QUnit, {
+       assert: assert,
+
        config: config,
 
        // Initialize the configuration options
@@ -620,7 +795,7 @@ extend( QUnit, {
                        autorun: false,
                        filter: "",
                        queue: [],
-                       semaphore: 0
+                       semaphore: 1
                });
 
                var tests, banner, result,
@@ -628,7 +803,7 @@ extend( QUnit, {
 
                if ( qunit ) {
                        qunit.innerHTML =
-                               "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+                               "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
                                "<h2 id='qunit-banner'></h2>" +
                                "<div id='qunit-testrunner-toolbar'></div>" +
                                "<h2 id='qunit-userAgent'></h2>" +
@@ -661,17 +836,10 @@ extend( QUnit, {
        },
 
        // Resets the test setup. Useful for tests that modify the DOM.
-       // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
        reset: function() {
-               var fixture;
-
-               if ( window.jQuery ) {
-                       jQuery( "#qunit-fixture" ).html( config.fixture );
-               } else {
-                       fixture = id( "qunit-fixture" );
-                       if ( fixture ) {
-                               fixture.innerHTML = config.fixture;
-                       }
+               var fixture = id( "qunit-fixture" );
+               if ( fixture ) {
+                       fixture.innerHTML = config.fixture;
                }
        },
 
@@ -691,7 +859,7 @@ extend( QUnit, {
 
        // Safe object type checking
        is: function( type, obj ) {
-               return QUnit.objectType( obj ) == type;
+               return QUnit.objectType( obj ) === type;
        },
 
        objectType: function( obj ) {
@@ -703,7 +871,8 @@ extend( QUnit, {
                                return "null";
                }
 
-               var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
+               var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
+                       type = match && match[1] || "";
 
                switch ( type ) {
                        case "Number":
@@ -732,22 +901,24 @@ extend( QUnit, {
 
                var output, source,
                        details = {
+                               module: config.current.module,
+                               name: config.current.testName,
                                result: result,
                                message: message,
                                actual: actual,
                                expected: expected
                        };
 
-               message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
+               message = escapeText( message ) || ( result ? "okay" : "failed" );
                message = "<span class='test-message'>" + message + "</span>";
                output = message;
 
                if ( !result ) {
-                       expected = escapeInnerText( QUnit.jsDump.parse(expected) );
-                       actual = escapeInnerText( QUnit.jsDump.parse(actual) );
+                       expected = escapeText( QUnit.jsDump.parse(expected) );
+                       actual = escapeText( QUnit.jsDump.parse(actual) );
                        output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
 
-                       if ( actual != expected ) {
+                       if ( actual !== expected ) {
                                output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
                                output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
                        }
@@ -756,7 +927,7 @@ extend( QUnit, {
 
                        if ( source ) {
                                details.source = source;
-                               output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+                               output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
                        }
 
                        output += "</table>";
@@ -770,26 +941,36 @@ extend( QUnit, {
                });
        },
 
-       pushFailure: function( message, source ) {
+       pushFailure: function( message, source, actual ) {
                if ( !config.current ) {
                        throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
                }
 
                var output,
                        details = {
+                               module: config.current.module,
+                               name: config.current.testName,
                                result: false,
                                message: message
                        };
 
-               message = escapeInnerText(message ) || "error";
+               message = escapeText( message ) || "error";
                message = "<span class='test-message'>" + message + "</span>";
                output = message;
 
+               output += "<table>";
+
+               if ( actual ) {
+                       output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
+               }
+
                if ( source ) {
                        details.source = source;
-                       output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+                       output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
                }
 
+               output += "</table>";
+
                runLoggingCallbacks( "log", QUnit, details );
 
                config.current.assertions.push({
@@ -810,7 +991,8 @@ extend( QUnit, {
                        querystring += encodeURIComponent( key ) + "=" +
                                encodeURIComponent( params[ key ] ) + "&";
                }
-               return window.location.pathname + querystring.slice( 0, -1 );
+               return window.location.protocol + "//" + window.location.host +
+                       window.location.pathname + querystring.slice( 0, -1 );
        },
 
        extend: extend,
@@ -841,7 +1023,7 @@ extend( QUnit.constructor.prototype, {
        // testStart: { name }
        testStart: registerLoggingCallback( "testStart" ),
 
-       // testDone: { name, failed, passed, total }
+       // testDone: { name, failed, passed, total, duration }
        testDone: registerLoggingCallback( "testDone" ),
 
        // moduleStart: { name }
@@ -860,6 +1042,9 @@ QUnit.load = function() {
 
        // Initialize the config, saving the execution queue
        var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
+               urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
+               numModules = 0,
+               moduleFilterHtml = "",
                urlConfigHtml = "",
                oldconfig = extend( {}, config );
 
@@ -872,10 +1057,36 @@ QUnit.load = function() {
 
        for ( i = 0; i < len; i++ ) {
                val = config.urlConfig[i];
-               config[val] = QUnit.urlParams[val];
-               urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>";
+               if ( typeof val === "string" ) {
+                       val = {
+                               id: val,
+                               label: val,
+                               tooltip: "[no tooltip available]"
+                       };
+               }
+               config[ val.id ] = QUnit.urlParams[ val.id ];
+               urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
+                       "' name='" + escapeText( val.id ) +
+                       "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
+                       " title='" + escapeText( val.tooltip ) +
+                       "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
+                       "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
        }
 
+       moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+               ( config.module === undefined  ? "selected='selected'" : "" ) +
+               ">< All Modules ></option>";
+
+       for ( i in config.modules ) {
+               if ( config.modules.hasOwnProperty( i ) ) {
+                       numModules += 1;
+                       moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " +
+                               ( config.module === i ? "selected='selected'" : "" ) +
+                               ">" + escapeText(i) + "</option>";
+               }
+       }
+       moduleFilterHtml += "</select>";
+
        // `userAgent` initialized at top of scope
        userAgent = id( "qunit-userAgent" );
        if ( userAgent ) {
@@ -885,12 +1096,7 @@ QUnit.load = function() {
        // `banner` initialized at top of scope
        banner = id( "qunit-header" );
        if ( banner ) {
-               banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml;
-               addEvent( banner, "change", function( event ) {
-                       var params = {};
-                       params[ event.target.name ] = event.target.checked ? true : undefined;
-                       window.location = QUnit.url( params );
-               });
+               banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
        }
 
        // `toolbar` initialized at top of scope
@@ -931,8 +1137,37 @@ QUnit.load = function() {
                // `label` initialized at top of scope
                label = document.createElement( "label" );
                label.setAttribute( "for", "qunit-filter-pass" );
+               label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
                label.innerHTML = "Hide passed tests";
                toolbar.appendChild( label );
+
+               urlConfigCheckboxesContainer = document.createElement("span");
+               urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
+               urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
+               // For oldIE support:
+               // * Add handlers to the individual elements instead of the container
+               // * Use "click" instead of "change"
+               // * Fallback from event.target to event.srcElement
+               addEvents( urlConfigCheckboxes, "click", function( event ) {
+                       var params = {},
+                               target = event.target || event.srcElement;
+                       params[ target.name ] = target.checked ? true : undefined;
+                       window.location = QUnit.url( params );
+               });
+               toolbar.appendChild( urlConfigCheckboxesContainer );
+
+               if (numModules > 1) {
+                       moduleFilter = document.createElement( 'span' );
+                       moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
+                       moduleFilter.innerHTML = moduleFilterHtml;
+                       addEvent( moduleFilter.lastChild, "change", function() {
+                               var selectBox = moduleFilter.getElementsByTagName("select")[0],
+                                       selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+                               window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
+                       });
+                       toolbar.appendChild(moduleFilter);
+               }
        }
 
        // `main` initialized at top of scope
@@ -970,9 +1205,9 @@ window.onerror = function ( error, filePath, linerNr ) {
                        }
                        QUnit.pushFailure( error, filePath + ":" + linerNr );
                } else {
-                       QUnit.test( "global failure", function() {
+                       QUnit.test( "global failure", extend( function() {
                                QUnit.pushFailure( error, filePath + ":" + linerNr );
-                       });
+                       }, { validTest: validTest } ) );
                }
                return false;
        }
@@ -1004,7 +1239,7 @@ function done() {
                        " milliseconds.<br/>",
                        "<span class='passed'>",
                        passed,
-                       "</span> tests of <span class='total'>",
+                       "</span> assertions of <span class='total'>",
                        config.stats.all,
                        "</span> passed, <span class='failed'>",
                        config.stats.bad,
@@ -1039,6 +1274,11 @@ function done() {
                }
        }
 
+       // scroll back to top to show results
+       if ( window.scrollTo ) {
+               window.scrollTo(0, 0);
+       }
+
        runLoggingCallbacks( "done", QUnit, {
                failed: config.stats.bad,
                passed: passed,
@@ -1051,14 +1291,20 @@ function done() {
 function validTest( test ) {
        var include,
                filter = config.filter && config.filter.toLowerCase(),
-               module = config.module,
+               module = config.module && config.module.toLowerCase(),
                fullName = (test.module + ": " + test.testName).toLowerCase();
 
+       // Internally-generated tests are always valid
+       if ( test.callback && test.callback.validTest === validTest ) {
+               delete test.callback.validTest;
+               return true;
+       }
+
        if ( config.testNumber ) {
                return test.testNumber === config.testNumber;
        }
 
-       if ( module && test.module !== module ) {
+       if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
                return false;
        }
 
@@ -1086,7 +1332,7 @@ function validTest( test ) {
 function extractStacktrace( e, offset ) {
        offset = offset === undefined ? 3 : offset;
 
-       var stack, include, i, regex;
+       var stack, include, i;
 
        if ( e.stacktrace ) {
                // Opera
@@ -1100,7 +1346,7 @@ function extractStacktrace( e, offset ) {
                if ( fileName ) {
                        include = [];
                        for ( i = offset; i < stack.length; i++ ) {
-                               if ( stack[ i ].indexOf( fileName ) != -1 ) {
+                               if ( stack[ i ].indexOf( fileName ) !== -1 ) {
                                        break;
                                }
                                include.push( stack[ i ] );
@@ -1129,17 +1375,27 @@ function sourceFromStacktrace( offset ) {
        }
 }
 
-function escapeInnerText( s ) {
+/**
+ * Escape text for attribute or text content.
+ */
+function escapeText( s ) {
        if ( !s ) {
                return "";
        }
        s = s + "";
-       return s.replace( /[\&<>]/g, function( s ) {
+       // Both single quotes and double quotes (for attributes)
+       return s.replace( /['"<>&]/g, function( s ) {
                switch( s ) {
-                       case "&": return "&amp;";
-                       case "<": return "&lt;";
-                       case ">": return "&gt;";
-                       default: return s;
+                       case '\'':
+                               return '&#039;';
+                       case '"':
+                               return '&quot;';
+                       case '<':
+                               return '&lt;';
+                       case '>':
+                               return '&gt;';
+                       case '&':
+                               return '&amp;';
                }
        });
 }
@@ -1187,7 +1443,7 @@ function saveGlobal() {
        }
 }
 
-function checkPollution( name ) {
+function checkPollution() {
        var newGlobals,
                deletedGlobals,
                old = config.pollution;
@@ -1236,16 +1492,53 @@ function extend( a, b ) {
        return a;
 }
 
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
 function addEvent( elem, type, fn ) {
+       // Standards-based browsers
        if ( elem.addEventListener ) {
                elem.addEventListener( type, fn, false );
-       } else if ( elem.attachEvent ) {
-               elem.attachEvent( "on" + type, fn );
+       // IE
        } else {
-               fn();
+               elem.attachEvent( "on" + type, fn );
        }
 }
 
+/**
+ * @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 );
+       }
+}
+
+function hasClass( elem, name ) {
+       return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
+}
+
+function addClass( elem, name ) {
+       if ( !hasClass( elem, name ) ) {
+               elem.className += (elem.className ? " " : "") + name;
+       }
+}
+
+function removeClass( elem, name ) {
+       var set = " " + elem.className + " ";
+       // Class name may appear multiple times
+       while ( set.indexOf(" " + name + " ") > -1 ) {
+               set = set.replace(" " + name + " " , " ");
+       }
+       // If possible, trim it for prettiness, but not neccecarily
+       elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
+}
+
 function id( name ) {
        return !!( typeof document !== "undefined" && document && document.getElementById ) &&
                document.getElementById( name );
@@ -1259,7 +1552,6 @@ function registerLoggingCallback( key ) {
 
 // Supports deprecated method of completely overwriting logging callbacks
 function runLoggingCallbacks( key, scope, args ) {
-       //debugger;
        var i, callbacks;
        if ( QUnit.hasOwnProperty( key ) ) {
                QUnit[ key ].call(scope, args );
@@ -1301,6 +1593,7 @@ QUnit.equiv = (function() {
 
                        // for string, boolean, number and null
                        function useStrictEquality( b, a ) {
+                               /*jshint eqeqeq:false */
                                if ( b instanceof a.constructor || a instanceof b.constructor ) {
                                        // to catch short annotaion VS 'new' annotation of a
                                        // declaration
@@ -1335,7 +1628,8 @@ QUnit.equiv = (function() {
                                                a.global === b.global &&
                                                // (gmi) ...
                                                a.ignoreCase === b.ignoreCase &&
-                                               a.multiline === b.multiline;
+                                               a.multiline === b.multiline &&
+                                               a.sticky === b.sticky;
                                },
 
                                // - skip when the property is a method of an instance (OOP)
@@ -1496,7 +1790,8 @@ QUnit.jsDump = (function() {
 
        var reName = /^function (\w+)/,
                jsDump = {
-                       parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+                       // type is used mostly internally, you can fix a (custom)type in advance
+                       parse: function( obj, type, stack ) {
                                stack = stack || [ ];
                                var inStack, res,
                                        parser = this.parsers[ type || this.typeOf(obj) ];
@@ -1504,18 +1799,16 @@ QUnit.jsDump = (function() {
                                type = typeof parser;
                                inStack = inArray( obj, stack );
 
-                               if ( inStack != -1 ) {
+                               if ( inStack !== -1 ) {
                                        return "recursion(" + (inStack - stack.length) + ")";
                                }
-                               //else
-                               if ( type == "function" )  {
+                               if ( type === "function" )  {
                                        stack.push( obj );
                                        res = parser.call( this, obj, stack );
                                        stack.pop();
                                        return res;
                                }
-                               // else
-                               return ( type == "string" ) ? parser : this.parsers.error;
+                               return ( type === "string" ) ? parser : this.parsers.error;
                        },
                        typeOf: function( obj ) {
                                var type;
@@ -1542,6 +1835,8 @@ QUnit.jsDump = (function() {
                                        ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
                                ) {
                                        type = "array";
+                               } else if ( obj.constructor === Error.prototype.constructor ) {
+                                       type = "error";
                                } else {
                                        type = typeof obj;
                                }
@@ -1550,7 +1845,8 @@ QUnit.jsDump = (function() {
                        separator: function() {
                                return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
                        },
-                       indent: function( extra ) {// 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 "";
                                }
@@ -1579,13 +1875,16 @@ QUnit.jsDump = (function() {
                        parsers: {
                                window: "[Window]",
                                document: "[Document]",
-                               error: "[ERROR]", //when no parser is found, shouldn"t happen
+                               error: function(error) {
+                                       return "Error(\"" + error.message + "\")";
+                               },
                                unknown: "[Unknown]",
                                "null": "null",
                                "undefined": "undefined",
                                "function": function( fn ) {
                                        var ret = "function",
-                                               name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//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;
@@ -1601,13 +1900,9 @@ QUnit.jsDump = (function() {
                                object: function( map, stack ) {
                                        var ret = [ ], keys, key, val, i;
                                        QUnit.jsDump.up();
-                                       if ( Object.keys ) {
-                                               keys = Object.keys( map );
-                                       } else {
-                                               keys = [];
-                                               for ( key in map ) {
-                                                       keys.push( key );
-                                               }
+                                       keys = [];
+                                       for ( key in map ) {
+                                               keys.push( key );
                                        }
                                        keys.sort();
                                        for ( i = 0; i < keys.length; i++ ) {
@@ -1619,21 +1914,34 @@ QUnit.jsDump = (function() {
                                        return join( "{", ret, "}" );
                                },
                                node: function( node ) {
-                                       var a, val,
+                                       var len, i, val,
                                                open = QUnit.jsDump.HTML ? "&lt;" : "<",
                                                close = QUnit.jsDump.HTML ? "&gt;" : ">",
                                                tag = node.nodeName.toLowerCase(),
-                                               ret = open + tag;
-
-                                       for ( a in QUnit.jsDump.DOMAttrs ) {
-                                               val = node[ QUnit.jsDump.DOMAttrs[a] ];
-                                               if ( val ) {
-                                                       ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
+                                               ret = open + tag,
+                                               attrs = node.attributes;
+
+                                       if ( attrs ) {
+                                               for ( i = 0, len = attrs.length; i < len; i++ ) {
+                                                       val = attrs[i].nodeValue;
+                                                       // IE6 includes all attributes in .attributes, even ones not explicitly set.
+                                                       // Those have values like undefined, null, 0, false, "" or "inherit".
+                                                       if ( val && val !== "inherit" ) {
+                                                               ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
+                                                       }
                                                }
                                        }
-                                       return ret + close + open + "/" + tag + close;
+                                       ret += close;
+
+                                       // Show content of TextNode or CDATASection
+                                       if ( node.nodeType === 3 || node.nodeType === 4 ) {
+                                               ret += node.nodeValue;
+                                       }
+
+                                       return ret + open + "/" + tag + close;
                                },
-                               functionArgs: function( fn ) {//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;
 
@@ -1643,54 +1951,34 @@ QUnit.jsDump = (function() {
 
                                        args = new Array(l);
                                        while ( l-- ) {
-                                               args[l] = String.fromCharCode(97+l);//97 is 'a'
+                                               // 97 is 'a'
+                                               args[l] = String.fromCharCode(97+l);
                                        }
                                        return " " + args.join( ", " ) + " ";
                                },
-                               key: quote, //object calls it internally, the key part of an item in a map
-                               functionCode: "[code]", //function calls it internally, it's the content of the function
-                               attribute: quote, //node calls it internally, it's an html attribute value
+                               // 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
+                               functionCode: "[code]",
+                               // node calls it internally, it's an html attribute value
+                               attribute: quote,
                                string: quote,
                                date: quote,
-                               regexp: literal, //regex
+                               regexp: literal,
                                number: literal,
                                "boolean": literal
                        },
-                       DOMAttrs: {
-                               //attributes to dump from nodes, name=>realName
-                               id: "id",
-                               name: "name",
-                               "class": "className"
-                       },
-                       HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
-                       indentChar: "  ",//indentation unit
-                       multiline: true //if true, items in a collection, are separated by a \n, else just a space.
+                       // if true, entities are escaped ( <, >, \t, space and \n )
+                       HTML: false,
+                       // indentation unit
+                       indentChar: "  ",
+                       // if true, items in a collection, are separated by a \n, else just a space.
+                       multiline: true
                };
 
        return jsDump;
 }());
 
-// from Sizzle.js
-function getText( elems ) {
-       var i, elem,
-               ret = "";
-
-       for ( i = 0; elems[i]; i++ ) {
-               elem = elems[i];
-
-               // Get the text from text nodes and CDATA nodes
-               if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
-                       ret += elem.nodeValue;
-
-               // Traverse everything else, except comment nodes
-               } else if ( elem.nodeType !== 8 ) {
-                       ret += getText( elem.childNodes );
-               }
-       }
-
-       return ret;
-}
-
 // from jquery.js
 function inArray( elem, array ) {
        if ( array.indexOf ) {
@@ -1721,13 +2009,14 @@ function inArray( elem, array ) {
  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
  */
 QUnit.diff = (function() {
+       /*jshint eqeqeq:false, eqnull:true */
        function diff( o, n ) {
                var i,
                        ns = {},
                        os = {};
 
                for ( i = 0; i < n.length; i++ ) {
-                       if ( ns[ n[i] ] == null ) {
+                       if ( !hasOwn.call( ns, n[i] ) ) {
                                ns[ n[i] ] = {
                                        rows: [],
                                        o: null
@@ -1737,7 +2026,7 @@ QUnit.diff = (function() {
                }
 
                for ( i = 0; i < o.length; i++ ) {
-                       if ( os[ o[i] ] == null ) {
+                       if ( !hasOwn.call( os, o[i] ) ) {
                                os[ o[i] ] = {
                                        rows: [],
                                        n: null
@@ -1750,7 +2039,7 @@ QUnit.diff = (function() {
                        if ( !hasOwn.call( ns, i ) ) {
                                continue;
                        }
-                       if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
+                       if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
                                n[ ns[i].rows[0] ] = {
                                        text: n[ ns[i].rows[0] ],
                                        row: os[i].rows[0]
@@ -1856,7 +2145,7 @@ QUnit.diff = (function() {
 
 // for CommonJS enviroments, export everything
 if ( typeof exports !== "undefined" ) {
-       extend(exports, QUnit);
+       extend( exports, QUnit );
 }
 
 // get at whatever the global object is, like window in browsers