]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Upgrade QUnit to 2.3.3
authorJohann-S <johann.servoire@gmail.com>
Wed, 14 Jun 2017 08:29:09 +0000 (10:29 +0200)
committerJohann-S <johann.servoire@gmail.com>
Wed, 14 Jun 2017 09:18:12 +0000 (11:18 +0200)
js/tests/vendor/qunit.css
js/tests/vendor/qunit.js

index 75d8b6279f677548134364f7aa27a3a58817d0ae..7a469353341b4a610fe6ec41ae40b5461854305f 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 2.3.2
+ * QUnit 2.3.3
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2017-04-18T02:19Z
+ * Date: 2017-06-02T14:07Z
  */
 
 /** Font Family and Sizes */
index fa728db5b61e421e05acf4e0e62989fc8ae534e2..3cda99631a9542dd813c6a0a22f61bcfccef4f52 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 2.3.2
+ * QUnit 2.3.3
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2017-04-18T02:19Z
+ * Date: 2017-06-02T14:07Z
  */
 (function (global$1) {
   'use strict';
@@ -14,6 +14,7 @@
   global$1 = 'default' in global$1 ? global$1['default'] : global$1;
 
   var window = global$1.window;
+  var self$1 = global$1.self;
   var console = global$1.console;
   var setTimeout = global$1.setTimeout;
   var clearTimeout = global$1.clearTimeout;
        return objectType(obj) === type;
   }
 
+  // Based on Java's String.hashCode, a simple but not
+  // rigorously collision resistant hashing function
+  function generateHash(module, testName) {
+       var str = module + "\x1C" + testName;
+       var hash = 0;
+
+       for (var i = 0; i < str.length; i++) {
+               hash = (hash << 5) - hash + str.charCodeAt(i);
+               hash |= 0;
+       }
+
+       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+       // strictly necessary but increases user understanding that the id is a SHA-like hash
+       var hex = (0x100000000 + hash).toString(16);
+       if (hex.length < 8) {
+               hex = "0000000" + hex;
+       }
+
+       return hex.slice(-8);
+  }
+
   // Test for equality any JavaScript type.
   // Authors: Philippe Rathé <prathe@gmail.com>, David Chan <david@troi.org>
   var equiv = (function () {
        // Set of all modules.
        modules: [],
 
-       // Stack of nested modules
-       moduleStack: [],
-
        // The first unnamed module
        currentModule: {
                name: "",
                tests: [],
                childModules: [],
-               testsRun: 0
+               testsRun: 0,
+               unskippedTestsRun: 0
        },
 
        callbacks: {},
 
        // The storage module to use for reordering tests
-       storage: sessionStorage
+       storage: localSessionStorage
   };
 
   // take a predefined QUnit.config and extend the defaults
        return extractStacktrace(error, offset);
   }
 
+  var priorityCount = 0;
+  var unitSampler = void 0;
+
+  /**
+   * Advances the ProcessingQueue to the next item if it is ready.
+   * @param {Boolean} last
+   */
+  function advance() {
+       var start = now();
+       config.depth = (config.depth || 0) + 1;
+
+       while (config.queue.length && !config.blocking) {
+               var elapsedTime = now() - start;
+
+               if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
+                       if (priorityCount > 0) {
+                               priorityCount--;
+                       }
+
+                       config.queue.shift()();
+               } else {
+                       setTimeout(advance, 13);
+                       break;
+               }
+       }
+
+       config.depth--;
+
+       if (!config.blocking && !config.queue.length && config.depth === 0) {
+               done();
+       }
+  }
+
+  function addToQueueImmediate(callback) {
+       if (objectType(callback) === "array") {
+               while (callback.length) {
+                       addToQueueImmediate(callback.pop());
+               }
+
+               return;
+       }
+
+       config.queue.unshift(callback);
+       priorityCount++;
+  }
+
+  /**
+   * Adds a function to the ProcessingQueue for execution.
+   * @param {Function|Array} callback
+   * @param {Boolean} priority
+   * @param {String} seed
+   */
+  function addToQueue(callback, prioritize, seed) {
+       if (prioritize) {
+               config.queue.splice(priorityCount++, 0, callback);
+       } else if (seed) {
+               if (!unitSampler) {
+                       unitSampler = unitSamplerGenerator(seed);
+               }
+
+               // Insert into a random position after all prioritized items
+               var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
+               config.queue.splice(priorityCount + index, 0, callback);
+       } else {
+               config.queue.push(callback);
+       }
+  }
+
+  /**
+   * Creates a seeded "sample" generator which is used for randomizing tests.
+   */
+  function unitSamplerGenerator(seed) {
+
+       // 32-bit xorshift, requires only a nonzero seed
+       // http://excamera.com/sphinx/article-xorshift.html
+       var sample = parseInt(generateHash(seed), 16) || -1;
+       return function () {
+               sample ^= sample << 13;
+               sample ^= sample >>> 17;
+               sample ^= sample << 5;
+
+               // ECMAScript has no unsigned number type
+               if (sample < 0) {
+                       sample += 0x100000000;
+               }
+
+               return sample / 0x100000000;
+       };
+  }
+
+  /**
+   * This function is called when the ProcessingQueue is done processing all
+   * items. It handles emitting the final run events.
+   */
+  function done() {
+       var storage = config.storage;
+
+       ProcessingQueue.finished = true;
+
+       var runtime = now() - config.started;
+       var passed = config.stats.all - config.stats.bad;
+
+       emit("runEnd", globalSuite.end(true));
+       runLoggingCallbacks("done", {
+               passed: passed,
+               failed: config.stats.bad,
+               total: config.stats.all,
+               runtime: runtime
+       });
+
+       // Clear own storage items if all tests passed
+       if (storage && config.stats.bad === 0) {
+               for (var i = storage.length - 1; i >= 0; i--) {
+                       var key = storage.key(i);
+
+                       if (key.indexOf("qunit-test-") === 0) {
+                               storage.removeItem(key);
+                       }
+               }
+       }
+  }
+
+  var ProcessingQueue = {
+       finished: false,
+       add: addToQueue,
+       addImmediate: addToQueueImmediate,
+       advance: advance
+  };
+
   var TestReport = function () {
        function TestReport(name, suite, options) {
                classCallCheck(this, TestReport);
                this.skipped = !!options.skip;
                this.todo = !!options.todo;
 
+               this.valid = options.valid;
+
                this._startTime = 0;
                this._endTime = 0;
 
                value: function getAssertions() {
                        return this.assertions.slice();
                }
+
+               // Remove actual and expected values from assertions. This is to prevent
+               // leaking memory throughout a test suite.
+
+       }, {
+               key: "slimAssertions",
+               value: function slimAssertions() {
+                       this.assertions = this.assertions.map(function (assertion) {
+                               delete assertion.actual;
+                               delete assertion.expected;
+                               return assertion;
+                       });
+               }
        }]);
        return TestReport;
   }();
 
-  var unitSampler;
   var focused = false;
-  var priorityCount = 0;
 
   function Test(settings) {
        var i, l;
        extend(this, settings);
        this.assertions = [];
        this.semaphore = 0;
-       this.usedAsync = false;
        this.module = config.currentModule;
        this.stack = sourceFromStacktrace(3);
        this.steps = [];
 
        this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
                todo: settings.todo,
-               skip: settings.skip
+               skip: settings.skip,
+               valid: this.valid()
        });
 
        // Register unique strings
 
        this.module.tests.push({
                name: this.testName,
-               testId: this.testId
+               testId: this.testId,
+               skip: !!settings.skip
        });
 
        if (settings.skip) {
 
                config.current = this;
 
-               if (module.testEnvironment) {
-                       delete module.testEnvironment.before;
-                       delete module.testEnvironment.beforeEach;
-                       delete module.testEnvironment.afterEach;
-                       delete module.testEnvironment.after;
-               }
                this.testEnvironment = extend({}, module.testEnvironment);
 
                this.started = now();
                    test = this;
                return function runHook() {
                        if (hookName === "before") {
-                               if (hookOwner.testsRun !== 0) {
+                               if (hookOwner.unskippedTestsRun !== 0) {
                                        return;
                                }
 
                                test.preserveEnvironment = true;
                        }
 
-                       if (hookName === "after" && hookOwner.testsRun !== numberOfTests(hookOwner) - 1) {
+                       if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) {
                                return;
                        }
 
                        if (module.parentModule) {
                                processHooks(test, module.parentModule);
                        }
-                       if (module.testEnvironment && objectType(module.testEnvironment[handler]) === "function") {
-                               hooks.push(test.queueHook(module.testEnvironment[handler], handler, module));
+                       if (module.hooks && objectType(module.hooks[handler]) === "function") {
+                               hooks.push(test.queueHook(module.hooks[handler], handler, module));
                        }
                }
 
                        }
                }
 
-               notifyTestsRan(module);
+               notifyTestsRan(module, skipped);
 
                // Store result when possible
                if (storage) {
                        }
                }
 
+               // After emitting the js-reporters event we cleanup the assertion data to
+               // avoid leaking it. It is not used by the legacy testDone callbacks.
                emit("testEnd", this.testReport.end(true));
+               this.testReport.slimAssertions();
+
                runLoggingCallbacks("testDone", {
                        name: testName,
                        module: moduleName,
                });
 
                if (module.testsRun === numberOfTests(module)) {
+                       logSuiteEnd(module);
+
+                       // Check if the parent modules, iteratively, are done. If that the case,
+                       // we emit the `suiteEnd` event and trigger `moduleDone` callback.
+                       var parent = module.parentModule;
+                       while (parent && parent.testsRun === numberOfTests(parent)) {
+                               logSuiteEnd(parent);
+                               parent = parent.parentModule;
+                       }
+               }
+
+               config.current = undefined;
+
+               function logSuiteEnd(module) {
                        emit("suiteEnd", module.suiteReport.end(true));
                        runLoggingCallbacks("moduleDone", {
                                name: module.name,
                                runtime: now() - module.stats.started
                        });
                }
-
-               config.current = undefined;
        },
 
        preserveTestEnvironment: function preserveTestEnvironment() {
        },
 
        queue: function queue() {
-               var priority,
-                   previousFailCount,
-                   test = this;
+               var test = this;
 
                if (!this.valid()) {
                        return;
                }
 
-               function run() {
+               function runTest() {
 
                        // Each of these can by async
-                       synchronize([function () {
+                       ProcessingQueue.addImmediate([function () {
                                test.before();
                        }, test.hooks("before"), function () {
                                test.preserveTestEnvironment();
                        }]);
                }
 
-               previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
+               var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
 
                // Prioritize previously failed tests, detected from storage
-               priority = config.reorder && previousFailCount;
+               var prioritize = config.reorder && !!previousFailCount;
 
                this.previousFailure = !!previousFailCount;
 
-               return synchronize(run, priority, config.seed);
+               ProcessingQueue.add(runTest, prioritize, config.seed);
+
+               // If the queue has already finished, we manually process the new test
+               if (ProcessingQueue.finished) {
+                       ProcessingQueue.advance();
+               }
        },
 
+
        pushResult: function pushResult(resultInfo) {
+               if (this !== config.current) {
+                       throw new Error("Assertion occured after test had finished.");
+               }
 
                // Destructure of resultInfo = { result, actual, expected, message, negative }
                var source,
                        throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
                }
 
-               this.assert.pushResult({
+               this.pushResult({
                        result: false,
                        message: message || "error",
                        actual: actual || null,
        return currentTest.pushFailure.apply(currentTest, arguments);
   }
 
-  // Based on Java's String.hashCode, a simple but not
-  // rigorously collision resistant hashing function
-  function generateHash(module, testName) {
-       var hex,
-           i = 0,
-           hash = 0,
-           str = module + "\x1C" + testName,
-           len = str.length;
-
-       for (; i < len; i++) {
-               hash = (hash << 5) - hash + str.charCodeAt(i);
-               hash |= 0;
-       }
-
-       // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
-       // strictly necessary but increases user understanding that the id is a SHA-like hash
-       hex = (0x100000000 + hash).toString(16);
-       if (hex.length < 8) {
-               hex = "0000000" + hex;
-       }
-
-       return hex.slice(-8);
-  }
-
-  function synchronize(callback, priority, seed) {
-       var last = !priority,
-           index;
-
-       if (objectType(callback) === "array") {
-               while (callback.length) {
-                       synchronize(callback.shift());
-               }
-               return;
-       }
-
-       if (priority) {
-               config.queue.splice(priorityCount++, 0, callback);
-       } else if (seed) {
-               if (!unitSampler) {
-                       unitSampler = unitSamplerGenerator(seed);
-               }
-
-               // Insert into a random position after all priority items
-               index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
-               config.queue.splice(priorityCount + index, 0, callback);
-       } else {
-               config.queue.push(callback);
-       }
-
-       if (internalState.autorun && !config.blocking) {
-               process(last);
-       }
-  }
-
-  function unitSamplerGenerator(seed) {
-
-       // 32-bit xorshift, requires only a nonzero seed
-       // http://excamera.com/sphinx/article-xorshift.html
-       var sample = parseInt(generateHash(seed), 16) || -1;
-       return function () {
-               sample ^= sample << 13;
-               sample ^= sample >>> 17;
-               sample ^= sample << 5;
-
-               // ECMAScript has no unsigned number type
-               if (sample < 0) {
-                       sample += 0x100000000;
-               }
-
-               return sample / 0x100000000;
-       };
-  }
-
   function saveGlobal() {
        config.pollution = [];
 
        }
   }
 
-  function numberOfTests(module) {
-       var count = module.tests.length,
-           modules = [].concat(toConsumableArray(module.childModules));
+  function collectTests(module) {
+       var tests = [].concat(module.tests);
+       var modules = [].concat(toConsumableArray(module.childModules));
 
        // Do a breadth-first traversal of the child modules
        while (modules.length) {
                var nextModule = modules.shift();
-               count += nextModule.tests.length;
+               tests.push.apply(tests, nextModule.tests);
                modules.push.apply(modules, toConsumableArray(nextModule.childModules));
        }
 
-       return count;
+       return tests;
+  }
+
+  function numberOfTests(module) {
+       return collectTests(module).length;
+  }
+
+  function numberOfUnskippedTests(module) {
+       return collectTests(module).filter(function (test) {
+               return !test.skip;
+       }).length;
   }
 
-  function notifyTestsRan(module) {
+  function notifyTestsRan(module, skipped) {
        module.testsRun++;
+       if (!skipped) {
+               module.unskippedTestsRun++;
+       }
        while (module = module.parentModule) {
                module.testsRun++;
+               if (!skipped) {
+                       module.unskippedTestsRun++;
+               }
        }
   }
 
        }, {
                key: "async",
                value: function async(count) {
-                       var test$$1 = this.test,
-                           popped = false,
+                       var test$$1 = this.test;
+
+                       var popped = false,
                            acceptCallCount = count;
 
                        if (typeof acceptCallCount === "undefined") {
                                acceptCallCount = 1;
                        }
 
-                       test$$1.usedAsync = true;
                        var resume = internalStop(test$$1);
 
                        return function done() {
+                               if (config.current !== test$$1) {
+                                       throw Error("assert.async callback called after test finished.");
+                               }
+
                                if (popped) {
                                        test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
                                        return;
                value: function pushResult(resultInfo) {
 
                        // Destructure of resultInfo = { result, actual, expected, message, negative }
-                       var assert = this,
-                           currentTest = assert instanceof Assert && assert.test || config.current;
+                       var assert = this;
+                       var currentTest = assert instanceof Assert && assert.test || config.current;
 
                        // Backwards compatibility fix.
                        // Allows the direct use of global exported assertions and QUnit.assert.*
                                throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
                        }
 
-                       if (currentTest.usedAsync === true && currentTest.semaphore === 0) {
-                               currentTest.pushFailure("Assertion after the final `assert.async` was resolved", sourceFromStacktrace(2));
-
-                               // Allow this assertion to continue running anyway...
-                       }
-
                        if (!(assert instanceof Assert)) {
                                assert = currentTest.assert;
                        }
                key: "throws",
                value: function throws(block, expected, message) {
                        var actual = void 0,
-                           result = false,
-                           currentTest = this instanceof Assert && this.test || config.current;
+                           result = false;
+
+                       var currentTest = this instanceof Assert && this.test || config.current;
 
                        // 'expected' is optional unless doing string comparison
                        if (objectType(expected) === "string") {
                });
                QUnit.config.autostart = false;
        }
+
+       // For Web/Service Workers
+       if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
+               self$1.QUnit = QUnit;
+       }
   }
 
   var SuiteReport = function () {
                        var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
 
                        counts = this.tests.reduce(function (counts, test) {
-                               counts[test.getStatus()]++;
-                               counts.total++;
+                               if (test.valid) {
+                                       counts[test.getStatus()]++;
+                                       counts.total++;
+                               }
+
                                return counts;
                        }, counts);
 
   // it since each module has a suiteReport associated with it.
   config.currentModule.suiteReport = globalSuite;
 
+  var moduleStack = [];
   var globalStartCalled = false;
   var runStarted = false;
 
-  var internalState = {
-       autorun: false
-  };
-
   // Figure out if we're running the tests from a server or not
   QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
 
   // Expose the current QUnit version
-  QUnit.version = "2.2.0";
+  QUnit.version = "2.3.3";
+
+  function createModule(name, testEnvironment) {
+       var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
+       var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+       var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
+       var module = {
+               name: moduleName,
+               parentModule: parentModule,
+               tests: [],
+               moduleId: generateHash(moduleName),
+               testsRun: 0,
+               unskippedTestsRun: 0,
+               childModules: [],
+               suiteReport: new SuiteReport(name, parentSuite)
+       };
+
+       var env = {};
+       if (parentModule) {
+               parentModule.childModules.push(module);
+               extend(env, parentModule.testEnvironment);
+       }
+       extend(env, testEnvironment);
+       module.testEnvironment = env;
+
+       config.modules.push(module);
+       return module;
+  }
 
   extend(QUnit, {
        on: on,
 
        // Call on start of module test to prepend name to all tests
        module: function module(name, testEnvironment, executeNow) {
-               var module, moduleFns;
-               var currentModule = config.currentModule;
-
                if (arguments.length === 2) {
                        if (objectType(testEnvironment) === "function") {
                                executeNow = testEnvironment;
                        }
                }
 
-               module = createModule();
+               var module = createModule(name, testEnvironment);
+
+               // Move any hooks to a 'hooks' object
+               if (module.testEnvironment) {
+                       module.hooks = {
+                               before: module.testEnvironment.before,
+                               beforeEach: module.testEnvironment.beforeEach,
+                               afterEach: module.testEnvironment.afterEach,
+                               after: module.testEnvironment.after
+                       };
+
+                       delete module.testEnvironment.before;
+                       delete module.testEnvironment.beforeEach;
+                       delete module.testEnvironment.afterEach;
+                       delete module.testEnvironment.after;
+               }
 
-               moduleFns = {
+               var moduleFns = {
                        before: setHook(module, "before"),
                        beforeEach: setHook(module, "beforeEach"),
                        afterEach: setHook(module, "afterEach"),
                        after: setHook(module, "after")
                };
 
+               var currentModule = config.currentModule;
                if (objectType(executeNow) === "function") {
-                       config.moduleStack.push(module);
-                       setCurrentModule(module);
+                       moduleStack.push(module);
+                       config.currentModule = module;
                        executeNow.call(module.testEnvironment, moduleFns);
-                       config.moduleStack.pop();
+                       moduleStack.pop();
                        module = module.parentModule || currentModule;
                }
 
-               setCurrentModule(module);
-
-               function createModule() {
-                       var parentModule = config.moduleStack.length ? config.moduleStack.slice(-1)[0] : null;
-                       var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
-                       var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
-
-                       var module = {
-                               name: moduleName,
-                               parentModule: parentModule,
-                               tests: [],
-                               moduleId: generateHash(moduleName),
-                               testsRun: 0,
-                               childModules: [],
-                               suiteReport: new SuiteReport(name, parentSuite)
-                       };
-
-                       var env = {};
-                       if (parentModule) {
-                               parentModule.childModules.push(module);
-                               extend(env, parentModule.testEnvironment);
-                               delete env.beforeEach;
-                               delete env.afterEach;
-                       }
-                       extend(env, testEnvironment);
-                       module.testEnvironment = env;
-
-                       config.modules.push(module);
-                       return module;
-               }
-
-               function setCurrentModule(module) {
-                       config.currentModule = module;
-               }
+               config.currentModule = module;
        },
 
        test: test,
        }
 
        config.blocking = false;
-       process(true);
-  }
-
-  function process(last) {
-       function next() {
-               process(last);
-       }
-       var start = now();
-       config.depth = (config.depth || 0) + 1;
-
-       while (config.queue.length && !config.blocking) {
-               if (!defined.setTimeout || config.updateRate <= 0 || now() - start < config.updateRate) {
-                       if (config.current) {
-
-                               // Reset async tracking for each phase of the Test lifecycle
-                               config.current.usedAsync = false;
-                       }
-                       config.queue.shift()();
-               } else {
-                       setTimeout(next, 13);
-                       break;
-               }
-       }
-       config.depth--;
-       if (last && !config.blocking && !config.queue.length && config.depth === 0) {
-               done();
-       }
-  }
-
-  function done() {
-       var runtime,
-           passed,
-           i,
-           key,
-           storage = config.storage;
-
-       internalState.autorun = true;
-
-       runtime = now() - config.started;
-       passed = config.stats.all - config.stats.bad;
-
-       emit("runEnd", globalSuite.end(true));
-       runLoggingCallbacks("done", {
-               failed: config.stats.bad,
-               passed: passed,
-               total: config.stats.all,
-               runtime: runtime
-       });
-
-       // Clear own storage items if all tests passed
-       if (storage && config.stats.bad === 0) {
-               for (i = storage.length - 1; i >= 0; i--) {
-                       key = storage.key(i);
-                       if (key.indexOf("qunit-test-") === 0) {
-                               storage.removeItem(key);
-                       }
-               }
-       }
+       ProcessingQueue.advance();
   }
 
   function setHook(module, hookName) {
-       if (module.testEnvironment === undefined) {
-               module.testEnvironment = {};
+       if (!module.hooks) {
+               module.hooks = {};
        }
 
        return function (callback) {
-               module.testEnvironment[hookName] = callback;
+               module.hooks[hookName] = callback;
        };
   }
 
 
                                message += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText(actual) + "</pre></td></tr>";
 
-                               // Don't show diff if actual or expected are booleans
-                               if (!/^(true|false)$/.test(actual) && !/^(true|false)$/.test(expected)) {
+                               if (typeof details.actual === "number" && typeof details.expected === "number") {
+                                       if (!isNaN(details.actual) && !isNaN(details.expected)) {
+                                               showDiff = true;
+                                               diff = details.actual - details.expected;
+                                               diff = (diff > 0 ? "+" : "") + diff;
+                                       }
+                               } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
                                        diff = QUnit.diff(expected, actual);
+
+                                       // don't show diff if there is zero overlap
                                        showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
                                }
 
-                               // Don't show diff if expected and actual are totally different
                                if (showDiff) {
                                        message += "<tr class='test-diff'><th>Diff: </th><td><pre>" + diff + "</pre></td></tr>";
                                }
                                var todoLabel = document$$1.createElement("em");
                                todoLabel.className = "qunit-todo-label";
                                todoLabel.innerHTML = "todo";
+                               testItem.className += " todo";
                                testItem.insertBefore(todoLabel, testTitle);
                        }