]> source.dussan.org Git - jquery-ui.git/commitdiff
Tests: Updated QUnit.
authorScott González <scott.gonzalez@gmail.com>
Mon, 24 Jan 2011 19:47:03 +0000 (14:47 -0500)
committerScott González <scott.gonzalez@gmail.com>
Mon, 24 Jan 2011 19:47:03 +0000 (14:47 -0500)
external/qunit.css
external/qunit.js

index a4daa27e4244d65dc130201bf0c58bcbe22e3bb9..500d404e33d69395426c4581cb77cabd203d3da1 100644 (file)
@@ -10,7 +10,7 @@
 
 /** Resets */
 
-#qunit-tests, #qunit-tests li ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
        margin: 0;
        padding: 0;
 }
 
 #qunit-header {
        padding: 0.5em 0 0.5em 1em;
-       
-       color: #fff;
-       text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
+
+       color: #8699a4;
        background-color: #0d3349;
+
+       font-size: 1.5em;
+       line-height: 1em;
+       font-weight: normal;
        
        border-radius: 15px 15px 0 0;
        -moz-border-radius: 15px 15px 0 0;
 
 #qunit-header a {
        text-decoration: none;
-       color: white;
+       color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+       color: #fff;
 }
 
 #qunit-banner {
@@ -41,7 +49,9 @@
 }
 
 #qunit-testrunner-toolbar {
-       padding: 0em 0 0.5em 2em;
+       padding: 0.5em 0 0.5em 2em;
+       color: #5E740B;
+       background-color: #eee;
 }
 
 #qunit-userAgent {
@@ -68,7 +78,7 @@
        cursor: pointer;
 }
 
-#qunit-tests li ol {
+#qunit-tests ol {
        margin-top: 0.5em;
        padding: 0.5em;
        
        -webkit-box-shadow: inset 0px 2px 13px #999;
 }
 
+#qunit-tests table {
+       border-collapse: collapse;
+       margin-top: .2em;
+}
+
+#qunit-tests th {
+       text-align: right;
+       vertical-align: top;
+       padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+       vertical-align: top;
+}
+
+#qunit-tests pre {
+       margin: 0;
+       white-space: pre-wrap;
+       word-wrap: break-word;
+}
+
+#qunit-tests del {
+       background-color: #e0f2be;
+       color: #374e0c;
+       text-decoration: none;
+}
+
+#qunit-tests ins {
+       background-color: #ffcaca;
+       color: #500;
+       text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.passed                       { color: #5E740B; }
+#qunit-tests b.failed                       { color: #710909; }
+
 #qunit-tests li li {
        margin: 0.5em;
        padding: 0.4em 0.5em 0.4em 0.5em;
        border-left: 26px solid #C6E746;
 }
 
-#qunit-tests li.pass                        { color: #528CE0; background-color: #D2E0E6; }
-#qunit-tests li.pass span.test-name         { color: #366097; }
+#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name               { color: #366097; }
  
-#qunit-tests li li.pass span.test-actual,
-#qunit-tests li li.pass span.test-expected  { color: #999999; }
-
-strong b.pass                               { color: #5E740B; }
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected           { color: #999999; }
 
 #qunit-banner.qunit-pass                    { background-color: #C6E746; }
 
@@ -117,31 +164,28 @@ strong b.pass                               { color: #5E740B; }
        border-left: 26px solid #EE5757;
 }
 
-#qunit-tests li.fail                        { color: #000000; background-color: #EE5757; }
-#qunit-tests li.fail span.test-name,
-#qunit-tests li.fail span.module-name       { color: #000000; }
-
-#qunit-tests li li.fail span.test-actual    { color: #EE5757; }
-#qunit-tests li li.fail span.test-expected  { color: green;   }
+#qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name             { color: #000000; }
 
-strong b.fail                               { color: #710909; }
+#qunit-tests .fail .test-actual             { color: #EE5757; }
+#qunit-tests .fail .test-expected           { color: green;   }
 
-#qunit-banner.qunit-fail, 
-#qunit-testrunner-toolbar                   { background-color: #EE5757; }
+#qunit-banner.qunit-fail                    { background-color: #EE5757; }
 
 
 /** Footer */
 
 #qunit-testresult {
        padding: 0.5em 0.5em 0.5em 2.5em;
-       
+
        color: #2b81af;
        background-color: #D2E0E6;
 
        border-radius: 0 0 15px 15px;
        -moz-border-radius: 0 0 15px 15px;
        -webkit-border-bottom-right-radius: 15px;
-       -webkit-border-bottom-left-radius: 15px;        
+       -webkit-border-bottom-left-radius: 15px;
 }
 
 /** Fixture */
@@ -150,4 +194,4 @@ strong b.fail                               { color: #710909; }
        position: absolute;
        top: -10000px;
        left: -10000px;
-}
+}
\ No newline at end of file
index 45ad1dcf8fb0e891768de1a40455d5b1262914f6..3d7e7c1f3820ba96f1e79db6b0d260722c1d8389 100644 (file)
  * 
  * http://docs.jquery.com/QUnit
  *
- * Copyright (c) 2009 John Resig, Jörn Zaefferer
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
  * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * or GPL (GPL-LICENSE.txt) licenses.
  */
 
 (function(window) {
 
-var QUnit = {
+var defined = {
+       setTimeout: typeof window.setTimeout !== "undefined",
+       sessionStorage: (function() {
+               try {
+                       return !!sessionStorage.getItem;
+               } catch(e){
+                       return false;
+               }
+  })()
+}
 
-       // call on start of module test to prepend name to all tests
-       module: function(name, testEnvironment) {
-               config.currentModule = name;
+var testId = 0;
 
-               synchronize(function() {
-                       if ( config.currentModule ) {
-                               QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
+       this.name = name;
+       this.testName = testName;
+       this.expected = expected;
+       this.testEnvironmentArg = testEnvironmentArg;
+       this.async = async;
+       this.callback = callback;
+       this.assertions = [];
+};
+Test.prototype = {
+       init: function() {
+               var tests = id("qunit-tests");
+               if (tests) {
+                       var b = document.createElement("strong");
+                               b.innerHTML = "Running " + this.name;
+                       var li = document.createElement("li");
+                               li.appendChild( b );
+                               li.id = this.id = "test-output" + testId++;
+                       tests.appendChild( li );
+               }
+       },
+       setup: function() {
+               if (this.module != config.previousModule) {
+                       if ( config.previousModule ) {
+                               QUnit.moduleDone( {
+                                       name: config.previousModule,
+                                       failed: config.moduleStats.bad,
+                                       passed: config.moduleStats.all - config.moduleStats.bad,
+                                       total: config.moduleStats.all
+                               } );
                        }
-
-                       config.currentModule = name;
-                       config.moduleTestEnvironment = testEnvironment;
+                       config.previousModule = this.module;
                        config.moduleStats = { all: 0, bad: 0 };
+                       QUnit.moduleStart( {
+                               name: this.module
+                       } );
+               }
 
-                       QUnit.moduleStart( name, testEnvironment );
-               });
-       },
-
-       asyncTest: function(testName, expected, callback) {
-               if ( arguments.length === 2 ) {
-                       callback = expected;
-                       expected = 0;
+               config.current = this;
+               this.testEnvironment = extend({
+                       setup: function() {},
+                       teardown: function() {}
+               }, this.moduleTestEnvironment);
+               if (this.testEnvironmentArg) {
+                       extend(this.testEnvironment, this.testEnvironmentArg);
                }
 
-               QUnit.test(testName, expected, callback, true);
-       },
-       
-       test: function(testName, expected, callback, async) {
-               var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
+               QUnit.testStart( {
+                       name: this.testName
+               } );
 
-               if ( arguments.length === 2 ) {
-                       callback = expected;
-                       expected = null;
-               }
-               // is 2nd argument a testEnvironment?
-               if ( expected && typeof expected === 'object') {
-                       testEnvironmentArg =  expected;
-                       expected = null;
-               }
+               // allow utility functions to access the current test environment
+               // TODO why??
+               QUnit.current_testEnvironment = this.testEnvironment;
+               
+               try {
+                       if ( !config.pollution ) {
+                               saveGlobal();
+                       }
 
-               if ( config.currentModule ) {
-                       name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+                       this.testEnvironment.setup.call(this.testEnvironment);
+               } catch(e) {
+                       QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
+               }
+       },
+       run: function() {
+               if ( this.async ) {
+                       QUnit.stop();
                }
 
-               if ( !validTest(config.currentModule + ": " + testName) ) {
+               if ( config.notrycatch ) {
+                       this.callback.call(this.testEnvironment);
                        return;
                }
+               try {
+                       this.callback.call(this.testEnvironment);
+               } catch(e) {
+                       fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
+                       QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
+                       // else next test will carry the responsibility
+                       saveGlobal();
+
+                       // Restart the tests if they're blocking
+                       if ( config.blocking ) {
+                               start();
+                       }
+               }
+       },
+       teardown: function() {
+               try {
+                       checkPollution();
+                       this.testEnvironment.teardown.call(this.testEnvironment);
+               } catch(e) {
+                       QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
+               }
+       },
+       finish: function() {
+               if ( this.expected && this.expected != this.assertions.length ) {
+                       QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
+               }
+               
+               var good = 0, bad = 0,
+                       tests = id("qunit-tests");
 
-               synchronize(function() {
+               config.stats.all += this.assertions.length;
+               config.moduleStats.all += this.assertions.length;
 
-                       testEnvironment = extend({
-                               setup: function() {},
-                               teardown: function() {}
-                       }, config.moduleTestEnvironment);
-                       if (testEnvironmentArg) {
-                               extend(testEnvironment,testEnvironmentArg);
-                       }
+               if ( tests ) {
+                       var ol  = document.createElement("ol");
 
-                       QUnit.testStart( testName, testEnvironment );
+                       for ( var i = 0; i < this.assertions.length; i++ ) {
+                               var assertion = this.assertions[i];
 
-                       // allow utility functions to access the current test environment
-                       QUnit.current_testEnvironment = testEnvironment;
-                       
-                       config.assertions = [];
-                       config.expected = expected;
-                       
-                       var tests = id("qunit-tests");
-                       if (tests) {
-                               var b = document.createElement("strong");
-                                       b.innerHTML = "Running " + name;
                                var li = document.createElement("li");
-                                       li.appendChild( b );
-                                       li.id = "current-test-output";
-                               tests.appendChild( li )
+                               li.className = assertion.result ? "pass" : "fail";
+                               li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
+                               ol.appendChild( li );
+
+                               if ( assertion.result ) {
+                                       good++;
+                               } else {
+                                       bad++;
+                                       config.stats.bad++;
+                                       config.moduleStats.bad++;
+                               }
                        }
 
-                       try {
-                               if ( !config.pollution ) {
-                                       saveGlobal();
-                               }
+                       // store result when possible
+                       defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad);
 
-                               testEnvironment.setup.call(testEnvironment);
-                       } catch(e) {
-                               QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
-                       }
-           });
-       
-           synchronize(function() {
-                       if ( async ) {
-                               QUnit.stop();
+                       if (bad == 0) {
+                               ol.style.display = "none";
                        }
 
-                       try {
-                               callback.call(testEnvironment);
-                       } catch(e) {
-                               fail("Test " + name + " died, exception and test follows", e, callback);
-                               QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
-                               // else next test will carry the responsibility
-                               saveGlobal();
-
-                               // Restart the tests if they're blocking
-                               if ( config.blocking ) {
-                                       start();
+                       var 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>";
+                       
+                       addEvent(b, "click", function() {
+                               var next = b.nextSibling, display = next.style.display;
+                               next.style.display = display === "none" ? "block" : "none";
+                       });
+                       
+                       addEvent(b, "dblclick", function(e) {
+                               var target = e && e.target ? e.target : window.event.srcElement;
+                               if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+                                       target = target.parentNode;
                                }
-                       }
-               });
+                               if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+                                       window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
+                               }
+                       });
 
-               synchronize(function() {
-                       try {
-                               checkPollution();
-                               testEnvironment.teardown.call(testEnvironment);
-                       } catch(e) {
-                               QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
-                       }
-           });
-       
-           synchronize(function() {
-                       try {
-                               QUnit.reset();
-                       } catch(e) {
-                               fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
-                       }
+                       var li = id(this.id);
+                       li.className = bad ? "fail" : "pass";
+                       li.style.display = resultDisplayStyle(!bad);
+                       li.removeChild( li.firstChild );
+                       li.appendChild( b );
+                       li.appendChild( ol );
 
-                       if ( config.expected && config.expected != config.assertions.length ) {
-                               QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
+               } else {
+                       for ( var i = 0; i < this.assertions.length; i++ ) {
+                               if ( !this.assertions[i].result ) {
+                                       bad++;
+                                       config.stats.bad++;
+                                       config.moduleStats.bad++;
+                               }
                        }
+               }
 
-                       var good = 0, bad = 0,
-                               tests = id("qunit-tests");
-
-                       config.stats.all += config.assertions.length;
-                       config.moduleStats.all += config.assertions.length;
-
-                       if ( tests ) {
-                               var ol  = document.createElement("ol");
-
-                               for ( var i = 0; i < config.assertions.length; i++ ) {
-                                       var assertion = config.assertions[i];
+               try {
+                       QUnit.reset();
+               } catch(e) {
+                       fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
+               }
 
-                                       var li = document.createElement("li");
-                                       li.className = assertion.result ? "pass" : "fail";
-                                       li.innerHTML = assertion.message || "(no message)";
-                                       ol.appendChild( li );
+               QUnit.testDone( {
+                       name: this.testName,
+                       failed: bad,
+                       passed: this.assertions.length - bad,
+                       total: this.assertions.length
+               } );
+       },
+       
+       queue: function() {
+               var test = this;
+               synchronize(function() {
+                       test.init();
+               });
+               function run() {
+                       // each of these can by async
+                       synchronize(function() {
+                               test.setup();
+                       });
+                       synchronize(function() {
+                               test.run();
+                       });
+                       synchronize(function() {
+                               test.teardown();
+                       });
+                       synchronize(function() {
+                               test.finish();
+                       });
+               }
+               // defer when previous test run passed, if storage is available
+               var bad = defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName);
+               if (bad) {
+                       run();
+               } else {
+                       synchronize(run);
+               };
+       }
+       
+}
 
-                                       if ( assertion.result ) {
-                                               good++;
-                                       } else {
-                                               bad++;
-                                               config.stats.bad++;
-                                               config.moduleStats.bad++;
-                                       }
-                               }
-                               if (bad == 0) {
-                                       ol.style.display = "none";
-                               }
+var QUnit = {
 
-                               var b = document.createElement("strong");
-                               b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
-                               
-                               addEvent(b, "click", function() {
-                                       var next = b.nextSibling, display = next.style.display;
-                                       next.style.display = display === "none" ? "block" : "none";
-                               });
-                               
-                               addEvent(b, "dblclick", function(e) {
-                                       var target = e && e.target ? e.target : window.event.srcElement;
-                                       if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
-                                               target = target.parentNode;
-                                       }
-                                       if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
-                                               window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
-                                       }
-                               });
+       // call on start of module test to prepend name to all tests
+       module: function(name, testEnvironment) {
+               config.currentModule = name;
+               config.currentModuleTestEnviroment = testEnvironment;
+       },
 
-                               var li = id("current-test-output");
-                               li.id = "";
-                               li.className = bad ? "fail" : "pass";
-                               li.removeChild( li.firstChild );
-                               li.appendChild( b );
-                               li.appendChild( ol );
-
-                               if ( bad ) {
-                                       var toolbar = id("qunit-testrunner-toolbar");
-                                       if ( toolbar ) {
-                                               toolbar.style.display = "block";
-                                               id("qunit-filter-pass").disabled = null;
-                                               id("qunit-filter-missing").disabled = null;
-                                       }
-                               }
+       asyncTest: function(testName, expected, callback) {
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = 0;
+               }
 
-                       } else {
-                               for ( var i = 0; i < config.assertions.length; i++ ) {
-                                       if ( !config.assertions[i].result ) {
-                                               bad++;
-                                               config.stats.bad++;
-                                               config.moduleStats.bad++;
-                                       }
-                               }
-                       }
+               QUnit.test(testName, expected, callback, true);
+       },
+       
+       test: function(testName, expected, callback, async) {
+               var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
 
-                       QUnit.testDone( testName, bad, config.assertions.length );
+               if ( arguments.length === 2 ) {
+                       callback = expected;
+                       expected = null;
+               }
+               // is 2nd argument a testEnvironment?
+               if ( expected && typeof expected === 'object') {
+                       testEnvironmentArg =  expected;
+                       expected = null;
+               }
 
-                       if ( !window.setTimeout && !config.queue.length ) {
-                               done();
-                       }
-               });
+               if ( config.currentModule ) {
+                       name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+               }
 
-               synchronize( done );
+               if ( !validTest(config.currentModule + ": " + testName) ) {
+                       return;
+               }
+               
+               var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
+               test.module = config.currentModule;
+               test.moduleTestEnvironment = config.currentModuleTestEnviroment;
+               test.queue();
        },
        
        /**
         * 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.expected = asserts;
+               config.current.expected = asserts;
        },
 
        /**
@@ -233,11 +293,15 @@ var QUnit = {
         * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
         */
        ok: function(a, msg) {
+               a = !!a;
+               var details = {
+                       result: a,
+                       message: msg
+               };
                msg = escapeHtml(msg);
-               QUnit.log(a, msg);
-
-               config.assertions.push({
-                       result: !!a,
+               QUnit.log(details);
+               config.current.assertions.push({
+                       result: a,
                        message: msg
                });
        },
@@ -255,42 +319,74 @@ var QUnit = {
         * @param String message (optional)
         */
        equal: function(actual, expected, message) {
-               push(expected == actual, actual, expected, message);
+               QUnit.push(expected == actual, actual, expected, message);
        },
 
        notEqual: function(actual, expected, message) {
-               push(expected != actual, actual, expected, message);
+               QUnit.push(expected != actual, actual, expected, message);
        },
        
        deepEqual: function(actual, expected, message) {
-               push(QUnit.equiv(actual, expected), actual, expected, message);
+               QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
        },
 
        notDeepEqual: function(actual, expected, message) {
-               push(!QUnit.equiv(actual, expected), actual, expected, message);
+               QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
        },
 
        strictEqual: function(actual, expected, message) {
-               push(expected === actual, actual, expected, message);
+               QUnit.push(expected === actual, actual, expected, message);
        },
 
        notStrictEqual: function(actual, expected, message) {
-               push(expected !== actual, actual, expected, message);
+               QUnit.push(expected !== actual, actual, expected, message);
        },
 
-       raises: function(fn,  message) {
+       raises: function(block, expected, message) {
+               var actual, ok = false;
+       
+               if (typeof expected === 'string') {
+                       message = expected;
+                       expected = null;
+               }
+       
                try {
-                       fn();
-                       ok( false, message );
+                       block();
+               } catch (e) {
+                       actual = e;
                }
-               catch (e) {
-                       ok( true, message );
+       
+               if (actual) {
+                       // we don't want to validate thrown error
+                       if (!expected) {
+                               ok = true;
+                       // expected is a regexp 
+                       } else if (QUnit.objectType(expected) === "regexp") {
+                               ok = expected.test(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) {
+                               ok = true;
+                       }
                }
+                       
+               QUnit.ok(ok, message);
        },
 
        start: function() {
+               config.semaphore--;
+               if (config.semaphore > 0) {
+                       // don't start until equal number of stop-calls
+                       return;
+               }
+               if (config.semaphore < 0) {
+                       // ignore if start is called more often then stop
+                       config.semaphore = 0;
+               }
                // A slight delay, to avoid any current callbacks
-               if ( window.setTimeout ) {
+               if ( defined.setTimeout ) {
                        window.setTimeout(function() {
                                if ( config.timeout ) {
                                        clearTimeout(config.timeout);
@@ -306,9 +402,11 @@ var QUnit = {
        },
        
        stop: function(timeout) {
+               config.semaphore++;
                config.blocking = true;
 
-               if ( timeout && window.setTimeout ) {
+               if ( timeout && defined.setTimeout ) {
+                       clearTimeout(config.timeout);
                        config.timeout = window.setTimeout(function() {
                                QUnit.ok( false, "Test timed out" );
                                QUnit.start();
@@ -342,6 +440,10 @@ var config = {
                        GETParams.splice( i, 1 );
                        i--;
                        config.noglobals = true;
+               } else if ( GETParams[i] === "notrycatch" ) {
+                       GETParams.splice( i, 1 );
+                       i--;
+                       config.notrycatch = true;
                } else if ( GETParams[i].search('=') > -1 ) {
                        GETParams.splice( i, 1 );
                        i--;
@@ -379,9 +481,9 @@ extend(QUnit, {
                        blocking: false,
                        autostart: true,
                        autorun: false,
-                       assertions: [],
                        filters: [],
-                       queue: []
+                       queue: [],
+                       semaphore: 0
                });
 
                var tests = id("qunit-tests"),
@@ -403,10 +505,17 @@ 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() {
                if ( window.jQuery ) {
-                       jQuery("#main, #qunit-fixture").html( config.fixture );
+                       jQuery( "#main, #qunit-fixture" ).html( config.fixture );
+               } else {
+                       var main = id( 'main' ) || id( 'qunit-fixture' );
+                       if ( main ) {
+                               main.innerHTML = config.fixture;
+                       }
                }
        },
        
@@ -469,14 +578,55 @@ extend(QUnit, {
                return undefined;
        },
        
-       // Logging callbacks
+       push: function(result, actual, expected, message) {
+               var details = {
+                       result: result,
+                       message: message,
+                       actual: actual,
+                       expected: expected
+               };
+               
+               message = escapeHtml(message) || (result ? "okay" : "failed");
+               message = '<span class="test-message">' + message + "</span>";
+               expected = escapeHtml(QUnit.jsDump.parse(expected));
+               actual = escapeHtml(QUnit.jsDump.parse(actual));
+               var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
+               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>';
+               }
+               if (!result) {
+                       var source = sourceFromStacktrace();
+                       if (source) {
+                               details.source = source;
+                               output += '<tr class="test-source"><th>Source: </th><td><pre>' + source +'</pre></td></tr>';
+                       }
+               }
+               output += "</table>";
+               
+               QUnit.log(details);
+               
+               config.current.assertions.push({
+                       result: !!result,
+                       message: output
+               });
+       },
+       
+       // Logging callbacks; all receive a single argument with the listed properties
+       // run test/logs.html for any related changes
        begin: function() {},
-       done: function(failures, total) {},
-       log: function(result, message) {},
-       testStart: function(name, testEnvironment) {},
-       testDone: function(name, failures, total) {},
-       moduleStart: function(name, testEnvironment) {},
-       moduleDone: function(name, failures, total) {}
+       // done: { failed, passed, total, runtime }
+       done: function() {},
+       // log: { result, actual, expected, message }
+       log: function() {},
+       // testStart: { name }
+       testStart: function() {},
+       // testDone: { name, failed, passed, total }
+       testDone: function() {},
+       // moduleStart: { name }
+       moduleStart: function() {},
+       // moduleDone: { name, failed, passed, total }
+       moduleDone: function() {}
 });
 
 if ( typeof document === "undefined" || document.readyState === "complete" ) {
@@ -484,7 +634,7 @@ if ( typeof document === "undefined" || document.readyState === "complete" ) {
 }
 
 addEvent(window, "load", function() {
-       QUnit.begin();
+       QUnit.begin({});
        
        // Initialize the config, saving the execution queue
        var oldconfig = extend({}, config);
@@ -499,17 +649,23 @@ addEvent(window, "load", function() {
        }
        var banner = id("qunit-header");
        if ( banner ) {
-               banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>'; 
+               var paramsIndex = location.href.lastIndexOf(location.search);
+               if ( paramsIndex > -1 ) {
+                       var mainPageLocation = location.href.slice(0, paramsIndex);
+                       if ( mainPageLocation == location.href ) {
+                               banner.innerHTML = '<a href=""> ' + banner.innerHTML + '</a> ';
+                       } else {
+                               var testName = decodeURIComponent(location.search.slice(1));
+                               banner.innerHTML = '<a href="' + mainPageLocation + '">' + banner.innerHTML + '</a> &#8250; <a href="">' + testName + '</a>';
+                       }
+               }
        }
        
        var toolbar = id("qunit-testrunner-toolbar");
        if ( toolbar ) {
-               toolbar.style.display = "none";
-               
                var filter = document.createElement("input");
                filter.type = "checkbox";
                filter.id = "qunit-filter-pass";
-               filter.disabled = true;
                addEvent( filter, "click", function() {
                        var li = document.getElementsByTagName("li");
                        for ( var i = 0; i < li.length; i++ ) {
@@ -517,32 +673,19 @@ addEvent(window, "load", function() {
                                        li[i].style.display = filter.checked ? "none" : "";
                                }
                        }
+                       if ( defined.sessionStorage ) {
+                               sessionStorage.setItem("qunit-filter-passed-tests", filter.checked ? "true" : "");
+                       }
                });
+               if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+                       filter.checked = true;
+               }
                toolbar.appendChild( filter );
 
                var label = document.createElement("label");
                label.setAttribute("for", "qunit-filter-pass");
                label.innerHTML = "Hide passed tests";
                toolbar.appendChild( label );
-
-               var missing = document.createElement("input");
-               missing.type = "checkbox";
-               missing.id = "qunit-filter-missing";
-               missing.disabled = true;
-               addEvent( missing, "click", function() {
-                       var li = document.getElementsByTagName("li");
-                       for ( var i = 0; i < li.length; i++ ) {
-                               if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
-                                       li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
-                               }
-                       }
-               });
-               toolbar.appendChild( missing );
-
-               label = document.createElement("label");
-               label.setAttribute("for", "qunit-filter-missing");
-               label.innerHTML = "Hide missing tests (untested code is broken code)";
-               toolbar.appendChild( label );
        }
 
        var main = id('main') || id('qunit-fixture');
@@ -556,35 +699,34 @@ addEvent(window, "load", function() {
 });
 
 function done() {
-       if ( config.doneTimer && window.clearTimeout ) {
-               window.clearTimeout( config.doneTimer );
-               config.doneTimer = null;
-       }
-
-       if ( config.queue.length ) {
-               config.doneTimer = window.setTimeout(function(){
-                       if ( !config.queue.length ) {
-                               done();
-                       } else {
-                               synchronize( done );
-                       }
-               }, 13);
-
-               return;
-       }
-
        config.autorun = true;
 
        // Log the last module results
        if ( config.currentModule ) {
-               QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+               QUnit.moduleDone( {
+                       name: config.currentModule,
+                       failed: config.moduleStats.bad,
+                       passed: config.moduleStats.all - config.moduleStats.bad,
+                       total: config.moduleStats.all
+               } );
        }
 
        var banner = id("qunit-banner"),
                tests = id("qunit-tests"),
-               html = ['Tests completed in ',
-               +new Date - config.started, ' milliseconds.<br/>',
-               '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
+               runtime = +new Date - config.started,
+               passed = config.stats.all - config.stats.bad,
+               html = [
+                       'Tests completed in ',
+                       runtime,
+                       ' milliseconds.<br/>',
+                       '<span class="passed">',
+                       passed,
+                       '</span> tests of <span class="total">',
+                       config.stats.all,
+                       '</span> passed, <span class="failed">',
+                       config.stats.bad,
+                       '</span> failed.'
+               ].join('');
 
        if ( banner ) {
                banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
@@ -603,7 +745,12 @@ function done() {
                result.innerHTML = html;
        }
 
-       QUnit.done( config.stats.bad, config.stats.all );
+       QUnit.done( {
+               failed: config.stats.bad,
+               passed: passed, 
+               total: config.stats.all,
+               runtime: runtime
+       } );
 }
 
 function validTest( name ) {
@@ -634,8 +781,31 @@ function validTest( name ) {
        return run;
 }
 
+// so far supports only Firefox, Chrome and Opera (buggy)
+// could be extended in the future to use something like https://github.com/csnover/TraceKit
+function sourceFromStacktrace() {
+       try {
+               throw new Error();
+       } catch ( e ) {
+               if (e.stacktrace) {
+                       // Opera
+                       return e.stacktrace.split("\n")[6];
+               } else if (e.stack) {
+                       // Firefox, Chrome
+                       return e.stack.split("\n")[4];
+               }
+       }
+}
+
+function resultDisplayStyle(passed) {
+       return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : '';
+}
+
 function escapeHtml(s) {
-       s = s === null ? "" : s + "";
+       if (!s) {
+               return "";
+       }
+       s = s + "";
        return s.replace(/[\&"<>\\]/g, function(s) {
                switch(s) {
                        case "&": return "&amp;";
@@ -648,24 +818,6 @@ function escapeHtml(s) {
        });
 }
 
-function push(result, actual, expected, message) {
-       message = escapeHtml(message) || (result ? "okay" : "failed");
-       message = '<span class="test-message">' + message + "</span>";
-       expected = escapeHtml(QUnit.jsDump.parse(expected));
-       actual = escapeHtml(QUnit.jsDump.parse(actual));
-       var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
-       if (actual != expected) {
-               output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
-       }
-       
-       // can't use ok, as that would double-escape messages
-       QUnit.log(result, output);
-       config.assertions.push({
-               result: !!result,
-               message: output
-       });
-}
-
 function synchronize( callback ) {
        config.queue.push( callback );
 
@@ -680,12 +832,14 @@ function process() {
        while ( config.queue.length && !config.blocking ) {
                if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
                        config.queue.shift()();
-
                } else {
-                       setTimeout( process, 13 );
+                       window.setTimeout( process, 13 );
                        break;
                }
        }
+  if (!config.blocking && !config.queue.length) {
+    done();
+  }
 }
 
 function saveGlobal() {
@@ -705,13 +859,13 @@ function checkPollution( name ) {
        var newGlobals = diff( old, config.pollution );
        if ( newGlobals.length > 0 ) {
                ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
-               config.expected++;
+               config.current.expected++;
        }
 
        var deletedGlobals = diff( config.pollution, old );
        if ( deletedGlobals.length > 0 ) {
                ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
-               config.expected++;
+               config.current.expected++;
        }
 }
 
@@ -988,7 +1142,7 @@ QUnit.jsDump = (function() {
                                type = "date";
                        } else if (QUnit.is("Function", obj)) {
                                type = "function";
-                       } else if (obj.setInterval && obj.document && !obj.nodeType) {
+                       } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
                                type = "window";
                        } else if (obj.nodeType === 9) {
                                type = "document";
@@ -1042,31 +1196,31 @@ QUnit.jsDump = (function() {
                                        ret += ' ' + name;
                                ret += '(';
                                
-                               ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
-                               return join( ret, this.parse(fn,'functionCode'), '}' );
+                               ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
+                               return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
                        },
                        array: array,
                        nodelist: array,
                        arguments: array,
                        object:function( map ) {
                                var ret = [ ];
-                               this.up();
+                               QUnit.jsDump.up();
                                for ( var key in map )
-                                       ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
-                               this.down();
+                                       ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
+                               QUnit.jsDump.down();
                                return join( '{', ret, '}' );
                        },
                        node:function( node ) {
-                               var open = this.HTML ? '&lt;' : '<',
-                                       close = this.HTML ? '&gt;' : '>';
+                               var open = QUnit.jsDump.HTML ? '&lt;' : '<',
+                                       close = QUnit.jsDump.HTML ? '&gt;' : '>';
                                        
                                var tag = node.nodeName.toLowerCase(),
                                        ret = open + tag;
                                        
-                               for ( var a in this.DOMAttrs ) {
-                                       var val = node[this.DOMAttrs[a]];
+                               for ( var a in QUnit.jsDump.DOMAttrs ) {
+                                       var val = node[QUnit.jsDump.DOMAttrs[a]];
                                        if ( val )
-                                               ret += ' ' + a + '=' + this.parse( val, 'attribute' );
+                                               ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
                                }
                                return ret + close + open + '/' + tag + close;
                        },
@@ -1094,8 +1248,8 @@ QUnit.jsDump = (function() {
                        'class':'className'
                },
                HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
-               indentChar:'   ',//indentation unit
-               multiline:false //if true, items in a collection, are separated by a \n, else just a space.
+               indentChar:'  ',//indentation unit
+               multiline:true //if true, items in a collection, are separated by a \n, else just a space.
        };
 
        return jsDump;
@@ -1255,7 +1409,7 @@ QUnit.diff = (function() {
                }
                
                return str;
-       }
+       };
 })();
 
-})(this);
+})(this);
\ No newline at end of file