text: /**
   * QUnit v1.5.0 - A JavaScript Unit Testing Framework
   *
   * http://docs.jquery.com/QUnit
   *
   * Copyright (c) 2012 John Resig, Jörn Zaefferer
   * Dual licensed under the MIT (MIT-LICENSE.txt)
   * or GPL (GPL-LICENSE.txt) licenses.
   */
  
  (function (window) {
  
      var defined = {
          setTimeout: typeof window.setTimeout !== "undefined",
          sessionStorage: (function () {
              var x = "qunit-test-string";
              try {
                  sessionStorage.setItem(x, x);
                  sessionStorage.removeItem(x);
                  return true;
              } catch (e) {
                  return false;
              }
          }())
      };
  
      var testId = 0,
          toString = Object.prototype.toString,
          hasOwn = Object.prototype.hasOwnProperty;
  
      var Test = function (name, testName, expected, async, callback) {
          this.name = name;
          this.testName = testName;
          this.expected = expected;
          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.className = "running";
                  li.id = this.id = "test-output" + testId++;
                  tests.appendChild(li);
              }
          },
          setup: function () {
              if (this.module != config.previousModule) {
                  if (config.previousModule) {
                      runLoggingCallbacks('moduleDone', QUnit, {
                          name: config.previousModule,
                          failed: config.moduleStats.bad,
                          passed: config.moduleStats.all - config.moduleStats.bad,
                          total: config.moduleStats.all
                      });
                  }
                  config.previousModule = this.module;
                  config.moduleStats = {all: 0, bad: 0};
                  runLoggingCallbacks('moduleStart', QUnit, {
                      name: this.module
                  });
              } else if (config.autorun) {
                  runLoggingCallbacks('moduleStart', QUnit, {
                      name: this.module
                  });
              }
  
              config.current = this;
              this.testEnvironment = extend({
                  setup: function () {
                  },
                  teardown: function () {
                  }
              }, this.moduleTestEnvironment);
  
              runLoggingCallbacks('testStart', QUnit, {
                  name: this.testName,
                  module: this.module
              });
  
              // allow utility functions to access the current test environment
              // TODO why??
              QUnit.current_testEnvironment = this.testEnvironment;
  
              if (!config.pollution) {
                  saveGlobal();
              }
              if (config.notrycatch) {
                  this.testEnvironment.setup.call(this.testEnvironment);
                  return;
              }
              try {
                  this.testEnvironment.setup.call(this.testEnvironment);
              } catch (e) {
                  QUnit.pushFailure("Setup failed on " + this.testName + ": " + e.message, extractStacktrace(e, 1));
              }
          },
          run: function () {
              config.current = this;
  
              var running = id("qunit-testresult");
  
              if (running) {
                  running.innerHTML = "Running: <br/>" + this.name;
              }
  
              if (this.async) {
                  QUnit.stop();
              }
  
              if (config.notrycatch) {
                  this.callback.call(this.testEnvironment);
                  return;
              }
              try {
                  this.callback.call(this.testEnvironment);
              } catch (e) {
                  QUnit.pushFailure("Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace(e, 1));
                  // else next test will carry the responsibility
                  saveGlobal();
  
                  // Restart the tests if they're blocking
                  if (config.blocking) {
                      QUnit.start();
                  }
              }
          },
          teardown: function () {
              config.current = this;
              if (config.notrycatch) {
                  this.testEnvironment.teardown.call(this.testEnvironment);
                  return;
              } else {
                  try {
                      this.testEnvironment.teardown.call(this.testEnvironment);
                  } catch (e) {
                      QUnit.pushFailure("Teardown failed on " + this.testName + ": " + e.message, extractStacktrace(e, 1));
                  }
              }
              checkPollution();
          },
          finish: function () {
              config.current = this;
              if (this.expected != null && this.expected != this.assertions.length) {
                  QUnit.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run");
              } else if (this.expected == null && !this.assertions.length) {
                  QUnit.pushFailure("Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.");
              }
  
              var good = 0, bad = 0,
                  li, i,
                  tests = id("qunit-tests");
  
              config.stats.all += this.assertions.length;
              config.moduleStats.all += this.assertions.length;
  
              if (tests) {
                  var ol = document.createElement("ol");
  
                  for (i = 0; i < this.assertions.length; i++) {
                      var assertion = this.assertions[i];
  
                      li = document.createElement("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++;
                      }
                  }
  
                  // store result when possible
                  if (QUnit.config.reorder && defined.sessionStorage) {
                      if (bad) {
                          sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad);
                      } else {
                          sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName);
                      }
                  }
  
                  if (bad === 0) {
                      ol.style.display = "none";
                  }
  
                  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>";
  
                  var a = document.createElement("a");
                  a.innerHTML = "Rerun";
                  a.href = QUnit.url({filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "")});
  
                  addEvent(b, "click", function () {
                      var next = b.nextSibling.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 = QUnit.url({filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "")});
                      }
                  });
  
                  li = id(this.id);
                  li.className = bad ? "fail" : "pass";
                  li.removeChild(li.firstChild);
                  li.appendChild(b);
                  li.appendChild(a);
                  li.appendChild(ol);
  
              } else {
                  for (i = 0; i < this.assertions.length; i++) {
                      if (!this.assertions[i].result) {
                          bad++;
                          config.stats.bad++;
                          config.moduleStats.bad++;
                      }
                  }
              }
  
              QUnit.reset();
  
              runLoggingCallbacks('testDone', QUnit, {
                  name: this.testName,
                  module: this.module,
                  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 = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);
              if (bad) {
                  run();
              } else {
                  synchronize(run, true);
              }
          }
  
      };
  
      var QUnit = {
  
          // call on start of module test to prepend name to all tests
          module: function (name, testEnvironment) {
              config.currentModule = name;
              config.currentModuleTestEnviroment = testEnvironment;
          },
  
          asyncTest: function (testName, expected, callback) {
              if (arguments.length === 2) {
                  callback = expected;
                  expected = null;
              }
  
              QUnit.test(testName, expected, callback, true);
          },
  
          test: function (testName, expected, callback, async) {
              var name = '<span class="test-name">' + escapeInnerText(testName) + '</span>';
  
              if (arguments.length === 2) {
                  callback = expected;
                  expected = null;
              }
  
              if (config.currentModule) {
                  name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
              }
  
              if (!validTest(config.currentModule + ": " + testName)) {
                  return;
              }
  
              var test = new Test(name, testName, expected, 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.current.expected = asserts;
          },
  
          // Asserts true.
          // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
          ok: function (result, msg) {
              if (!config.current) {
                  throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
              }
              result = !!result;
              var details = {
                  result: result,
                  message: msg
              };
              msg = escapeInnerText(msg || (result ? "okay" : "failed"));
              if (!result) {
                  var source = sourceFromStacktrace(2);
                  if (source) {
                      details.source = source;
                      msg += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
                  }
              }
              runLoggingCallbacks('log', QUnit, details);
              config.current.assertions.push({
                  result: result,
                  message: msg
              });
          },
  
          // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values.
          // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
          equal: function (actual, expected, message) {
              QUnit.push(expected == actual, actual, expected, message);
          },
  
          notEqual: function (actual, expected, message) {
              QUnit.push(expected != actual, actual, expected, message);
          },
  
          deepEqual: function (actual, expected, message) {
              QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
          },
  
          notDeepEqual: function (actual, expected, message) {
              QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
          },
  
          strictEqual: function (actual, expected, message) {
              QUnit.push(expected === actual, actual, expected, message);
          },
  
          notStrictEqual: function (actual, expected, message) {
              QUnit.push(expected !== actual, actual, expected, message);
          },
  
          raises: function (block, expected, message) {
              var actual, ok = false;
  
              if (typeof expected === 'string') {
                  message = expected;
                  expected = null;
              }
  
              try {
                  block.call(config.current.testEnvironment);
              } catch (e) {
                  actual = e;
              }
  
              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 (count) {
              config.semaphore -= count || 1;
              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 (defined.setTimeout) {
                  window.setTimeout(function () {
                      if (config.semaphore > 0) {
                          return;
                      }
                      if (config.timeout) {
                          clearTimeout(config.timeout);
                      }
  
                      config.blocking = false;
                      process(true);
                  }, 13);
              } else {
                  config.blocking = false;
                  process(true);
              }
          },
  
          stop: function (count) {
              config.semaphore += count || 1;
              config.blocking = true;
  
              if (config.testTimeout && defined.setTimeout) {
                  clearTimeout(config.timeout);
                  config.timeout = window.setTimeout(function () {
                      QUnit.ok(false, "Test timed out");
                      config.semaphore = 1;
                      QUnit.start();
                  }, config.testTimeout);
              }
          }
      };
  
  //We want access to the constructor's prototype
      (function () {
          function F() {
          }
  
          F.prototype = QUnit;
          QUnit = new F();
          //Make F QUnit's constructor so that we can add to the prototype later
          QUnit.constructor = F;
      }());
  
  // deprecated; still export them to window to provide clear error messages
  // next step: remove entirely
      QUnit.equals = function () {
          QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
      };
      QUnit.same = function () {
          QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
      };
  
  // Maintain internal state
      var config = {
          // The queue of tests to run
          queue: [],
  
          // block until document ready
          blocking: true,
  
          // when enabled, show only failing tests
          // gets persisted through sessionStorage and can be changed in UI via checkbox
          hidepassed: false,
  
          // by default, run previously failed tests first
          // very useful in combination with "Hide passed tests" checked
          reorder: true,
  
          // by default, modify document.title when suite is done
          altertitle: true,
  
          urlConfig: ['noglobals', 'notrycatch'],
  
          //logging callback queues
          begin: [],
          done: [],
          log: [],
          testStart: [],
          testDone: [],
          moduleStart: [],
          moduleDone: []
      };
  
  // Load paramaters
      (function () {
          var location = window.location || {search: "", protocol: "file:"},
              params = location.search.slice(1).split("&"),
              length = params.length,
              urlParams = {},
              current;
  
          if (params[0]) {
              for (var i = 0; i < length; i++) {
                  current = params[i].split("=");
                  current[0] = decodeURIComponent(current[0]);
                  // allow just a key to turn on a flag, e.g., test.html?noglobals
                  current[1] = current[1] ? decodeURIComponent(current[1]) : true;
                  urlParams[current[0]] = current[1];
              }
          }
  
          QUnit.urlParams = urlParams;
          config.filter = urlParams.filter;
  
          // Figure out if we're running the tests from a server or not
          QUnit.isLocal = location.protocol === 'file:';
      }());
  
  // Expose the API as global variables, unless an 'exports'
  // object exists, in that case we assume we're in CommonJS - export everything at the end
      if (typeof exports === "undefined" || typeof require === "undefined") {
          extend(window, QUnit);
          window.QUnit = QUnit;
      }
  
  // define these after exposing globals to keep them in these QUnit namespace only
      extend(QUnit, {
          config: config,
  
          // Initialize the configuration options
          init: function () {
              extend(config, {
                  stats: {all: 0, bad: 0},
                  moduleStats: {all: 0, bad: 0},
                  started: +new Date(),
                  updateRate: 1000,
                  blocking: false,
                  autostart: true,
                  autorun: false,
                  filter: "",
                  queue: [],
                  semaphore: 0
              });
  
              var qunit = id("qunit");
              if (qunit) {
                  qunit.innerHTML =
                      '<h1 id="qunit-header">' + escapeInnerText(document.title) + '</h1>' +
                      '<h2 id="qunit-banner"></h2>' +
                      '<div id="qunit-testrunner-toolbar"></div>' +
                      '<h2 id="qunit-userAgent"></h2>' +
                      '<ol id="qunit-tests"></ol>';
              }
  
              var tests = id("qunit-tests"),
                  banner = id("qunit-banner"),
                  result = id("qunit-testresult");
  
              if (tests) {
                  tests.innerHTML = "";
              }
  
              if (banner) {
                  banner.className = "";
              }
  
              if (result) {
                  result.parentNode.removeChild(result);
              }
  
              if (tests) {
                  result = document.createElement("p");
                  result.id = "qunit-testresult";
                  result.className = "result";
                  tests.parentNode.insertBefore(result, tests);
                  result.innerHTML = 'Running...<br/>&nbsp;';
              }
          },
  
          // 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("#qunit-fixture").html(config.fixture);
              } else {
                  var main = id('qunit-fixture');
                  if (main) {
                      main.innerHTML = config.fixture;
                  }
              }
          },
  
          // Trigger an event on an element.
          // @example triggerEvent( document.body, "click" );
          triggerEvent: function (elem, type, event) {
              if (document.createEvent) {
                  event = document.createEvent("MouseEvents");
                  event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
                      0, 0, 0, 0, 0, false, false, false, false, 0, null);
                  elem.dispatchEvent(event);
  
              } else if (elem.fireEvent) {
                  elem.fireEvent("on" + type);
              }
          },
  
          // Safe object type checking
          is: function (type, obj) {
              return QUnit.objectType(obj) == type;
          },
  
          objectType: function (obj) {
              if (typeof obj === "undefined") {
                  return "undefined";
  
                  // consider: typeof null === object
              }
              if (obj === null) {
                  return "null";
              }
  
              var type = toString.call(obj).match(/^\[object\s(.*)\]$/)[1] || '';
  
              switch (type) {
                  case 'Number':
                      if (isNaN(obj)) {
                          return "nan";
                      }
                      return "number";
                  case 'String':
                  case 'Boolean':
                  case 'Array':
                  case 'Date':
                  case 'RegExp':
                  case 'Function':
                      return type.toLowerCase();
              }
              if (typeof obj === "object") {
                  return "object";
              }
              return undefined;
          },
  
          push: function (result, actual, expected, message) {
              if (!config.current) {
                  throw new Error("assertion outside test context, was " + sourceFromStacktrace());
              }
              var details = {
                  result: result,
                  message: message,
                  actual: actual,
                  expected: expected
              };
  
              message = escapeInnerText(message) || (result ? "okay" : "failed");
              message = '<span class="test-message">' + message + "</span>";
              var output = message;
              if (!result) {
                  expected = escapeInnerText(QUnit.jsDump.parse(expected));
                  actual = escapeInnerText(QUnit.jsDump.parse(actual));
                  output += '<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>';
                  }
                  var source = sourceFromStacktrace();
                  if (source) {
                      details.source = source;
                      output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
                  }
                  output += "</table>";
              }
  
              runLoggingCallbacks('log', QUnit, details);
  
              config.current.assertions.push({
                  result: !!result,
                  message: output
              });
          },
  
          pushFailure: function (message, source) {
              var details = {
                  result: false,
                  message: message
              };
              var output = escapeInnerText(message);
              if (source) {
                  details.source = source;
                  output += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
              }
              runLoggingCallbacks('log', QUnit, details);
              config.current.assertions.push({
                  result: false,
                  message: output
              });
          },
  
          url: function (params) {
              params = extend(extend({}, QUnit.urlParams), params);
              var querystring = "?",
                  key;
              for (key in params) {
                  if (!hasOwn.call(params, key)) {
                      continue;
                  }
                  querystring += encodeURIComponent(key) + "=" +
                      encodeURIComponent(params[key]) + "&";
              }
              return window.location.pathname + querystring.slice(0, -1);
          },
  
          extend: extend,
          id: id,
          addEvent: addEvent
      });
  
  //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
  //Doing this allows us to tell if the following methods have been overwritten on the actual
  //QUnit object, which is a deprecated way of using the callbacks.
      extend(QUnit.constructor.prototype, {
          // Logging callbacks; all receive a single argument with the listed properties
          // run test/logs.html for any related changes
          begin: registerLoggingCallback('begin'),
          // done: { failed, passed, total, runtime }
          done: registerLoggingCallback('done'),
          // log: { result, actual, expected, message }
          log: registerLoggingCallback('log'),
          // testStart: { name }
          testStart: registerLoggingCallback('testStart'),
          // testDone: { name, failed, passed, total }
          testDone: registerLoggingCallback('testDone'),
          // moduleStart: { name }
          moduleStart: registerLoggingCallback('moduleStart'),
          // moduleDone: { name, failed, passed, total }
          moduleDone: registerLoggingCallback('moduleDone')
      });
  
      if (typeof document === "undefined" || document.readyState === "complete") {
          config.autorun = true;
      }
  
      QUnit.load = function () {
          runLoggingCallbacks('begin', QUnit, {});
  
          // Initialize the config, saving the execution queue
          var oldconfig = extend({}, config);
          QUnit.init();
          extend(config, oldconfig);
  
          config.blocking = false;
  
          var urlConfigHtml = '', len = config.urlConfig.length;
          for (var i = 0, val; i < len; i++) {
              val = config.urlConfig[i];
              config[val] = QUnit.urlParams[val];
              urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + (config[val] ? ' checked="checked"' : '') + '>' + val + '</label>';
          }
  
          var userAgent = id("qunit-userAgent");
          if (userAgent) {
              userAgent.innerHTML = navigator.userAgent;
          }
          var banner = id("qunit-header");
          if (banner) {
              banner.innerHTML = '<a href="' + QUnit.url({filter: undefined}) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
              addEvent(banner, "change", function (event) {
                  var params = {};
                  params[event.target.name] = event.target.checked ? true : undefined;
                  window.location = QUnit.url(params);
              });
          }
  
          var toolbar = id("qunit-testrunner-toolbar");
          if (toolbar) {
              var filter = document.createElement("input");
              filter.type = "checkbox";
              filter.id = "qunit-filter-pass";
              addEvent(filter, "click", function () {
                  var ol = document.getElementById("qunit-tests");
                  if (filter.checked) {
                      ol.className = ol.className + " hidepass";
                  } else {
                      var tmp = " " + ol.className.replace(/[\n\t\r]/g, " ") + " ";
                      ol.className = tmp.replace(/ hidepass /, " ");
                  }
                  if (defined.sessionStorage) {
                      if (filter.checked) {
                          sessionStorage.setItem("qunit-filter-passed-tests", "true");
                      } else {
                          sessionStorage.removeItem("qunit-filter-passed-tests");
                      }
                  }
              });
              if (config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests")) {
                  filter.checked = true;
                  var ol = document.getElementById("qunit-tests");
                  ol.className = ol.className + " hidepass";
              }
              toolbar.appendChild(filter);
  
              var label = document.createElement("label");
              label.setAttribute("for", "qunit-filter-pass");
              label.innerHTML = "Hide passed tests";
              toolbar.appendChild(label);
          }
  
          var main = id('qunit-fixture');
          if (main) {
              config.fixture = main.innerHTML;
          }
  
          if (config.autostart) {
              QUnit.start();
          }
      };
  
      addEvent(window, "load", QUnit.load);
  
  // addEvent(window, "error") gives us a useless event object
      window.onerror = function (message, file, line) {
          if (QUnit.config.current) {
              QUnit.pushFailure(message, file + ":" + line);
          } else {
              QUnit.test("global failure", function () {
                  QUnit.pushFailure(message, file + ":" + line);
              });
          }
      };
  
      function done() {
          config.autorun = true;
  
          // Log the last module results
          if (config.currentModule) {
              runLoggingCallbacks('moduleDone', QUnit, {
                  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"),
              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");
          }
  
          if (tests) {
              id("qunit-testresult").innerHTML = html;
          }
  
          if (config.altertitle && typeof document !== "undefined" && document.title) {
              // show ✖ for good, ✔ for bad suite result in title
              // use escape sequences in case file gets loaded with non-utf-8-charset
              document.title = [
                  (config.stats.bad ? "\u2716" : "\u2714"),
                  document.title.replace(/^[\u2714\u2716] /i, "")
              ].join(" ");
          }
  
          // clear own sessionStorage items if all tests passed
          if (config.reorder && defined.sessionStorage && config.stats.bad === 0) {
              var key;
              for (var i = 0; i < sessionStorage.length; i++) {
                  key = sessionStorage.key(i++);
                  if (key.indexOf("qunit-test-") === 0) {
                      sessionStorage.removeItem(key);
                  }
              }
          }
  
          runLoggingCallbacks('done', QUnit, {
              failed: config.stats.bad,
              passed: passed,
              total: config.stats.all,
              runtime: runtime
          });
      }
  
      function validTest(name) {
          var filter = config.filter,
              run = false;
  
          if (!filter) {
              return true;
          }
  
          var not = filter.charAt(0) === "!";
          if (not) {
              filter = filter.slice(1);
          }
  
          if (name.indexOf(filter) !== -1) {
              return !not;
          }
  
          if (not) {
              run = true;
          }
  
          return run;
      }
  
  // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
  // Later Safari and IE10 are supposed to support error.stack as well
  // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
      function extractStacktrace(e, offset) {
          offset = offset || 3;
          if (e.stacktrace) {
              // Opera
              return e.stacktrace.split("\n")[offset + 3];
          } else if (e.stack) {
              // Firefox, Chrome
              var stack = e.stack.split("\n");
              if (/^error$/i.test(stack[0])) {
                  stack.shift();
              }
              return stack[offset];
          } else if (e.sourceURL) {
              // Safari, PhantomJS
              // hopefully one day Safari provides actual stacktraces
              // exclude useless self-reference for generated Error objects
              if (/qunit.js$/.test(e.sourceURL)) {
                  return;
              }
              // for actual exceptions, this is useful
              return e.sourceURL + ":" + e.line;
          }
      }
  
      function sourceFromStacktrace(offset) {
          try {
              throw new Error();
          } catch (e) {
              return extractStacktrace(e, offset);
          }
      }
  
      function escapeInnerText(s) {
          if (!s) {
              return "";
          }
          s = s + "";
          return s.replace(/[\&<>]/g, function (s) {
              switch (s) {
                  case "&":
                      return "&amp;";
                  case "<":
                      return "&lt;";
                  case ">":
                      return "&gt;";
                  default:
                      return s;
              }
          });
      }
  
      function synchronize(callback, last) {
          config.queue.push(callback);
  
          if (config.autorun && !config.blocking) {
              process(last);
          }
      }
  
      function process(last) {
          function next() {
              process(last);
          }
  
          var start = new Date().getTime();
          config.depth = config.depth ? config.depth + 1 : 1;
  
          while (config.queue.length && !config.blocking) {
              if (!defined.setTimeout || config.updateRate <= 0 || ((new Date().getTime() - start) < config.updateRate)) {
                  config.queue.shift()();
              } else {
                  window.setTimeout(next, 13);
                  break;
              }
          }
          config.depth--;
          if (last && !config.blocking && !config.queue.length && config.depth === 0) {
              done();
          }
      }
  
      function saveGlobal() {
          config.pollution = [];
  
          if (config.noglobals) {
              for (var key in window) {
                  if (!hasOwn.call(window, key)) {
                      continue;
                  }
                  config.pollution.push(key);
              }
          }
      }
  
      function checkPollution(name) {
          var old = config.pollution;
          saveGlobal();
  
          var newGlobals = diff(config.pollution, old);
          if (newGlobals.length > 0) {
              QUnit.pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
          }
  
          var deletedGlobals = diff(old, config.pollution);
          if (deletedGlobals.length > 0) {
              QUnit.pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
          }
      }
  
  // returns a new Array with the elements that are in a but not in b
      function diff(a, b) {
          var result = a.slice();
          for (var i = 0; i < result.length; i++) {
              for (var j = 0; j < b.length; j++) {
                  if (result[i] === b[j]) {
                      result.splice(i, 1);
                      i--;
                      break;
                  }
              }
          }
          return result;
      }
  
      function extend(a, b) {
          for (var prop in b) {
              if (b[prop] === undefined) {
                  delete a[prop];
  
                  // Avoid "Member not found" error in IE8 caused by setting window.constructor
              } else if (prop !== "constructor" || a !== window) {
                  a[prop] = b[prop];
              }
          }
  
          return a;
      }
  
      function addEvent(elem, type, fn) {
          if (elem.addEventListener) {
              elem.addEventListener(type, fn, false);
          } else if (elem.attachEvent) {
              elem.attachEvent("on" + type, fn);
          } else {
              fn();
          }
      }
  
      function id(name) {
          return !!(typeof document !== "undefined" && document && document.getElementById) &&
              document.getElementById(name);
      }
  
      function registerLoggingCallback(key) {
          return function (callback) {
              config[key].push(callback);
          };
      }
  
  // Supports deprecated method of completely overwriting logging callbacks
      function runLoggingCallbacks(key, scope, args) {
          //debugger;
          var callbacks;
          if (QUnit.hasOwnProperty(key)) {
              QUnit[key].call(scope, args);
          } else {
              callbacks = config[key];
              for (var i = 0; i < callbacks.length; i++) {
                  callbacks[i].call(scope, args);
              }
          }
      }
  
  // Test for equality any JavaScript type.
  // Author: Philippe Rathé <prathe@gmail.com>
      QUnit.equiv = (function () {
  
          var innerEquiv; // the real equiv function
          var callers = []; // stack to decide between skip/abort functions
          var parents = []; // stack to avoiding loops from circular referencing
  
          // Call the o related callback with the given arguments.
          function bindCallbacks(o, callbacks, args) {
              var prop = QUnit.objectType(o);
              if (prop) {
                  if (QUnit.objectType(callbacks[prop]) === "function") {
                      return callbacks[prop].apply(callbacks, args);
                  } else {
                      return callbacks[prop]; // or undefined
                  }
              }
          }
  
          var getProto = Object.getPrototypeOf || function (obj) {
              return obj.__proto__;
          };
  
          var callbacks = (function () {
  
              // for string, boolean, number and null
              function useStrictEquality(b, a) {
                  if (b instanceof a.constructor || a instanceof b.constructor) {
                      // to catch short annotaion VS 'new' annotation of a
                      // declaration
                      // e.g. var i = 1;
                      // var j = new Number(1);
                      return a == b;
                  } else {
                      return a === b;
                  }
              }
  
              return {
                  "string": useStrictEquality,
                  "boolean": useStrictEquality,
                  "number": useStrictEquality,
                  "null": useStrictEquality,
                  "undefined": useStrictEquality,
  
                  "nan": function (b) {
                      return isNaN(b);
                  },
  
                  "date": function (b, a) {
                      return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
                  },
  
                  "regexp": function (b, a) {
                      return QUnit.objectType(b) === "regexp" &&
                          // the regex itself
                          a.source === b.source &&
                          // and its modifers
                          a.global === b.global &&
                          // (gmi) ...
                          a.ignoreCase === b.ignoreCase &&
                          a.multiline === b.multiline;
                  },
  
                  // - skip when the property is a method of an instance (OOP)
                  // - abort otherwise,
                  // initial === would have catch identical references anyway
                  "function": function () {
                      var caller = callers[callers.length - 1];
                      return caller !== Object && typeof caller !== "undefined";
                  },
  
                  "array": function (b, a) {
                      var i, j, loop;
                      var len;
  
                      // b could be an object literal here
                      if (QUnit.objectType(b) !== "array") {
                          return false;
                      }
  
                      len = a.length;
                      if (len !== b.length) { // safe and faster
                          return false;
                      }
  
                      // track reference to avoid circular references
                      parents.push(a);
                      for (i = 0; i < len; i++) {
                          loop = false;
                          for (j = 0; j < parents.length; j++) {
                              if (parents[j] === a[i]) {
                                  loop = true;// dont rewalk array
                              }
                          }
                          if (!loop && !innerEquiv(a[i], b[i])) {
                              parents.pop();
                              return false;
                          }
                      }
                      parents.pop();
                      return true;
                  },
  
                  "object": function (b, a) {
                      var i, j, loop;
                      var eq = true; // unless we can proove it
                      var aProperties = [], bProperties = []; // collection of
                                                              // strings
  
                      // comparing constructors is more strict than using
                      // instanceof
                      if (a.constructor !== b.constructor) {
                          // Allow objects with no prototype to be equivalent to
                          // objects with Object as their constructor.
                          if (!((getProto(a) === null && getProto(b) === Object.prototype) ||
                              (getProto(b) === null && getProto(a) === Object.prototype))) {
                              return false;
                          }
                      }
  
                      // stack constructor before traversing properties
                      callers.push(a.constructor);
                      // track reference to avoid circular references
                      parents.push(a);
  
                      for (i in a) { // be strict: don't ensures hasOwnProperty
                          // and go deep
                          loop = false;
                          for (j = 0; j < parents.length; j++) {
                              if (parents[j] === a[i]) {
                                  // don't go down the same path twice
                                  loop = true;
                              }
                          }
                          aProperties.push(i); // collect a's properties
  
                          if (!loop && !innerEquiv(a[i], b[i])) {
                              eq = false;
                              break;
                          }
                      }
  
                      callers.pop(); // unstack, we are done
                      parents.pop();
  
                      for (i in b) {
                          bProperties.push(i); // collect b's properties
                      }
  
                      // Ensures identical properties name
                      return eq && innerEquiv(aProperties.sort(), bProperties.sort());
                  }
              };
          }());
  
          innerEquiv = function () { // can take multiple arguments
              var args = Array.prototype.slice.apply(arguments);
              if (args.length < 2) {
                  return true; // end transition
              }
  
              return (function (a, b) {
                  if (a === b) {
                      return true; // catch the most you can
                  } else if (a === null || b === null || typeof a === "undefined" ||
                      typeof b === "undefined" ||
                      QUnit.objectType(a) !== QUnit.objectType(b)) {
                      return false; // don't lose time with error prone cases
                  } else {
                      return bindCallbacks(a, callbacks, [b, a]);
                  }
  
                  // apply transition with (1..n) arguments
              }(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)));
          };
  
          return innerEquiv;
  
      }());
  
      /**
       * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
       * http://flesler.blogspot.com Licensed under BSD
       * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
       *
       * @projectDescription Advanced and extensible data dumping for Javascript.
       * @version 1.0.0
       * @author Ariel Flesler
       * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
       */
      QUnit.jsDump = (function () {
          function quote(str) {
              return '"' + str.toString().replace(/"/g, '\\"') + '"';
          }
  
          function literal(o) {
              return o + '';
          }
  
          function join(pre, arr, post) {
              var s = jsDump.separator(),
                  base = jsDump.indent(),
                  inner = jsDump.indent(1);
              if (arr.join) {
                  arr = arr.join(',' + s + inner);
              }
              if (!arr) {
                  return pre + post;
              }
              return [pre, inner + arr, base + post].join(s);
          }
  
          function array(arr, stack) {
              var i = arr.length, ret = new Array(i);
              this.up();
              while (i--) {
                  ret[i] = this.parse(arr[i], undefined, stack);
              }
              this.down();
              return join('[', ret, ']');
          }
  
          var reName = /^function (\w+)/;
  
          var jsDump = {
              parse: function (obj, type, stack) { //type is used mostly internally, you can fix a (custom)type in advance
                  stack = stack || [];
                  var parser = this.parsers[type || this.typeOf(obj)];
                  type = typeof parser;
                  var inStack = inArray(obj, stack);
                  if (inStack != -1) {
                      return 'recursion(' + (inStack - stack.length) + ')';
                  }
                  //else
                  if (type == 'function') {
                      stack.push(obj);
                      var res = parser.call(this, obj, stack);
                      stack.pop();
                      return res;
                  }
                  // else
                  return (type == 'string') ? parser : this.parsers.error;
              },
              typeOf: function (obj) {
                  var type;
                  if (obj === null) {
                      type = "null";
                  } else if (typeof obj === "undefined") {
                      type = "undefined";
                  } else if (QUnit.is("RegExp", obj)) {
                      type = "regexp";
                  } else if (QUnit.is("Date", obj)) {
                      type = "date";
                  } else if (QUnit.is("Function", obj)) {
                      type = "function";
                  } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
                      type = "window";
                  } else if (obj.nodeType === 9) {
                      type = "document";
                  } else if (obj.nodeType) {
                      type = "node";
                  } else if (
                      // native arrays
                      toString.call(obj) === "[object Array]" ||
                      // NodeList objects
                      (typeof obj.length === "number" && typeof obj.item !== "undefined" && (obj.length ? obj.item(0) === obj[0] : (obj.item(0) === null && typeof obj[0] === "undefined")))
                  ) {
                      type = "array";
                  } else {
                      type = typeof obj;
                  }
                  return type;
              },
              separator: function () {
                  return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
              },
              indent: function (extra) {// extra can be a number, shortcut for increasing-calling-decreasing
                  if (!this.multiline) {
                      return '';
                  }
                  var chr = this.indentChar;
                  if (this.HTML) {
                      chr = chr.replace(/\t/g, '   ').replace(/ /g, '&nbsp;');
                  }
                  return new Array(this._depth_ + (extra || 0)).join(chr);
              },
              up: function (a) {
                  this._depth_ += a || 1;
              },
              down: function (a) {
                  this._depth_ -= a || 1;
              },
              setParser: function (name, parser) {
                  this.parsers[name] = parser;
              },
              // The next 3 are exposed so you can use them
              quote: quote,
              literal: literal,
              join: join,
              //
              _depth_: 1,
              // This is the list of parsers, to modify them, use jsDump.setParser
              parsers: {
                  window: '[Window]',
                  document: '[Document]',
                  error: '[ERROR]', //when no parser is found, shouldn't happen
                  unknown: '[Unknown]',
                  'null': 'null',
                  'undefined': 'undefined',
                  'function': function (fn) {
                      var ret = 'function',
                          name = 'name' in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
                      if (name) {
                          ret += ' ' + name;
                      }
                      ret += '(';
  
                      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, stack) {
                      var ret = [], keys, key, val, i;
                      QUnit.jsDump.up();
                      if (Object.keys) {
                          keys = Object.keys(map);
                      } else {
                          keys = [];
                          for (key in map) {
                              keys.push(key);
                          }
                      }
                      keys.sort();
                      for (i = 0; i < keys.length; i++) {
                          key = keys[i];
                          val = map[key];
                          ret.push(QUnit.jsDump.parse(key, 'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
                      }
                      QUnit.jsDump.down();
                      return join('{', ret, '}');
                  },
                  node: function (node) {
                      var open = QUnit.jsDump.HTML ? '&lt;' : '<',
                          close = QUnit.jsDump.HTML ? '&gt;' : '>';
  
                      var tag = node.nodeName.toLowerCase(),
                          ret = open + tag;
  
                      for (var a in QUnit.jsDump.DOMAttrs) {
                          var val = node[QUnit.jsDump.DOMAttrs[a]];
                          if (val) {
                              ret += ' ' + a + '=' + QUnit.jsDump.parse(val, 'attribute');
                          }
                      }
                      return ret + close + open + '/' + tag + close;
                  },
                  functionArgs: function (fn) {//function calls it internally, it's the arguments part of the function
                      var l = fn.length;
                      if (!l) {
                          return '';
                      }
  
                      var args = new Array(l);
                      while (l--) {
                          args[l] = String.fromCharCode(97 + l);//97 is 'a'
                      }
                      return ' ' + args.join(', ') + ' ';
                  },
                  key: quote, //object calls it internally, the key part of an item in a map
                  functionCode: '[code]', //function calls it internally, it's the content of the function
                  attribute: quote, //node calls it internally, it's an html attribute value
                  string: quote,
                  date: quote,
                  regexp: literal, //regex
                  number: literal,
                  'boolean': literal
              },
              DOMAttrs: {//attributes to dump from nodes, name=>realName
                  id: 'id',
                  name: 'name',
                  'class': 'className'
              },
              HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
              indentChar: '  ',//indentation unit
              multiline: true //if true, items in a collection, are separated by a \n, else just a space.
          };
  
          return jsDump;
      }());
  
  // from Sizzle.js
      function getText(elems) {
          var ret = "", elem;
  
          for (var i = 0; elems[i]; i++) {
              elem = elems[i];
  
              // Get the text from text nodes and CDATA nodes
              if (elem.nodeType === 3 || elem.nodeType === 4) {
                  ret += elem.nodeValue;
  
                  // Traverse everything else, except comment nodes
              } else if (elem.nodeType !== 8) {
                  ret += getText(elem.childNodes);
              }
          }
  
          return ret;
      }
  
  //from jquery.js
      function inArray(elem, array) {
          if (array.indexOf) {
              return array.indexOf(elem);
          }
  
          for (var i = 0, length = array.length; i < length; i++) {
              if (array[i] === elem) {
                  return i;
              }
          }
  
          return -1;
      }
  
      /*
       * Javascript Diff Algorithm
       *  By John Resig (http://ejohn.org/)
       *  Modified by Chu Alan "sprite"
       *
       * Released under the MIT license.
       *
       * More Info:
       *  http://ejohn.org/projects/javascript-diff-algorithm/
       *
       * Usage: QUnit.diff(expected, actual)
       *
       * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
       */
      QUnit.diff = (function () {
          function diff(o, n) {
              var ns = {};
              var os = {};
              var i;
  
              for (i = 0; i < n.length; i++) {
                  if (ns[n[i]] == null) {
                      ns[n[i]] = {
                          rows: [],
                          o: null
                      };
                  }
                  ns[n[i]].rows.push(i);
              }
  
              for (i = 0; i < o.length; i++) {
                  if (os[o[i]] == null) {
                      os[o[i]] = {
                          rows: [],
                          n: null
                      };
                  }
                  os[o[i]].rows.push(i);
              }
  
              for (i in ns) {
                  if (!hasOwn.call(ns, i)) {
                      continue;
                  }
                  if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
                      n[ns[i].rows[0]] = {
                          text: n[ns[i].rows[0]],
                          row: os[i].rows[0]
                      };
                      o[os[i].rows[0]] = {
                          text: o[os[i].rows[0]],
                          row: ns[i].rows[0]
                      };
                  }
              }
  
              for (i = 0; i < n.length - 1; i++) {
                  if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
                      n[i + 1] == o[n[i].row + 1]) {
                      n[i + 1] = {
                          text: n[i + 1],
                          row: n[i].row + 1
                      };
                      o[n[i].row + 1] = {
                          text: o[n[i].row + 1],
                          row: i + 1
                      };
                  }
              }
  
              for (i = n.length - 1; i > 0; i--) {
                  if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
                      n[i - 1] == o[n[i].row - 1]) {
                      n[i - 1] = {
                          text: n[i - 1],
                          row: n[i].row - 1
                      };
                      o[n[i].row - 1] = {
                          text: o[n[i].row - 1],
                          row: i - 1
                      };
                  }
              }
  
              return {
                  o: o,
                  n: n
              };
          }
  
          return function (o, n) {
              o = o.replace(/\s+$/, '');
              n = n.replace(/\s+$/, '');
              var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/));
  
              var str = "";
              var i;
  
              var oSpace = o.match(/\s+/g);
              if (oSpace == null) {
                  oSpace = [" "];
              }
              else {
                  oSpace.push(" ");
              }
              var nSpace = n.match(/\s+/g);
              if (nSpace == null) {
                  nSpace = [" "];
              }
              else {
                  nSpace.push(" ");
              }
  
              if (out.n.length === 0) {
                  for (i = 0; i < out.o.length; i++) {
                      str += '<del>' + out.o[i] + oSpace[i] + "</del>";
                  }
              }
              else {
                  if (out.n[0].text == null) {
                      for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
                          str += '<del>' + out.o[n] + oSpace[n] + "</del>";
                      }
                  }
  
                  for (i = 0; i < out.n.length; i++) {
                      if (out.n[i].text == null) {
                          str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
                      }
                      else {
                          var pre = "";
  
                          for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
                              pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
                          }
                          str += " " + out.n[i].text + nSpace[i] + pre;
                      }
                  }
              }
  
              return str;
          };
      }());
  
  // for CommonJS enviroments, export everything
      if (typeof exports !== "undefined" || typeof require !== "undefined") {
          extend(exports, QUnit);
      }
  
  // get at whatever the global object is, like window in browsers
  }((function () {
      return this;
  }.call())));
  