Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Unit testing with QUnit, DOCTYPE on index page.

  • Loading branch information...
commit dbbfe73fbbfd82cefbc1acd52fa92cf73c8032dd 1 parent 8c7e1ab
George Paterson authored
225 external/qunit.css
... ... @@ -0,0 +1,225 @@
  1 +/**
  2 + * QUnit - A JavaScript Unit Testing Framework
  3 + *
  4 + * http://docs.jquery.com/QUnit
  5 + *
  6 + * Copyright (c) 2011 John Resig, Jörn Zaefferer
  7 + * Dual licensed under the MIT (MIT-LICENSE.txt)
  8 + * or GPL (GPL-LICENSE.txt) licenses.
  9 + */
  10 +
  11 +/** Font Family and Sizes */
  12 +
  13 +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
  14 + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
  15 +}
  16 +
  17 +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
  18 +#qunit-tests { font-size: smaller; }
  19 +
  20 +
  21 +/** Resets */
  22 +
  23 +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
  24 + margin: 0;
  25 + padding: 0;
  26 +}
  27 +
  28 +
  29 +/** Header */
  30 +
  31 +#qunit-header {
  32 + padding: 0.5em 0 0.5em 1em;
  33 +
  34 + color: #8699a4;
  35 + background-color: #0d3349;
  36 +
  37 + font-size: 1.5em;
  38 + line-height: 1em;
  39 + font-weight: normal;
  40 +
  41 + border-radius: 15px 15px 0 0;
  42 + -moz-border-radius: 15px 15px 0 0;
  43 + -webkit-border-top-right-radius: 15px;
  44 + -webkit-border-top-left-radius: 15px;
  45 +}
  46 +
  47 +#qunit-header a {
  48 + text-decoration: none;
  49 + color: #c2ccd1;
  50 +}
  51 +
  52 +#qunit-header a:hover,
  53 +#qunit-header a:focus {
  54 + color: #fff;
  55 +}
  56 +
  57 +#qunit-banner {
  58 + height: 5px;
  59 +}
  60 +
  61 +#qunit-testrunner-toolbar {
  62 + padding: 0.5em 0 0.5em 2em;
  63 + color: #5E740B;
  64 + background-color: #eee;
  65 +}
  66 +
  67 +#qunit-userAgent {
  68 + padding: 0.5em 0 0.5em 2.5em;
  69 + background-color: #2b81af;
  70 + color: #fff;
  71 + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
  72 +}
  73 +
  74 +
  75 +/** Tests: Pass/Fail */
  76 +
  77 +#qunit-tests {
  78 + list-style-position: inside;
  79 +}
  80 +
  81 +#qunit-tests li {
  82 + padding: 0.4em 0.5em 0.4em 2.5em;
  83 + border-bottom: 1px solid #fff;
  84 + list-style-position: inside;
  85 +}
  86 +
  87 +#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
  88 + display: none;
  89 +}
  90 +
  91 +#qunit-tests li strong {
  92 + cursor: pointer;
  93 +}
  94 +
  95 +#qunit-tests li a {
  96 + padding: 0.5em;
  97 + color: #c2ccd1;
  98 + text-decoration: none;
  99 +}
  100 +#qunit-tests li a:hover,
  101 +#qunit-tests li a:focus {
  102 + color: #000;
  103 +}
  104 +
  105 +#qunit-tests ol {
  106 + margin-top: 0.5em;
  107 + padding: 0.5em;
  108 +
  109 + background-color: #fff;
  110 +
  111 + border-radius: 15px;
  112 + -moz-border-radius: 15px;
  113 + -webkit-border-radius: 15px;
  114 +
  115 + box-shadow: inset 0px 2px 13px #999;
  116 + -moz-box-shadow: inset 0px 2px 13px #999;
  117 + -webkit-box-shadow: inset 0px 2px 13px #999;
  118 +}
  119 +
  120 +#qunit-tests table {
  121 + border-collapse: collapse;
  122 + margin-top: .2em;
  123 +}
  124 +
  125 +#qunit-tests th {
  126 + text-align: right;
  127 + vertical-align: top;
  128 + padding: 0 .5em 0 0;
  129 +}
  130 +
  131 +#qunit-tests td {
  132 + vertical-align: top;
  133 +}
  134 +
  135 +#qunit-tests pre {
  136 + margin: 0;
  137 + white-space: pre-wrap;
  138 + word-wrap: break-word;
  139 +}
  140 +
  141 +#qunit-tests del {
  142 + background-color: #e0f2be;
  143 + color: #374e0c;
  144 + text-decoration: none;
  145 +}
  146 +
  147 +#qunit-tests ins {
  148 + background-color: #ffcaca;
  149 + color: #500;
  150 + text-decoration: none;
  151 +}
  152 +
  153 +/*** Test Counts */
  154 +
  155 +#qunit-tests b.counts { color: black; }
  156 +#qunit-tests b.passed { color: #5E740B; }
  157 +#qunit-tests b.failed { color: #710909; }
  158 +
  159 +#qunit-tests li li {
  160 + margin: 0.5em;
  161 + padding: 0.4em 0.5em 0.4em 0.5em;
  162 + background-color: #fff;
  163 + border-bottom: none;
  164 + list-style-position: inside;
  165 +}
  166 +
  167 +/*** Passing Styles */
  168 +
  169 +#qunit-tests li li.pass {
  170 + color: #5E740B;
  171 + background-color: #fff;
  172 + border-left: 26px solid #C6E746;
  173 +}
  174 +
  175 +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
  176 +#qunit-tests .pass .test-name { color: #366097; }
  177 +
  178 +#qunit-tests .pass .test-actual,
  179 +#qunit-tests .pass .test-expected { color: #999999; }
  180 +
  181 +#qunit-banner.qunit-pass { background-color: #C6E746; }
  182 +
  183 +/*** Failing Styles */
  184 +
  185 +#qunit-tests li li.fail {
  186 + color: #710909;
  187 + background-color: #fff;
  188 + border-left: 26px solid #EE5757;
  189 +}
  190 +
  191 +#qunit-tests > li:last-child {
  192 + border-radius: 0 0 15px 15px;
  193 + -moz-border-radius: 0 0 15px 15px;
  194 + -webkit-border-bottom-right-radius: 15px;
  195 + -webkit-border-bottom-left-radius: 15px;
  196 +}
  197 +
  198 +#qunit-tests .fail { color: #000000; background-color: #EE5757; }
  199 +#qunit-tests .fail .test-name,
  200 +#qunit-tests .fail .module-name { color: #000000; }
  201 +
  202 +#qunit-tests .fail .test-actual { color: #EE5757; }
  203 +#qunit-tests .fail .test-expected { color: green; }
  204 +
  205 +#qunit-banner.qunit-fail { background-color: #EE5757; }
  206 +
  207 +
  208 +/** Result */
  209 +
  210 +#qunit-testresult {
  211 + padding: 0.5em 0.5em 0.5em 2.5em;
  212 +
  213 + color: #2b81af;
  214 + background-color: #D2E0E6;
  215 +
  216 + border-bottom: 1px solid white;
  217 +}
  218 +
  219 +/** Fixture */
  220 +
  221 +#qunit-fixture {
  222 + position: absolute;
  223 + top: -10000px;
  224 + left: -10000px;
  225 +}
1,452 external/qunit.js
... ... @@ -0,0 +1,1452 @@
  1 +/**
  2 + * QUnit - A JavaScript Unit Testing Framework
  3 + *
  4 + * http://docs.jquery.com/QUnit
  5 + *
  6 + * Copyright (c) 2011 John Resig, Jörn Zaefferer
  7 + * Dual licensed under the MIT (MIT-LICENSE.txt)
  8 + * or GPL (GPL-LICENSE.txt) licenses.
  9 + */
  10 +
  11 +(function(window) {
  12 +
  13 +var defined = {
  14 + setTimeout: typeof window.setTimeout !== "undefined",
  15 + sessionStorage: (function() {
  16 + try {
  17 + return !!sessionStorage.getItem;
  18 + } catch(e){
  19 + return false;
  20 + }
  21 + })()
  22 +};
  23 +
  24 +var testId = 0;
  25 +
  26 +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
  27 + this.name = name;
  28 + this.testName = testName;
  29 + this.expected = expected;
  30 + this.testEnvironmentArg = testEnvironmentArg;
  31 + this.async = async;
  32 + this.callback = callback;
  33 + this.assertions = [];
  34 +};
  35 +Test.prototype = {
  36 + init: function() {
  37 + var tests = id("qunit-tests");
  38 + if (tests) {
  39 + var b = document.createElement("strong");
  40 + b.innerHTML = "Running " + this.name;
  41 + var li = document.createElement("li");
  42 + li.appendChild( b );
  43 + li.className = "running";
  44 + li.id = this.id = "test-output" + testId++;
  45 + tests.appendChild( li );
  46 + }
  47 + },
  48 + setup: function() {
  49 + if (this.module != config.previousModule) {
  50 + if ( config.previousModule ) {
  51 + QUnit.moduleDone( {
  52 + name: config.previousModule,
  53 + failed: config.moduleStats.bad,
  54 + passed: config.moduleStats.all - config.moduleStats.bad,
  55 + total: config.moduleStats.all
  56 + } );
  57 + }
  58 + config.previousModule = this.module;
  59 + config.moduleStats = { all: 0, bad: 0 };
  60 + QUnit.moduleStart( {
  61 + name: this.module
  62 + } );
  63 + }
  64 +
  65 + config.current = this;
  66 + this.testEnvironment = extend({
  67 + setup: function() {},
  68 + teardown: function() {}
  69 + }, this.moduleTestEnvironment);
  70 + if (this.testEnvironmentArg) {
  71 + extend(this.testEnvironment, this.testEnvironmentArg);
  72 + }
  73 +
  74 + QUnit.testStart( {
  75 + name: this.testName
  76 + } );
  77 +
  78 + // allow utility functions to access the current test environment
  79 + // TODO why??
  80 + QUnit.current_testEnvironment = this.testEnvironment;
  81 +
  82 + try {
  83 + if ( !config.pollution ) {
  84 + saveGlobal();
  85 + }
  86 +
  87 + this.testEnvironment.setup.call(this.testEnvironment);
  88 + } catch(e) {
  89 + QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
  90 + }
  91 + },
  92 + run: function() {
  93 + if ( this.async ) {
  94 + QUnit.stop();
  95 + }
  96 +
  97 + if ( config.notrycatch ) {
  98 + this.callback.call(this.testEnvironment);
  99 + return;
  100 + }
  101 + try {
  102 + this.callback.call(this.testEnvironment);
  103 + } catch(e) {
  104 + fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
  105 + QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
  106 + // else next test will carry the responsibility
  107 + saveGlobal();
  108 +
  109 + // Restart the tests if they're blocking
  110 + if ( config.blocking ) {
  111 + start();
  112 + }
  113 + }
  114 + },
  115 + teardown: function() {
  116 + try {
  117 + this.testEnvironment.teardown.call(this.testEnvironment);
  118 + checkPollution();
  119 + } catch(e) {
  120 + QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
  121 + }
  122 + },
  123 + finish: function() {
  124 + if ( this.expected && this.expected != this.assertions.length ) {
  125 + QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
  126 + }
  127 +
  128 + var good = 0, bad = 0,
  129 + tests = id("qunit-tests");
  130 +
  131 + config.stats.all += this.assertions.length;
  132 + config.moduleStats.all += this.assertions.length;
  133 +
  134 + if ( tests ) {
  135 + var ol = document.createElement("ol");
  136 +
  137 + for ( var i = 0; i < this.assertions.length; i++ ) {
  138 + var assertion = this.assertions[i];
  139 +
  140 + var li = document.createElement("li");
  141 + li.className = assertion.result ? "pass" : "fail";
  142 + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
  143 + ol.appendChild( li );
  144 +
  145 + if ( assertion.result ) {
  146 + good++;
  147 + } else {
  148 + bad++;
  149 + config.stats.bad++;
  150 + config.moduleStats.bad++;
  151 + }
  152 + }
  153 +
  154 + // store result when possible
  155 + if ( QUnit.config.reorder && defined.sessionStorage ) {
  156 + if (bad) {
  157 + sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
  158 + } else {
  159 + sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
  160 + }
  161 + }
  162 +
  163 + if (bad == 0) {
  164 + ol.style.display = "none";
  165 + }
  166 +
  167 + var b = document.createElement("strong");
  168 + b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
  169 +
  170 + var a = document.createElement("a");
  171 + a.innerHTML = "Rerun";
  172 + a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
  173 +
  174 + addEvent(b, "click", function() {
  175 + var next = b.nextSibling.nextSibling,
  176 + display = next.style.display;
  177 + next.style.display = display === "none" ? "block" : "none";
  178 + });
  179 +
  180 + addEvent(b, "dblclick", function(e) {
  181 + var target = e && e.target ? e.target : window.event.srcElement;
  182 + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
  183 + target = target.parentNode;
  184 + }
  185 + if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
  186 + window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
  187 + }
  188 + });
  189 +
  190 + var li = id(this.id);
  191 + li.className = bad ? "fail" : "pass";
  192 + li.removeChild( li.firstChild );
  193 + li.appendChild( b );
  194 + li.appendChild( a );
  195 + li.appendChild( ol );
  196 +
  197 + } else {
  198 + for ( var i = 0; i < this.assertions.length; i++ ) {
  199 + if ( !this.assertions[i].result ) {
  200 + bad++;
  201 + config.stats.bad++;
  202 + config.moduleStats.bad++;
  203 + }
  204 + }
  205 + }
  206 +
  207 + try {
  208 + QUnit.reset();
  209 + } catch(e) {
  210 + fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
  211 + }
  212 +
  213 + QUnit.testDone( {
  214 + name: this.testName,
  215 + failed: bad,
  216 + passed: this.assertions.length - bad,
  217 + total: this.assertions.length
  218 + } );
  219 + },
  220 +
  221 + queue: function() {
  222 + var test = this;
  223 + synchronize(function() {
  224 + test.init();
  225 + });
  226 + function run() {
  227 + // each of these can by async
  228 + synchronize(function() {
  229 + test.setup();
  230 + });
  231 + synchronize(function() {
  232 + test.run();
  233 + });
  234 + synchronize(function() {
  235 + test.teardown();
  236 + });
  237 + synchronize(function() {
  238 + test.finish();
  239 + });
  240 + }
  241 + // defer when previous test run passed, if storage is available
  242 + var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
  243 + if (bad) {
  244 + run();
  245 + } else {
  246 + synchronize(run);
  247 + };
  248 + }
  249 +
  250 +};
  251 +
  252 +var QUnit = {
  253 +
  254 + // call on start of module test to prepend name to all tests
  255 + module: function(name, testEnvironment) {
  256 + config.currentModule = name;
  257 + config.currentModuleTestEnviroment = testEnvironment;
  258 + },
  259 +
  260 + asyncTest: function(testName, expected, callback) {
  261 + if ( arguments.length === 2 ) {
  262 + callback = expected;
  263 + expected = 0;
  264 + }
  265 +
  266 + QUnit.test(testName, expected, callback, true);
  267 + },
  268 +
  269 + test: function(testName, expected, callback, async) {
  270 + var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
  271 +
  272 + if ( arguments.length === 2 ) {
  273 + callback = expected;
  274 + expected = null;
  275 + }
  276 + // is 2nd argument a testEnvironment?
  277 + if ( expected && typeof expected === 'object') {
  278 + testEnvironmentArg = expected;
  279 + expected = null;
  280 + }
  281 +
  282 + if ( config.currentModule ) {
  283 + name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
  284 + }
  285 +
  286 + if ( !validTest(config.currentModule + ": " + testName) ) {
  287 + return;
  288 + }
  289 +
  290 + var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
  291 + test.module = config.currentModule;
  292 + test.moduleTestEnvironment = config.currentModuleTestEnviroment;
  293 + test.queue();
  294 + },
  295 +
  296 + /**
  297 + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
  298 + */
  299 + expect: function(asserts) {
  300 + config.current.expected = asserts;
  301 + },
  302 +
  303 + /**
  304 + * Asserts true.
  305 + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
  306 + */
  307 + ok: function(a, msg) {
  308 + a = !!a;
  309 + var details = {
  310 + result: a,
  311 + message: msg
  312 + };
  313 + msg = escapeHtml(msg);
  314 + QUnit.log(details);
  315 + config.current.assertions.push({
  316 + result: a,
  317 + message: msg
  318 + });
  319 + },
  320 +
  321 + /**
  322 + * Checks that the first two arguments are equal, with an optional message.
  323 + * Prints out both actual and expected values.
  324 + *
  325 + * Prefered to ok( actual == expected, message )
  326 + *
  327 + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
  328 + *
  329 + * @param Object actual
  330 + * @param Object expected
  331 + * @param String message (optional)
  332 + */
  333 + equal: function(actual, expected, message) {
  334 + QUnit.push(expected == actual, actual, expected, message);
  335 + },
  336 +
  337 + notEqual: function(actual, expected, message) {
  338 + QUnit.push(expected != actual, actual, expected, message);
  339 + },
  340 +
  341 + deepEqual: function(actual, expected, message) {
  342 + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
  343 + },
  344 +
  345 + notDeepEqual: function(actual, expected, message) {
  346 + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
  347 + },
  348 +
  349 + strictEqual: function(actual, expected, message) {
  350 + QUnit.push(expected === actual, actual, expected, message);
  351 + },
  352 +
  353 + notStrictEqual: function(actual, expected, message) {
  354 + QUnit.push(expected !== actual, actual, expected, message);
  355 + },
  356 +
  357 + raises: function(block, expected, message) {
  358 + var actual, ok = false;
  359 +
  360 + if (typeof expected === 'string') {
  361 + message = expected;
  362 + expected = null;
  363 + }
  364 +
  365 + try {
  366 + block();
  367 + } catch (e) {
  368 + actual = e;
  369 + }
  370 +
  371 + if (actual) {
  372 + // we don't want to validate thrown error
  373 + if (!expected) {
  374 + ok = true;
  375 + // expected is a regexp
  376 + } else if (QUnit.objectType(expected) === "regexp") {
  377 + ok = expected.test(actual);
  378 + // expected is a constructor
  379 + } else if (actual instanceof expected) {
  380 + ok = true;
  381 + // expected is a validation function which returns true is validation passed
  382 + } else if (expected.call({}, actual) === true) {
  383 + ok = true;
  384 + }
  385 + }
  386 +
  387 + QUnit.ok(ok, message);
  388 + },
  389 +
  390 + start: function() {
  391 + config.semaphore--;
  392 + if (config.semaphore > 0) {
  393 + // don't start until equal number of stop-calls
  394 + return;
  395 + }
  396 + if (config.semaphore < 0) {
  397 + // ignore if start is called more often then stop
  398 + config.semaphore = 0;
  399 + }
  400 + // A slight delay, to avoid any current callbacks
  401 + if ( defined.setTimeout ) {
  402 + window.setTimeout(function() {
  403 + if ( config.timeout ) {
  404 + clearTimeout(config.timeout);
  405 + }
  406 +
  407 + config.blocking = false;
  408 + process();
  409 + }, 13);
  410 + } else {
  411 + config.blocking = false;
  412 + process();
  413 + }
  414 + },
  415 +
  416 + stop: function(timeout) {
  417 + config.semaphore++;
  418 + config.blocking = true;
  419 +
  420 + if ( timeout && defined.setTimeout ) {
  421 + clearTimeout(config.timeout);
  422 + config.timeout = window.setTimeout(function() {
  423 + QUnit.ok( false, "Test timed out" );
  424 + QUnit.start();
  425 + }, timeout);
  426 + }
  427 + }
  428 +};
  429 +
  430 +// Backwards compatibility, deprecated
  431 +QUnit.equals = QUnit.equal;
  432 +QUnit.same = QUnit.deepEqual;
  433 +
  434 +// Maintain internal state
  435 +var config = {
  436 + // The queue of tests to run
  437 + queue: [],
  438 +
  439 + // block until document ready
  440 + blocking: true,
  441 +
  442 + // by default, run previously failed tests first
  443 + // very useful in combination with "Hide passed tests" checked
  444 + reorder: true,
  445 +
  446 + noglobals: false,
  447 + notrycatch: false
  448 +};
  449 +
  450 +// Load paramaters
  451 +(function() {
  452 + var location = window.location || { search: "", protocol: "file:" },
  453 + params = location.search.slice( 1 ).split( "&" ),
  454 + length = params.length,
  455 + urlParams = {},
  456 + current;
  457 +
  458 + if ( params[ 0 ] ) {
  459 + for ( var i = 0; i < length; i++ ) {
  460 + current = params[ i ].split( "=" );
  461 + current[ 0 ] = decodeURIComponent( current[ 0 ] );
  462 + // allow just a key to turn on a flag, e.g., test.html?noglobals
  463 + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
  464 + urlParams[ current[ 0 ] ] = current[ 1 ];
  465 + if ( current[ 0 ] in config ) {
  466 + config[ current[ 0 ] ] = current[ 1 ];
  467 + }
  468 + }
  469 + }
  470 +
  471 + QUnit.urlParams = urlParams;
  472 + config.filter = urlParams.filter;
  473 +
  474 + // Figure out if we're running the tests from a server or not
  475 + QUnit.isLocal = !!(location.protocol === 'file:');
  476 +})();
  477 +
  478 +// Expose the API as global variables, unless an 'exports'
  479 +// object exists, in that case we assume we're in CommonJS
  480 +if ( typeof exports === "undefined" || typeof require === "undefined" ) {
  481 + extend(window, QUnit);
  482 + window.QUnit = QUnit;
  483 +} else {
  484 + extend(exports, QUnit);
  485 + exports.QUnit = QUnit;
  486 +}
  487 +
  488 +// define these after exposing globals to keep them in these QUnit namespace only
  489 +extend(QUnit, {
  490 + config: config,
  491 +
  492 + // Initialize the configuration options
  493 + init: function() {
  494 + extend(config, {
  495 + stats: { all: 0, bad: 0 },
  496 + moduleStats: { all: 0, bad: 0 },
  497 + started: +new Date,
  498 + updateRate: 1000,
  499 + blocking: false,
  500 + autostart: true,
  501 + autorun: false,
  502 + filter: "",
  503 + queue: [],
  504 + semaphore: 0
  505 + });
  506 +
  507 + var tests = id( "qunit-tests" ),
  508 + banner = id( "qunit-banner" ),
  509 + result = id( "qunit-testresult" );
  510 +
  511 + if ( tests ) {
  512 + tests.innerHTML = "";
  513 + }
  514 +
  515 + if ( banner ) {
  516 + banner.className = "";
  517 + }
  518 +
  519 + if ( result ) {
  520 + result.parentNode.removeChild( result );
  521 + }
  522 +
  523 + if ( tests ) {
  524 + result = document.createElement( "p" );
  525 + result.id = "qunit-testresult";
  526 + result.className = "result";
  527 + tests.parentNode.insertBefore( result, tests );
  528 + result.innerHTML = 'Running...<br/>&nbsp;';
  529 + }
  530 + },
  531 +
  532 + /**
  533 + * Resets the test setup. Useful for tests that modify the DOM.
  534 + *
  535 + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
  536 + */
  537 + reset: function() {
  538 + if ( window.jQuery ) {
  539 + jQuery( "#qunit-fixture" ).html( config.fixture );
  540 + } else {
  541 + var main = id( 'qunit-fixture' );
  542 + if ( main ) {
  543 + main.innerHTML = config.fixture;
  544 + }
  545 + }
  546 + },
  547 +
  548 + /**
  549 + * Trigger an event on an element.
  550 + *
  551 + * @example triggerEvent( document.body, "click" );
  552 + *
  553 + * @param DOMElement elem
  554 + * @param String type
  555 + */
  556 + triggerEvent: function( elem, type, event ) {
  557 + if ( document.createEvent ) {
  558 + event = document.createEvent("MouseEvents");
  559 + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
  560 + 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  561 + elem.dispatchEvent( event );
  562 +
  563 + } else if ( elem.fireEvent ) {
  564 + elem.fireEvent("on"+type);
  565 + }
  566 + },
  567 +
  568 + // Safe object type checking
  569 + is: function( type, obj ) {
  570 + return QUnit.objectType( obj ) == type;
  571 + },
  572 +
  573 + objectType: function( obj ) {
  574 + if (typeof obj === "undefined") {
  575 + return "undefined";
  576 +
  577 + // consider: typeof null === object
  578 + }
  579 + if (obj === null) {
  580 + return "null";
  581 + }
  582 +
  583 + var type = Object.prototype.toString.call( obj )
  584 + .match(/^\[object\s(.*)\]$/)[1] || '';
  585 +
  586 + switch (type) {
  587 + case 'Number':
  588 + if (isNaN(obj)) {
  589 + return "nan";
  590 + } else {
  591 + return "number";
  592 + }
  593 + case 'String':
  594 + case 'Boolean':
  595 + case 'Array':
  596 + case 'Date':
  597 + case 'RegExp':
  598 + case 'Function':
  599 + return type.toLowerCase();
  600 + }
  601 + if (typeof obj === "object") {
  602 + return "object";
  603 + }
  604 + return undefined;
  605 + },
  606 +
  607 + push: function(result, actual, expected, message) {
  608 + var details = {
  609 + result: result,
  610 + message: message,
  611 + actual: actual,
  612 + expected: expected
  613 + };
  614 +
  615 + message = escapeHtml(message) || (result ? "okay" : "failed");
  616 + message = '<span class="test-message">' + message + "</span>";
  617 + expected = escapeHtml(QUnit.jsDump.parse(expected));
  618 + actual = escapeHtml(QUnit.jsDump.parse(actual));
  619 + var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
  620 + if (actual != expected) {
  621 + output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
  622 + output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
  623 + }
  624 + if (!result) {
  625 + var source = sourceFromStacktrace();
  626 + if (source) {
  627 + details.source = source;
  628 + output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
  629 + }
  630 + }
  631 + output += "</table>";
  632 +
  633 + QUnit.log(details);
  634 +
  635 + config.current.assertions.push({
  636 + result: !!result,
  637 + message: output
  638 + });
  639 + },
  640 +
  641 + url: function( params ) {
  642 + params = extend( extend( {}, QUnit.urlParams ), params );
  643 + var querystring = "?",
  644 + key;
  645 + for ( key in params ) {
  646 + querystring += encodeURIComponent( key ) + "=" +
  647 + encodeURIComponent( params[ key ] ) + "&";
  648 + }
  649 + return window.location.pathname + querystring.slice( 0, -1 );
  650 + },
  651 +
  652 + extend: extend,
  653 + id: id,
  654 + addEvent: addEvent,
  655 +
  656 + // Logging callbacks; all receive a single argument with the listed properties
  657 + // run test/logs.html for any related changes
  658 + begin: function() {},
  659 + // done: { failed, passed, total, runtime }
  660 + done: function() {},
  661 + // log: { result, actual, expected, message }
  662 + log: function() {},
  663 + // testStart: { name }
  664 + testStart: function() {},
  665 + // testDone: { name, failed, passed, total }
  666 + testDone: function() {},
  667 + // moduleStart: { name }
  668 + moduleStart: function() {},
  669 + // moduleDone: { name, failed, passed, total }
  670 + moduleDone: function() {}
  671 +});
  672 +
  673 +if ( typeof document === "undefined" || document.readyState === "complete" ) {
  674 + config.autorun = true;
  675 +}
  676 +
  677 +addEvent(window, "load", function() {
  678 + QUnit.begin({});
  679 +
  680 + // Initialize the config, saving the execution queue
  681 + var oldconfig = extend({}, config);
  682 + QUnit.init();
  683 + extend(config, oldconfig);
  684 +
  685 + config.blocking = false;
  686 +
  687 + var userAgent = id("qunit-userAgent");
  688 + if ( userAgent ) {
  689 + userAgent.innerHTML = navigator.userAgent;
  690 + }
  691 + var banner = id("qunit-header");
  692 + if ( banner ) {
  693 + banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
  694 + '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
  695 + '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
  696 + addEvent( banner, "change", function( event ) {
  697 + var params = {};
  698 + params[ event.target.name ] = event.target.checked ? true : undefined;
  699 + window.location = QUnit.url( params );
  700 + });
  701 + }
  702 +
  703 + var toolbar = id("qunit-testrunner-toolbar");
  704 + if ( toolbar ) {
  705 + var filter = document.createElement("input");
  706 + filter.type = "checkbox";
  707 + filter.id = "qunit-filter-pass";
  708 + addEvent( filter, "click", function() {
  709 + var ol = document.getElementById("qunit-tests");
  710 + if ( filter.checked ) {
  711 + ol.className = ol.className + " hidepass";
  712 + } else {
  713 + var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
  714 + ol.className = tmp.replace(/ hidepass /, " ");
  715 + }
  716 + if ( defined.sessionStorage ) {
  717 + if (filter.checked) {
  718 + sessionStorage.setItem("qunit-filter-passed-tests", "true");
  719 + } else {
  720 + sessionStorage.removeItem("qunit-filter-passed-tests");
  721 + }
  722 + }
  723 + });
  724 + if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
  725 + filter.checked = true;
  726 + var ol = document.getElementById("qunit-tests");
  727 + ol.className = ol.className + " hidepass";
  728 + }
  729 + toolbar.appendChild( filter );
  730 +
  731 + var label = document.createElement("label");
  732 + label.setAttribute("for", "qunit-filter-pass");
  733 + label.innerHTML = "Hide passed tests";
  734 + toolbar.appendChild( label );
  735 + }
  736 +
  737 + var main = id('qunit-fixture');
  738 + if ( main ) {
  739 + config.fixture = main.innerHTML;
  740 + }
  741 +
  742 + if (config.autostart) {
  743 + QUnit.start();
  744 + }
  745 +});
  746 +
  747 +function done() {
  748 + config.autorun = true;
  749 +
  750 + // Log the last module results
  751 + if ( config.currentModule ) {
  752 + QUnit.moduleDone( {
  753 + name: config.currentModule,
  754 + failed: config.moduleStats.bad,
  755 + passed: config.moduleStats.all - config.moduleStats.bad,
  756 + total: config.moduleStats.all
  757 + } );
  758 + }
  759 +
  760 + var banner = id("qunit-banner"),
  761 + tests = id("qunit-tests"),
  762 + runtime = +new Date - config.started,
  763 + passed = config.stats.all - config.stats.bad,
  764 + html = [
  765 + 'Tests completed in ',
  766 + runtime,
  767 + ' milliseconds.<br/>',
  768 + '<span class="passed">',
  769 + passed,
  770 + '</span> tests of <span class="total">',
  771 + config.stats.all,
  772 + '</span> passed, <span class="failed">',
  773 + config.stats.bad,
  774 + '</span> failed.'
  775 + ].join('');
  776 +
  777 + if ( banner ) {
  778 + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
  779 + }
  780 +
  781 + if ( tests ) {
  782 + id( "qunit-testresult" ).innerHTML = html;
  783 + }
  784 +
  785 + if ( typeof document !== "undefined" && document.title ) {
  786 + // show ✖ for bad, ✔ for good suite result in title
  787 + // use escape sequences in case file gets loaded with non-utf-8-charset
  788 + document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title;
  789 + }
  790 +
  791 + QUnit.done( {
  792 + failed: config.stats.bad,
  793 + passed: passed,
  794 + total: config.stats.all,
  795 + runtime: runtime
  796 + } );
  797 +}
  798 +
  799 +function validTest( name ) {
  800 + var filter = config.filter,
  801 + run = false;
  802 +
  803 + if ( !filter ) {
  804 + return true;
  805 + }
  806 +
  807 + var not = filter.charAt( 0 ) === "!";
  808 + if ( not ) {
  809 + filter = filter.slice( 1 );
  810 + }
  811 +
  812 + if ( name.indexOf( filter ) !== -1 ) {
  813 + return !not;
  814 + }
  815 +
  816 + if ( not ) {
  817 + run = true;
  818 + }
  819 +
  820 + return run;
  821 +}
  822 +
  823 +// so far supports only Firefox, Chrome and Opera (buggy)
  824 +// could be extended in the future to use something like https://github.com/csnover/TraceKit
  825 +function sourceFromStacktrace() {
  826 + try {
  827 + throw new Error();
  828 + } catch ( e ) {
  829 + if (e.stacktrace) {
  830 + // Opera
  831 + return e.stacktrace.split("\n")[6];
  832 + } else if (e.stack) {
  833 + // Firefox, Chrome
  834 + return e.stack.split("\n")[4];
  835 + }
  836 + }
  837 +}
  838 +
  839 +function escapeHtml(s) {
  840 + if (!s) {
  841 + return "";
  842 + }
  843 + s = s + "";
  844 + return s.replace(/[\&"<>\\]/g, function(s) {
  845 + switch(s) {
  846 + case "&": return "&amp;";
  847 + case "\\": return "\\\\";
  848 + case '"': return '\"';
  849 + case "<": return "&lt;";
  850 + case ">": return "&gt;";
  851 + default: return s;
  852 + }
  853 + });
  854 +}
  855 +
  856 +function synchronize( callback ) {
  857 + config.queue.push( callback );
  858 +
  859 + if ( config.autorun && !config.blocking ) {
  860 + process();
  861 + }
  862 +}
  863 +
  864 +function process() {
  865 + var start = (new Date()).getTime();
  866 +
  867 + while ( config.queue.length && !config.blocking ) {
  868 + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
  869 + config.queue.shift()();
  870 + } else {
  871 + window.setTimeout( process, 13 );
  872 + break;
  873 + }
  874 + }
  875 + if (!config.blocking && !config.queue.length) {
  876 + done();
  877 + }
  878 +}
  879 +
  880 +function saveGlobal() {
  881 + config.pollution = [];
  882 +
  883 + if ( config.noglobals ) {
  884 + for ( var key in window ) {
  885 + config.pollution.push( key );
  886 + }
  887 + }
  888 +}
  889 +
  890 +function checkPollution( name ) {
  891 + var old = config.pollution;
  892 + saveGlobal();
  893 +
  894 + var newGlobals = diff( config.pollution, old );
  895 + if ( newGlobals.length > 0 ) {
  896 + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
  897 + }
  898 +
  899 + var deletedGlobals = diff( old, config.pollution );
  900 + if ( deletedGlobals.length > 0 ) {
  901 + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
  902 + }
  903 +}
  904 +
  905 +// returns a new Array with the elements that are in a but not in b
  906 +function diff( a, b ) {
  907 + var result = a.slice();
  908 + for ( var i = 0; i < result.length; i++ ) {
  909 + for ( var j = 0; j < b.length; j++ ) {
  910 + if ( result[i] === b[j] ) {
  911 + result.splice(i, 1);
  912 + i--;
  913 + break;
  914 + }
  915 + }
  916 + }
  917 + return result;
  918 +}
  919 +
  920 +function fail(message, exception, callback) {
  921 + if ( typeof console !== "undefined" && console.error && console.warn ) {
  922 + console.error(message);
  923 + console.error(exception);
  924 + console.warn(callback.toString());
  925 +
  926 + } else if ( window.opera && opera.postError ) {
  927 + opera.postError(message, exception, callback.toString);
  928 + }
  929 +}
  930 +
  931 +function extend(a, b) {
  932 + for ( var prop in b ) {
  933 + if ( b[prop] === undefined ) {
  934 + delete a[prop];
  935 + } else {
  936 + a[prop] = b[prop];
  937 + }
  938 + }
  939 +
  940 + return a;
  941 +}
  942 +
  943 +function addEvent(elem, type, fn) {
  944 + if ( elem.addEventListener ) {
  945 + elem.addEventListener( type, fn, false );
  946 + } else if ( elem.attachEvent ) {
  947 + elem.attachEvent( "on" + type, fn );
  948 + } else {
  949 + fn();
  950 + }
  951 +}
  952 +
  953 +function id(name) {
  954 + return !!(typeof document !== "undefined" && document && document.getElementById) &&
  955 + document.getElementById( name );
  956 +}
  957 +
  958 +// Test for equality any JavaScript type.
  959 +// Discussions and reference: http://philrathe.com/articles/equiv
  960 +// Test suites: http://philrathe.com/tests/equiv
  961 +// Author: Philippe Rathé <prathe@gmail.com>
  962 +QUnit.equiv = function () {
  963 +
  964 + var innerEquiv; // the real equiv function
  965 + var callers = []; // stack to decide between skip/abort functions
  966 + var parents = []; // stack to avoiding loops from circular referencing
  967 +
  968 + // Call the o related callback with the given arguments.
  969 + function bindCallbacks(o, callbacks, args) {
  970 + var prop = QUnit.objectType(o);
  971 + if (prop) {
  972 + if (QUnit.objectType(callbacks[prop]) === "function") {
  973 + return callbacks[prop].apply(callbacks, args);
  974 + } else {
  975 + return callbacks[prop]; // or undefined
  976 + }
  977 + }
  978 + }
  979 +
  980 + var callbacks = function () {
  981 +
  982 + // for string, boolean, number and null
  983 + function useStrictEquality(b, a) {
  984 + if (b instanceof a.constructor || a instanceof b.constructor) {
  985 + // to catch short annotaion VS 'new' annotation of a declaration
  986 + // e.g. var i = 1;
  987 + // var j = new Number(1);
  988 + return a == b;
  989 + } else {