[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[package.json]\nindent_size = 2\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nnpm-debug.*"
  },
  {
    "path": ".jscsrc",
    "content": "{\n    \"disallowKeywordsOnNewLine\": [\n        \"else\",\n        \"catch\"\n    ],\n    \"disallowMixedSpacesAndTabs\": true,\n    \"disallowSpacesInCallExpression\": true,\n    \"disallowSpacesInFunctionDeclaration\": {\n        \"beforeOpeningRoundBrace\": true\n    },\n    \"disallowTrailingComma\": true,\n    \"disallowTrailingWhitespace\": true,\n    \"excludeFiles\": [\n        \"bower_components/**\",\n        \"node_modules/**\"\n    ],\n    \"fileExtensions\": [\n        \".js\",\n        \"jscs\"\n    ],\n    \"requireCommaBeforeLineBreak\": true,\n    \"requireCurlyBraces\": [\n        \"if\",\n        \"else\",\n        \"for\",\n        \"while\",\n        \"do\",\n        \"try\",\n        \"catch\"\n    ],\n    \"requireMultipleVarDecl\": \"onevar\",\n    \"requireSpaceAfterBinaryOperators\": true,\n    \"requireSpaceAfterKeywords\": [\n        \"if\",\n        \"else\",\n        \"for\",\n        \"while\",\n        \"do\",\n        \"switch\",\n        \"return\",\n        \"try\",\n        \"catch\",\n        \"function\"\n    ],\n    \"requireSpaceBeforeBinaryOperators\": [\n        \"=\",\n        \"+=\",\n        \"-=\",\n        \"*=\",\n        \"/=\",\n        \"%=\",\n        \"<<=\",\n        \">>=\",\n        \">>>=\",\n        \"&=\",\n        \"|=\",\n        \"^=\",\n        \"+=\",\n        \"+\",\n        \"-\",\n        \"*\",\n        \"/\",\n        \"%\",\n        \"<<\",\n        \">>\",\n        \">>>\",\n        \"&\",\n        \"|\",\n        \"^\",\n        \"&&\",\n        \"||\",\n        \"===\",\n        \"==\",\n        \">=\",\n        \"<=\",\n        \"<\",\n        \">\",\n        \"!=\",\n        \"!==\"\n    ],\n    \"requireSpaceBeforeBlockStatements\": true,\n    \"requireSpaceBeforeKeywords\": [\n        \"else\"\n    ],\n    \"requireSpaceBeforeObjectValues\": true,\n    \"requireSpacesInConditionalExpression\": true,\n    \"requireSpacesInFunctionExpression\": {\n        \"beforeOpeningCurlyBrace\": true,\n        \"beforeOpeningRoundBrace\": true\n    },\n    \"requireSpacesInsideObjectBrackets\": \"allButNested\",\n    \"validateIndentation\": 4,\n    \"validateParameterSeparator\": \", \",\n    \"validateQuoteMarks\": \"'\"\n}\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n    \"predef\": [\n        \"console\",\n        \"define\",\n        \"self\",\n        \"unescape\",\n        \"module\",\n        \"File\",\n        \"SparkMD5\"\n    ],\n\n    \"browser\": true,\n    \"devel\": true,\n\n    \"bitwise\": false,\n    \"curly\": true,\n    \"eqeqeq\": false,\n    \"forin\": false,\n    \"immed\": true,\n    \"latedef\": false,\n    \"newcap\": true,\n    \"noarg\": true,\n    \"noempty\": false,\n    \"nonew\": true,\n    \"plusplus\": false,\n    \"regexp\": true,\n    \"undef\": true,\n    \"unused\": true,\n    \"quotmark\": \"single\",\n    \"strict\": false,\n    \"trailing\": true,\n\n    \"asi\": false,\n    \"boss\": false,\n    \"debug\": false,\n    \"eqnull\": true,\n    \"es5\": true,\n    \"esnext\": false,\n    \"evil\": false,\n    \"expr\": true,\n    \"funcscope\": false,\n    \"globalstrict\": false,\n    \"iterator\": false,\n    \"lastsemic\": false,\n    \"laxbreak\": false,\n    \"laxcomma\": false,\n    \"loopfunc\": true,\n    \"multistr\": true,\n    \"onecase\": true,\n    \"regexdash\": false,\n    \"scripturl\": false,\n    \"smarttabs\": false,\n    \"shadow\": false,\n    \"sub\": false,\n    \"supernew\": false,\n    \"validthis\": false,\n\n    \"nomen\": false,\n    \"onevar\": false,\n    \"white\": true\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2015 André Cruz <amdfcruz@gmail.com>\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "LICENSE2",
    "content": "Copyright (c) 2015 André Cruz <amdfcruz@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# SparkMD5\n\nSparkMD5 is a fast md5 implementation of the MD5 algorithm.\nThis script is based in the JKM md5 library which is the [fastest](http://jsperf.com/md5-shootout/7) algorithm around. This is most suitable for browser usage, because `nodejs` version might be faster.\n\nNOTE: Please disable Firebug while performing the test!\n      Firebug consumes a lot of memory and CPU and slows the test by a great margin.\n\n\n**[Demo](http://9px.ir/demo/incremental-md5.html)**\n\n## Install\n\n```sh\nnpm install --save spark-md5\n```\n\n## Improvements over the JKM md5 library\n\n * Strings are converted to utf8, like most server side algorithms\n * Fix computation for large amounts of data (overflow)\n * Incremental md5 (see below)\n * Support for array buffers (typed arrays)\n * Functionality wrapped in a closure, to avoid global assignments\n * Object oriented library\n * CommonJS (it can be used in node) and AMD integration\n * Code passed through JSHint and JSCS\n\n\nIncremental md5 performs a lot better for hashing large amounts of data, such as\nfiles. One could read files in chunks, using the FileReader & Blob's, and append\neach chunk for md5 hashing while keeping memory usage low. See example below.\n\n\n## Usage\n\n### Normal usage\n\n```js\nvar hexHash = SparkMD5.hash('Hi there');        // hex hash\nvar rawHash = SparkMD5.hash('Hi there', true);  // OR raw hash (binary string)\n```\n\n### Incremental usage\n\n```js\nvar spark = new SparkMD5();\nspark.append('Hi');\nspark.append(' there');\nvar hexHash = spark.end();                      // hex hash\nvar rawHash = spark.end(true);                  // OR raw hash (binary string)\n```\n\n### Hash a file incrementally\n\nNOTE: If you test the code bellow using the file:// protocol in chrome you must start the browser with -allow-file-access-from-files argument.\n      Please see: http://code.google.com/p/chromium/issues/detail?id=60889\n\n```js\ndocument.getElementById('file').addEventListener('change', function () {\n    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n        file = this.files[0],\n        chunkSize = 2097152,                             // Read in chunks of 2MB\n        chunks = Math.ceil(file.size / chunkSize),\n        currentChunk = 0,\n        spark = new SparkMD5.ArrayBuffer(),\n        fileReader = new FileReader();\n\n    fileReader.onload = function (e) {\n        console.log('read chunk nr', currentChunk + 1, 'of', chunks);\n        spark.append(e.target.result);                   // Append array buffer\n        currentChunk++;\n\n        if (currentChunk < chunks) {\n            loadNext();\n        } else {\n            console.log('finished loading');\n            console.info('computed hash', spark.end());  // Compute hash\n        }\n    };\n\n    fileReader.onerror = function () {\n        console.warn('oops, something went wrong.');\n    };\n\n    function loadNext() {\n        var start = currentChunk * chunkSize,\n            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;\n\n        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));\n    }\n\n    loadNext();\n});\n```\n\nYou can see some more examples in the test folder.\n\n## Documentation\n\n\n### SparkMD5 class\n\n#### SparkMD5#append(str)\n\nAppends a string, encoding it to UTF8 if necessary.\n\n#### SparkMD5#appendBinary(str)\n\nAppends a binary string (e.g.: string returned from the deprecated [readAsBinaryString](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString)).\n\n#### SparkMD5#end(raw)\n\nFinishes the computation of the md5, returning the hex result.\nIf `raw` is true, the result as a binary string will be returned instead.\n\n#### SparkMD5#reset()\n\nResets the internal state of the computation.\n\n#### SparkMD5#getState()\n\nReturns an object representing the internal computation state.\nYou can pass this state to setState(). This feature is useful to resume an incremental md5.\n\n#### SparkMD5#setState(state)\n\nSets the internal computation state. See: getState().\n\n#### SparkMD5#destroy()\n\nReleases memory used by the incremental buffer and other additional resources.\n\n#### SparkMD5.hash(str, raw)\n\nHashes a string directly, returning the hex result.\nIf `raw` is true, the result as a binary string will be returned instead.\nNote that this function is `static`.\n\n#### SparkMD5.hashBinary(str, raw)\n\nHashes a binary string directly (e.g.: string returned from the deprecated [readAsBinaryString](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString)), returning the hex result.\nIf `raw` is true, the result as a binary string will be returned instead.\nNote that this function is `static`.\n\n\n### SparkMD5.ArrayBuffer class\n\n#### SparkMD5.ArrayBuffer#append(arr)\n\nAppends an array buffer.\n\n#### SparkMD5.ArrayBuffer#end(raw)\n\nFinishes the computation of the md5, returning the hex result.\nIf `raw` is true, the result as a binary string will be returned instead.\n\n#### SparkMD5.ArrayBuffer#reset()\n\nResets the internal state of the computation.\n\n#### SparkMD5.ArrayBuffer#destroy()\n\nReleases memory used by the incremental buffer and other additional resources.\n\n#### SparkMD5.ArrayBuffer#getState()\n\nReturns an object representing the internal computation state.\nYou can pass this state to setState(). This feature is useful to resume an incremental md5.\n\n#### SparkMD5.ArrayBuffer#setState(state)\n\nSets the internal computation state. See: getState().\n\n#### SparkMD5.ArrayBuffer.hash(arr, raw)\n\nHashes an array buffer directly, returning the hex result.\nIf `raw` is true, the result as a binary string will be returned instead.\nNote that this function is `static`.\n\n\n## License\n\nThe project is double licensed, being [WTF2](./LICENSE) the master license and [MIT](./LICENSE2) the alternative license.\nThe reason to have two licenses is that some entities refuse to use the master license (WTF2) due to\nbad language. If that's also your case, you can choose the alternative license.\n\n\n## Credits\n\n[Joseph Myers](http://www.myersdaily.org/joseph/javascript/md5-text.html)\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"SparkMD5\",\n  \"version\": \"3.0.0\",\n  \"homepage\": \"https://github.com/satazor/js-spark-md5\",\n  \"authors\": [\n    \"André Cruz <andremiguelcruz@msn.com>\"\n  ],\n  \"description\": \"Lightning fast normal and incremental md5 for javascript\",\n  \"main\": \"spark-md5.js\",\n  \"keywords\": [\n    \"md5\",\n    \"spark\",\n    \"incremental\",\n    \"fast\"\n  ],\n  \"license\": \"http://www.wtfpl.net/\",\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\"\n  ]\n}\n"
  },
  {
    "path": "component.json",
    "content": "{\n  \"name\": \"md5\",\n  \"repo\": \"satazor/js-spark-md5\",\n  \"description\": \"Lightning fast normal and incremental md5 for javascript\",\n  \"version\": \"2.0.0\",\n  \"keywords\": [\n    \"md5\",\n    \"fast\",\n    \"spark\",\n    \"incremental\"\n  ],\n  \"dependencies\": {},\n  \"development\": {},\n  \"license\": \"WTFPL\",\n  \"main\": \"spark-md5.js\",\n  \"scripts\": [\n    \"spark-md5.js\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"spark-md5\",\n  \"version\": \"3.0.2\",\n  \"description\": \"Lightning fast normal and incremental md5 for javascript\",\n  \"main\": \"spark-md5.js\",\n  \"files\": [\n    \"spark-md5.js\",\n    \"spark-md5.min.js\"\n  ],\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:satazor/js-spark-md5.git\"\n  },\n  \"keywords\": [\n    \"md5\",\n    \"fast\",\n    \"spark\",\n    \"incremental\"\n  ],\n  \"author\": \"André Cruz <andremiguelcruz@msn.com>\",\n  \"license\": \"(WTFPL OR MIT)\",\n  \"bugs\": {\n    \"url\": \"https://github.com/satazor/js-spark-md5/issues\"\n  },\n  \"scripts\": {\n    \"min\": \"uglifyjs spark-md5.js > spark-md5.min.js\",\n    \"test\": \"open test/index.html\"\n  },\n  \"devDependencies\": {\n    \"uglify-js\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "spark-md5.js",
    "content": "(function (factory) {\n    if (typeof exports === 'object') {\n        // Node/CommonJS\n        module.exports = factory();\n    } else if (typeof define === 'function' && define.amd) {\n        // AMD\n        define(factory);\n    } else {\n        // Browser globals (with support for web workers)\n        var glob;\n\n        try {\n            glob = window;\n        } catch (e) {\n            glob = self;\n        }\n\n        glob.SparkMD5 = factory();\n    }\n}(function (undefined) {\n\n    'use strict';\n\n    /*\n     * Fastest md5 implementation around (JKM md5).\n     * Credits: Joseph Myers\n     *\n     * @see http://www.myersdaily.org/joseph/javascript/md5-text.html\n     * @see http://jsperf.com/md5-shootout/7\n     */\n\n    /* this function is much faster,\n      so if possible we use it. Some IEs\n      are the only ones I know of that\n      need the idiotic second function,\n      generated by an if clause.  */\n    var add32 = function (a, b) {\n        return (a + b) & 0xFFFFFFFF;\n    },\n        hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];\n\n\n    function cmn(q, a, b, x, s, t) {\n        a = add32(add32(a, q), add32(x, t));\n        return add32((a << s) | (a >>> (32 - s)), b);\n    }\n\n    function md5cycle(x, k) {\n        var a = x[0],\n            b = x[1],\n            c = x[2],\n            d = x[3];\n\n        a += (b & c | ~b & d) + k[0] - 680876936 | 0;\n        a  = (a << 7 | a >>> 25) + b | 0;\n        d += (a & b | ~a & c) + k[1] - 389564586 | 0;\n        d  = (d << 12 | d >>> 20) + a | 0;\n        c += (d & a | ~d & b) + k[2] + 606105819 | 0;\n        c  = (c << 17 | c >>> 15) + d | 0;\n        b += (c & d | ~c & a) + k[3] - 1044525330 | 0;\n        b  = (b << 22 | b >>> 10) + c | 0;\n        a += (b & c | ~b & d) + k[4] - 176418897 | 0;\n        a  = (a << 7 | a >>> 25) + b | 0;\n        d += (a & b | ~a & c) + k[5] + 1200080426 | 0;\n        d  = (d << 12 | d >>> 20) + a | 0;\n        c += (d & a | ~d & b) + k[6] - 1473231341 | 0;\n        c  = (c << 17 | c >>> 15) + d | 0;\n        b += (c & d | ~c & a) + k[7] - 45705983 | 0;\n        b  = (b << 22 | b >>> 10) + c | 0;\n        a += (b & c | ~b & d) + k[8] + 1770035416 | 0;\n        a  = (a << 7 | a >>> 25) + b | 0;\n        d += (a & b | ~a & c) + k[9] - 1958414417 | 0;\n        d  = (d << 12 | d >>> 20) + a | 0;\n        c += (d & a | ~d & b) + k[10] - 42063 | 0;\n        c  = (c << 17 | c >>> 15) + d | 0;\n        b += (c & d | ~c & a) + k[11] - 1990404162 | 0;\n        b  = (b << 22 | b >>> 10) + c | 0;\n        a += (b & c | ~b & d) + k[12] + 1804603682 | 0;\n        a  = (a << 7 | a >>> 25) + b | 0;\n        d += (a & b | ~a & c) + k[13] - 40341101 | 0;\n        d  = (d << 12 | d >>> 20) + a | 0;\n        c += (d & a | ~d & b) + k[14] - 1502002290 | 0;\n        c  = (c << 17 | c >>> 15) + d | 0;\n        b += (c & d | ~c & a) + k[15] + 1236535329 | 0;\n        b  = (b << 22 | b >>> 10) + c | 0;\n\n        a += (b & d | c & ~d) + k[1] - 165796510 | 0;\n        a  = (a << 5 | a >>> 27) + b | 0;\n        d += (a & c | b & ~c) + k[6] - 1069501632 | 0;\n        d  = (d << 9 | d >>> 23) + a | 0;\n        c += (d & b | a & ~b) + k[11] + 643717713 | 0;\n        c  = (c << 14 | c >>> 18) + d | 0;\n        b += (c & a | d & ~a) + k[0] - 373897302 | 0;\n        b  = (b << 20 | b >>> 12) + c | 0;\n        a += (b & d | c & ~d) + k[5] - 701558691 | 0;\n        a  = (a << 5 | a >>> 27) + b | 0;\n        d += (a & c | b & ~c) + k[10] + 38016083 | 0;\n        d  = (d << 9 | d >>> 23) + a | 0;\n        c += (d & b | a & ~b) + k[15] - 660478335 | 0;\n        c  = (c << 14 | c >>> 18) + d | 0;\n        b += (c & a | d & ~a) + k[4] - 405537848 | 0;\n        b  = (b << 20 | b >>> 12) + c | 0;\n        a += (b & d | c & ~d) + k[9] + 568446438 | 0;\n        a  = (a << 5 | a >>> 27) + b | 0;\n        d += (a & c | b & ~c) + k[14] - 1019803690 | 0;\n        d  = (d << 9 | d >>> 23) + a | 0;\n        c += (d & b | a & ~b) + k[3] - 187363961 | 0;\n        c  = (c << 14 | c >>> 18) + d | 0;\n        b += (c & a | d & ~a) + k[8] + 1163531501 | 0;\n        b  = (b << 20 | b >>> 12) + c | 0;\n        a += (b & d | c & ~d) + k[13] - 1444681467 | 0;\n        a  = (a << 5 | a >>> 27) + b | 0;\n        d += (a & c | b & ~c) + k[2] - 51403784 | 0;\n        d  = (d << 9 | d >>> 23) + a | 0;\n        c += (d & b | a & ~b) + k[7] + 1735328473 | 0;\n        c  = (c << 14 | c >>> 18) + d | 0;\n        b += (c & a | d & ~a) + k[12] - 1926607734 | 0;\n        b  = (b << 20 | b >>> 12) + c | 0;\n\n        a += (b ^ c ^ d) + k[5] - 378558 | 0;\n        a  = (a << 4 | a >>> 28) + b | 0;\n        d += (a ^ b ^ c) + k[8] - 2022574463 | 0;\n        d  = (d << 11 | d >>> 21) + a | 0;\n        c += (d ^ a ^ b) + k[11] + 1839030562 | 0;\n        c  = (c << 16 | c >>> 16) + d | 0;\n        b += (c ^ d ^ a) + k[14] - 35309556 | 0;\n        b  = (b << 23 | b >>> 9) + c | 0;\n        a += (b ^ c ^ d) + k[1] - 1530992060 | 0;\n        a  = (a << 4 | a >>> 28) + b | 0;\n        d += (a ^ b ^ c) + k[4] + 1272893353 | 0;\n        d  = (d << 11 | d >>> 21) + a | 0;\n        c += (d ^ a ^ b) + k[7] - 155497632 | 0;\n        c  = (c << 16 | c >>> 16) + d | 0;\n        b += (c ^ d ^ a) + k[10] - 1094730640 | 0;\n        b  = (b << 23 | b >>> 9) + c | 0;\n        a += (b ^ c ^ d) + k[13] + 681279174 | 0;\n        a  = (a << 4 | a >>> 28) + b | 0;\n        d += (a ^ b ^ c) + k[0] - 358537222 | 0;\n        d  = (d << 11 | d >>> 21) + a | 0;\n        c += (d ^ a ^ b) + k[3] - 722521979 | 0;\n        c  = (c << 16 | c >>> 16) + d | 0;\n        b += (c ^ d ^ a) + k[6] + 76029189 | 0;\n        b  = (b << 23 | b >>> 9) + c | 0;\n        a += (b ^ c ^ d) + k[9] - 640364487 | 0;\n        a  = (a << 4 | a >>> 28) + b | 0;\n        d += (a ^ b ^ c) + k[12] - 421815835 | 0;\n        d  = (d << 11 | d >>> 21) + a | 0;\n        c += (d ^ a ^ b) + k[15] + 530742520 | 0;\n        c  = (c << 16 | c >>> 16) + d | 0;\n        b += (c ^ d ^ a) + k[2] - 995338651 | 0;\n        b  = (b << 23 | b >>> 9) + c | 0;\n\n        a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;\n        a  = (a << 6 | a >>> 26) + b | 0;\n        d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;\n        d  = (d << 10 | d >>> 22) + a | 0;\n        c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;\n        c  = (c << 15 | c >>> 17) + d | 0;\n        b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;\n        b  = (b << 21 |b >>> 11) + c | 0;\n        a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;\n        a  = (a << 6 | a >>> 26) + b | 0;\n        d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;\n        d  = (d << 10 | d >>> 22) + a | 0;\n        c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;\n        c  = (c << 15 | c >>> 17) + d | 0;\n        b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;\n        b  = (b << 21 |b >>> 11) + c | 0;\n        a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;\n        a  = (a << 6 | a >>> 26) + b | 0;\n        d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;\n        d  = (d << 10 | d >>> 22) + a | 0;\n        c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;\n        c  = (c << 15 | c >>> 17) + d | 0;\n        b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;\n        b  = (b << 21 |b >>> 11) + c | 0;\n        a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;\n        a  = (a << 6 | a >>> 26) + b | 0;\n        d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;\n        d  = (d << 10 | d >>> 22) + a | 0;\n        c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;\n        c  = (c << 15 | c >>> 17) + d | 0;\n        b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;\n        b  = (b << 21 | b >>> 11) + c | 0;\n\n        x[0] = a + x[0] | 0;\n        x[1] = b + x[1] | 0;\n        x[2] = c + x[2] | 0;\n        x[3] = d + x[3] | 0;\n    }\n\n    function md5blk(s) {\n        var md5blks = [],\n            i; /* Andy King said do it this way. */\n\n        for (i = 0; i < 64; i += 4) {\n            md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);\n        }\n        return md5blks;\n    }\n\n    function md5blk_array(a) {\n        var md5blks = [],\n            i; /* Andy King said do it this way. */\n\n        for (i = 0; i < 64; i += 4) {\n            md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);\n        }\n        return md5blks;\n    }\n\n    function md51(s) {\n        var n = s.length,\n            state = [1732584193, -271733879, -1732584194, 271733878],\n            i,\n            length,\n            tail,\n            tmp,\n            lo,\n            hi;\n\n        for (i = 64; i <= n; i += 64) {\n            md5cycle(state, md5blk(s.substring(i - 64, i)));\n        }\n        s = s.substring(i - 64);\n        length = s.length;\n        tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n        for (i = 0; i < length; i += 1) {\n            tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);\n        }\n        tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n        if (i > 55) {\n            md5cycle(state, tail);\n            for (i = 0; i < 16; i += 1) {\n                tail[i] = 0;\n            }\n        }\n\n        // Beware that the final length might not fit in 32 bits so we take care of that\n        tmp = n * 8;\n        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n        lo = parseInt(tmp[2], 16);\n        hi = parseInt(tmp[1], 16) || 0;\n\n        tail[14] = lo;\n        tail[15] = hi;\n\n        md5cycle(state, tail);\n        return state;\n    }\n\n    function md51_array(a) {\n        var n = a.length,\n            state = [1732584193, -271733879, -1732584194, 271733878],\n            i,\n            length,\n            tail,\n            tmp,\n            lo,\n            hi;\n\n        for (i = 64; i <= n; i += 64) {\n            md5cycle(state, md5blk_array(a.subarray(i - 64, i)));\n        }\n\n        // Not sure if it is a bug, however IE10 will always produce a sub array of length 1\n        // containing the last element of the parent array if the sub array specified starts\n        // beyond the length of the parent array - weird.\n        // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue\n        a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);\n\n        length = a.length;\n        tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n        for (i = 0; i < length; i += 1) {\n            tail[i >> 2] |= a[i] << ((i % 4) << 3);\n        }\n\n        tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n        if (i > 55) {\n            md5cycle(state, tail);\n            for (i = 0; i < 16; i += 1) {\n                tail[i] = 0;\n            }\n        }\n\n        // Beware that the final length might not fit in 32 bits so we take care of that\n        tmp = n * 8;\n        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n        lo = parseInt(tmp[2], 16);\n        hi = parseInt(tmp[1], 16) || 0;\n\n        tail[14] = lo;\n        tail[15] = hi;\n\n        md5cycle(state, tail);\n\n        return state;\n    }\n\n    function rhex(n) {\n        var s = '',\n            j;\n        for (j = 0; j < 4; j += 1) {\n            s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];\n        }\n        return s;\n    }\n\n    function hex(x) {\n        var i;\n        for (i = 0; i < x.length; i += 1) {\n            x[i] = rhex(x[i]);\n        }\n        return x.join('');\n    }\n\n    // In some cases the fast add32 function cannot be used..\n    if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') {\n        add32 = function (x, y) {\n            var lsw = (x & 0xFFFF) + (y & 0xFFFF),\n                msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n            return (msw << 16) | (lsw & 0xFFFF);\n        };\n    }\n\n    // ---------------------------------------------------\n\n    /**\n     * ArrayBuffer slice polyfill.\n     *\n     * @see https://github.com/ttaubert/node-arraybuffer-slice\n     */\n\n    if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {\n        (function () {\n            function clamp(val, length) {\n                val = (val | 0) || 0;\n\n                if (val < 0) {\n                    return Math.max(val + length, 0);\n                }\n\n                return Math.min(val, length);\n            }\n\n            ArrayBuffer.prototype.slice = function (from, to) {\n                var length = this.byteLength,\n                    begin = clamp(from, length),\n                    end = length,\n                    num,\n                    target,\n                    targetArray,\n                    sourceArray;\n\n                if (to !== undefined) {\n                    end = clamp(to, length);\n                }\n\n                if (begin > end) {\n                    return new ArrayBuffer(0);\n                }\n\n                num = end - begin;\n                target = new ArrayBuffer(num);\n                targetArray = new Uint8Array(target);\n\n                sourceArray = new Uint8Array(this, begin, num);\n                targetArray.set(sourceArray);\n\n                return target;\n            };\n        })();\n    }\n\n    // ---------------------------------------------------\n\n    /**\n     * Helpers.\n     */\n\n    function toUtf8(str) {\n        if (/[\\u0080-\\uFFFF]/.test(str)) {\n            str = unescape(encodeURIComponent(str));\n        }\n\n        return str;\n    }\n\n    function utf8Str2ArrayBuffer(str, returnUInt8Array) {\n        var length = str.length,\n           buff = new ArrayBuffer(length),\n           arr = new Uint8Array(buff),\n           i;\n\n        for (i = 0; i < length; i += 1) {\n            arr[i] = str.charCodeAt(i);\n        }\n\n        return returnUInt8Array ? arr : buff;\n    }\n\n    function arrayBuffer2Utf8Str(buff) {\n        return String.fromCharCode.apply(null, new Uint8Array(buff));\n    }\n\n    function concatenateArrayBuffers(first, second, returnUInt8Array) {\n        var result = new Uint8Array(first.byteLength + second.byteLength);\n\n        result.set(new Uint8Array(first));\n        result.set(new Uint8Array(second), first.byteLength);\n\n        return returnUInt8Array ? result : result.buffer;\n    }\n\n    function hexToBinaryString(hex) {\n        var bytes = [],\n            length = hex.length,\n            x;\n\n        for (x = 0; x < length - 1; x += 2) {\n            bytes.push(parseInt(hex.substr(x, 2), 16));\n        }\n\n        return String.fromCharCode.apply(String, bytes);\n    }\n\n    // ---------------------------------------------------\n\n    /**\n     * SparkMD5 OOP implementation.\n     *\n     * Use this class to perform an incremental md5, otherwise use the\n     * static methods instead.\n     */\n\n    function SparkMD5() {\n        // call reset to init the instance\n        this.reset();\n    }\n\n    /**\n     * Appends a string.\n     * A conversion will be applied if an utf8 string is detected.\n     *\n     * @param {String} str The string to be appended\n     *\n     * @return {SparkMD5} The instance itself\n     */\n    SparkMD5.prototype.append = function (str) {\n        // Converts the string to utf8 bytes if necessary\n        // Then append as binary\n        this.appendBinary(toUtf8(str));\n\n        return this;\n    };\n\n    /**\n     * Appends a binary string.\n     *\n     * @param {String} contents The binary string to be appended\n     *\n     * @return {SparkMD5} The instance itself\n     */\n    SparkMD5.prototype.appendBinary = function (contents) {\n        this._buff += contents;\n        this._length += contents.length;\n\n        var length = this._buff.length,\n            i;\n\n        for (i = 64; i <= length; i += 64) {\n            md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));\n        }\n\n        this._buff = this._buff.substring(i - 64);\n\n        return this;\n    };\n\n    /**\n     * Finishes the incremental computation, reseting the internal state and\n     * returning the result.\n     *\n     * @param {Boolean} raw True to get the raw string, false to get the hex string\n     *\n     * @return {String} The result\n     */\n    SparkMD5.prototype.end = function (raw) {\n        var buff = this._buff,\n            length = buff.length,\n            i,\n            tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            ret;\n\n        for (i = 0; i < length; i += 1) {\n            tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);\n        }\n\n        this._finish(tail, length);\n        ret = hex(this._hash);\n\n        if (raw) {\n            ret = hexToBinaryString(ret);\n        }\n\n        this.reset();\n\n        return ret;\n    };\n\n    /**\n     * Resets the internal state of the computation.\n     *\n     * @return {SparkMD5} The instance itself\n     */\n    SparkMD5.prototype.reset = function () {\n        this._buff = '';\n        this._length = 0;\n        this._hash = [1732584193, -271733879, -1732584194, 271733878];\n\n        return this;\n    };\n\n    /**\n     * Gets the internal state of the computation.\n     *\n     * @return {Object} The state\n     */\n    SparkMD5.prototype.getState = function () {\n        return {\n            buff: this._buff,\n            length: this._length,\n            hash: this._hash.slice()\n        };\n    };\n\n    /**\n     * Gets the internal state of the computation.\n     *\n     * @param {Object} state The state\n     *\n     * @return {SparkMD5} The instance itself\n     */\n    SparkMD5.prototype.setState = function (state) {\n        this._buff = state.buff;\n        this._length = state.length;\n        this._hash = state.hash;\n\n        return this;\n    };\n\n    /**\n     * Releases memory used by the incremental buffer and other additional\n     * resources. If you plan to use the instance again, use reset instead.\n     */\n    SparkMD5.prototype.destroy = function () {\n        delete this._hash;\n        delete this._buff;\n        delete this._length;\n    };\n\n    /**\n     * Finish the final calculation based on the tail.\n     *\n     * @param {Array}  tail   The tail (will be modified)\n     * @param {Number} length The length of the remaining buffer\n     */\n    SparkMD5.prototype._finish = function (tail, length) {\n        var i = length,\n            tmp,\n            lo,\n            hi;\n\n        tail[i >> 2] |= 0x80 << ((i % 4) << 3);\n        if (i > 55) {\n            md5cycle(this._hash, tail);\n            for (i = 0; i < 16; i += 1) {\n                tail[i] = 0;\n            }\n        }\n\n        // Do the final computation based on the tail and length\n        // Beware that the final length may not fit in 32 bits so we take care of that\n        tmp = this._length * 8;\n        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);\n        lo = parseInt(tmp[2], 16);\n        hi = parseInt(tmp[1], 16) || 0;\n\n        tail[14] = lo;\n        tail[15] = hi;\n        md5cycle(this._hash, tail);\n    };\n\n    /**\n     * Performs the md5 hash on a string.\n     * A conversion will be applied if utf8 string is detected.\n     *\n     * @param {String}  str The string\n     * @param {Boolean} [raw] True to get the raw string, false to get the hex string\n     *\n     * @return {String} The result\n     */\n    SparkMD5.hash = function (str, raw) {\n        // Converts the string to utf8 bytes if necessary\n        // Then compute it using the binary function\n        return SparkMD5.hashBinary(toUtf8(str), raw);\n    };\n\n    /**\n     * Performs the md5 hash on a binary string.\n     *\n     * @param {String}  content The binary string\n     * @param {Boolean} [raw]     True to get the raw string, false to get the hex string\n     *\n     * @return {String} The result\n     */\n    SparkMD5.hashBinary = function (content, raw) {\n        var hash = md51(content),\n            ret = hex(hash);\n\n        return raw ? hexToBinaryString(ret) : ret;\n    };\n\n    // ---------------------------------------------------\n\n    /**\n     * SparkMD5 OOP implementation for array buffers.\n     *\n     * Use this class to perform an incremental md5 ONLY for array buffers.\n     */\n    SparkMD5.ArrayBuffer = function () {\n        // call reset to init the instance\n        this.reset();\n    };\n\n    /**\n     * Appends an array buffer.\n     *\n     * @param {ArrayBuffer} arr The array to be appended\n     *\n     * @return {SparkMD5.ArrayBuffer} The instance itself\n     */\n    SparkMD5.ArrayBuffer.prototype.append = function (arr) {\n        var buff = concatenateArrayBuffers(this._buff.buffer, arr, true),\n            length = buff.length,\n            i;\n\n        this._length += arr.byteLength;\n\n        for (i = 64; i <= length; i += 64) {\n            md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));\n        }\n\n        this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);\n\n        return this;\n    };\n\n    /**\n     * Finishes the incremental computation, reseting the internal state and\n     * returning the result.\n     *\n     * @param {Boolean} raw True to get the raw string, false to get the hex string\n     *\n     * @return {String} The result\n     */\n    SparkMD5.ArrayBuffer.prototype.end = function (raw) {\n        var buff = this._buff,\n            length = buff.length,\n            tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n            i,\n            ret;\n\n        for (i = 0; i < length; i += 1) {\n            tail[i >> 2] |= buff[i] << ((i % 4) << 3);\n        }\n\n        this._finish(tail, length);\n        ret = hex(this._hash);\n\n        if (raw) {\n            ret = hexToBinaryString(ret);\n        }\n\n        this.reset();\n\n        return ret;\n    };\n\n    /**\n     * Resets the internal state of the computation.\n     *\n     * @return {SparkMD5.ArrayBuffer} The instance itself\n     */\n    SparkMD5.ArrayBuffer.prototype.reset = function () {\n        this._buff = new Uint8Array(0);\n        this._length = 0;\n        this._hash = [1732584193, -271733879, -1732584194, 271733878];\n\n        return this;\n    };\n\n    /**\n     * Gets the internal state of the computation.\n     *\n     * @return {Object} The state\n     */\n    SparkMD5.ArrayBuffer.prototype.getState = function () {\n        var state = SparkMD5.prototype.getState.call(this);\n\n        // Convert buffer to a string\n        state.buff = arrayBuffer2Utf8Str(state.buff);\n\n        return state;\n    };\n\n    /**\n     * Gets the internal state of the computation.\n     *\n     * @param {Object} state The state\n     *\n     * @return {SparkMD5.ArrayBuffer} The instance itself\n     */\n    SparkMD5.ArrayBuffer.prototype.setState = function (state) {\n        // Convert string to buffer\n        state.buff = utf8Str2ArrayBuffer(state.buff, true);\n\n        return SparkMD5.prototype.setState.call(this, state);\n    };\n\n    SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;\n\n    SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;\n\n    /**\n     * Performs the md5 hash on an array buffer.\n     *\n     * @param {ArrayBuffer} arr The array buffer\n     * @param {Boolean}     [raw] True to get the raw string, false to get the hex one\n     *\n     * @return {String} The result\n     */\n    SparkMD5.ArrayBuffer.hash = function (arr, raw) {\n        var hash = md51_array(new Uint8Array(arr)),\n            ret = hex(hash);\n\n        return raw ? hexToBinaryString(ret) : ret;\n    };\n\n    return SparkMD5;\n}));\n"
  },
  {
    "path": "test/css/qunit-1.16.0.css",
    "content": "/*!\n * QUnit 1.16.0\n * http://qunitjs.com/\n *\n * Copyright 2006, 2014 jQuery Foundation and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-03T16:32Z\n */\n\n/** Font Family and Sizes */\n\n#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {\n    font-family: \"Helvetica Neue Light\", \"HelveticaNeue-Light\", \"Helvetica Neue\", Calibri, Helvetica, Arial, sans-serif;\n}\n\n#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }\n#qunit-tests { font-size: smaller; }\n\n\n/** Resets */\n\n#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {\n    margin: 0;\n    padding: 0;\n}\n\n\n/** Header */\n\n#qunit-header {\n    padding: 0.5em 0 0.5em 1em;\n\n    color: #8699A4;\n    background-color: #0D3349;\n\n    font-size: 1.5em;\n    line-height: 1em;\n    font-weight: 400;\n\n    border-radius: 5px 5px 0 0;\n}\n\n#qunit-header a {\n    text-decoration: none;\n    color: #C2CCD1;\n}\n\n#qunit-header a:hover,\n#qunit-header a:focus {\n    color: #FFF;\n}\n\n#qunit-testrunner-toolbar label {\n    display: inline-block;\n    padding: 0 0.5em 0 0.1em;\n}\n\n#qunit-banner {\n    height: 5px;\n}\n\n#qunit-testrunner-toolbar {\n    padding: 0.5em 1em 0.5em 1em;\n    color: #5E740B;\n    background-color: #EEE;\n    overflow: hidden;\n}\n\n#qunit-userAgent {\n    padding: 0.5em 1em 0.5em 1em;\n    background-color: #2B81AF;\n    color: #FFF;\n    text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;\n}\n\n#qunit-modulefilter-container {\n    float: right;\n}\n\n/** Tests: Pass/Fail */\n\n#qunit-tests {\n    list-style-position: inside;\n}\n\n#qunit-tests li {\n    padding: 0.4em 1em 0.4em 1em;\n    border-bottom: 1px solid #FFF;\n    list-style-position: inside;\n}\n\n#qunit-tests > li {\n    display: none;\n}\n\n#qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail {\n    display: list-item;\n}\n\n#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {\n    display: none;\n}\n\n#qunit-tests li strong {\n    cursor: pointer;\n}\n\n#qunit-tests li.skipped strong {\n    cursor: default;\n}\n\n#qunit-tests li a {\n    padding: 0.5em;\n    color: #C2CCD1;\n    text-decoration: none;\n}\n#qunit-tests li a:hover,\n#qunit-tests li a:focus {\n    color: #000;\n}\n\n#qunit-tests li .runtime {\n    float: right;\n    font-size: smaller;\n}\n\n.qunit-assert-list {\n    margin-top: 0.5em;\n    padding: 0.5em;\n\n    background-color: #FFF;\n\n    border-radius: 5px;\n}\n\n.qunit-collapsed {\n    display: none;\n}\n\n#qunit-tests table {\n    border-collapse: collapse;\n    margin-top: 0.2em;\n}\n\n#qunit-tests th {\n    text-align: right;\n    vertical-align: top;\n    padding: 0 0.5em 0 0;\n}\n\n#qunit-tests td {\n    vertical-align: top;\n}\n\n#qunit-tests pre {\n    margin: 0;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n}\n\n#qunit-tests del {\n    background-color: #E0F2BE;\n    color: #374E0C;\n    text-decoration: none;\n}\n\n#qunit-tests ins {\n    background-color: #FFCACA;\n    color: #500;\n    text-decoration: none;\n}\n\n/*** Test Counts */\n\n#qunit-tests b.counts                       { color: #000; }\n#qunit-tests b.passed                       { color: #5E740B; }\n#qunit-tests b.failed                       { color: #710909; }\n\n#qunit-tests li li {\n    padding: 5px;\n    background-color: #FFF;\n    border-bottom: none;\n    list-style-position: inside;\n}\n\n/*** Passing Styles */\n\n#qunit-tests li li.pass {\n    color: #3C510C;\n    background-color: #FFF;\n    border-left: 10px solid #C6E746;\n}\n\n#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }\n#qunit-tests .pass .test-name               { color: #366097; }\n\n#qunit-tests .pass .test-actual,\n#qunit-tests .pass .test-expected           { color: #999; }\n\n#qunit-banner.qunit-pass                    { background-color: #C6E746; }\n\n/*** Failing Styles */\n\n#qunit-tests li li.fail {\n    color: #710909;\n    background-color: #FFF;\n    border-left: 10px solid #EE5757;\n    white-space: pre;\n}\n\n#qunit-tests > li:last-child {\n    border-radius: 0 0 5px 5px;\n}\n\n#qunit-tests .fail                          { color: #000; background-color: #EE5757; }\n#qunit-tests .fail .test-name,\n#qunit-tests .fail .module-name             { color: #000; }\n\n#qunit-tests .fail .test-actual             { color: #EE5757; }\n#qunit-tests .fail .test-expected           { color: #008000; }\n\n#qunit-banner.qunit-fail                    { background-color: #EE5757; }\n\n/*** Skipped tests */\n\n#qunit-tests .skipped {\n    background-color: #EBECE9;\n}\n\n#qunit-tests .qunit-skipped-label {\n    background-color: #F4FF77;\n    display: inline-block;\n    font-style: normal;\n    color: #366097;\n    line-height: 1.8em;\n    padding: 0 0.5em;\n    margin: -0.4em 0.4em -0.4em 0;\n}\n\n/** Result */\n\n#qunit-testresult {\n    padding: 0.5em 1em 0.5em 1em;\n\n    color: #2B81AF;\n    background-color: #D2E0E6;\n\n    border-bottom: 1px solid #FFF;\n}\n#qunit-testresult .module-name {\n    font-weight: 700;\n}\n\n/** Fixture */\n\n#qunit-fixture {\n    position: absolute;\n    top: -10000px;\n    left: -10000px;\n    width: 1000px;\n    height: 1000px;\n}\n"
  },
  {
    "path": "test/file_reader.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 file reader test</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <link rel=\"stylesheet\" href=\"css/bootstrap-1.4.min.css\">\n        <script src=\"../spark-md5.js\" type=\"text/javascript\"></script>\n\n        <style type=\"text/css\" media=\"screen\">\n            .alert-message {\n                margin-bottom: 5px;\n            }\n\n            input.input-file {\n                padding: 5px;\n                margin-right: 25px;\n                background-color: transparent;\n                line-height: 1;\n                vertical-align: middle;\n            }\n        </style>\n    </head>\n    <body class=\"container\">\n        <h1>SparkMD5 file reader test, incremental and normal md5</h1>\n\n        <h4>Please note that the advantage of doing an incremental md5 is to keep memory usage low.</h4>\n        <p>\n            Keep an eye on the memory usage of your browser. If you got chrome, open about:memory to see all the browsers memory usage (you must refresh continuously).\n            <br/>With normal md5, you should observe slightly faster times but high memory usage (because the entire file need to be read into an array).\n            <br/>With incremental md5, you should observe stable memory usage but slightly higher times.\n            <br/>Be aware that while using normal md5, the browser can crash due to high memory usage.\n        </p>\n\n        <div class=\"actions\">\n            <input type=\"file\" id=\"file\" class=\"input-file span5\"/>\n            <input type=\"button\" id=\"normal\" value=\"Normal\" class=\"btn primary\"/>\n            <input type=\"button\" id=\"incremental\" value=\"Incremental\" class=\"btn primary\"/>\n            <input type=\"button\" id=\"clear\" value=\"Clear\" class=\"btn\"/>\n        </div>\n        <div id=\"log\"></div>\n\n        <script type=\"text/javascript\">\n            var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n                log = document.getElementById('log'),\n                input = document.getElementById('file'),\n                running = false,\n                ua = navigator.userAgent.toLowerCase();\n\n            function registerLog(str, className) {\n                var elem = document.createElement('div');\n\n                elem.innerHTML = str;\n                elem.className = 'alert-message' + (className ? ' '  + className : '');\n                log.appendChild(elem);\n            }\n\n            function doIncrementalTest() {\n                if (running) {\n                    return;\n                }\n\n                if (!input.files.length) {\n                    registerLog('<strong>Please select a file.</strong><br/>');\n                    return;\n                }\n\n                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n                    file = input.files[0],\n                    chunkSize = 2097152,                           // read in chunks of 2MB\n                    chunks = Math.ceil(file.size / chunkSize),\n                    currentChunk = 0,\n                    spark = new SparkMD5.ArrayBuffer(),\n                    time,\n                    uniqueId = 'chunk_' + (new Date().getTime()),\n                    chunkId = null,\n                    fileReader = new FileReader();\n\n                fileReader.onload = function (e) {\n                    if (currentChunk === 0) {\n                        registerLog('Read chunk number <strong id=\"' + uniqueId + '\">' + (currentChunk + 1) + '</strong> of <strong>' + chunks + '</strong><br/>', 'info');\n                    } else {\n                        if (chunkId === null) {\n                            chunkId = document.getElementById(uniqueId);\n                        }\n\n                        chunkId.innerHTML = currentChunk + 1;\n                    }\n\n                    spark.append(e.target.result);                 // append array buffer\n                    currentChunk += 1;\n\n                    if (currentChunk < chunks) {\n                        loadNext();\n                    } else {\n                        running = false;\n                        registerLog('<strong>Finished loading!</strong><br/>', 'success');\n                        registerLog('<strong>Computed hash:</strong> ' + spark.end() + '<br/>', 'success'); // compute hash\n                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');\n                    }\n                };\n\n                fileReader.onerror = function () {\n                    running = false;\n                    registerLog('<strong>Oops, something went wrong.</strong>', 'error');\n                };\n\n                function loadNext() {\n                    var start = currentChunk * chunkSize,\n                        end = start + chunkSize >= file.size ? file.size : start + chunkSize;\n\n                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));\n                }\n\n                running = true;\n                registerLog('<p></p><strong>Starting incremental test (' + file.name + ')</strong><br/>', 'info');\n                time = new Date().getTime();\n                loadNext();\n            }\n\n            function doNormalTest() {\n                if (running) {\n                    return;\n                }\n\n                if (!input.files.length) {\n                    registerLog('<strong>Please select a file.</strong><br/>');\n                    return;\n                }\n\n                var fileReader = new FileReader(),\n                    file = input.files[0],\n                    time;\n\n                fileReader.onload = function (e) {\n                    running = false;\n\n                    if (file.size != e.target.result.byteLength) {\n                        registerLog('<strong>ERROR:</strong> Browser reported success but could not read the file until the end.<br/>', 'error');\n                    } else {\n                        registerLog('<strong>Finished loading!</strong><br/>', 'success');\n                        registerLog('<strong>Computed hash:</strong> ' + SparkMD5.ArrayBuffer.hash(e.target.result) + '<br/>', 'success'); // compute hash\n                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');\n                    }\n                };\n\n                fileReader.onerror = function () {\n                    running = false;\n                    registerLog('<strong>ERROR:</strong> FileReader onerror was triggered, maybe the browser aborted due to high memory usage.<br/>', 'error');\n                };\n\n                running = true;\n                registerLog('<strong>Starting normal test (' + file.name + ')</strong><br/>', 'info');\n                time = new Date().getTime();\n                fileReader.readAsArrayBuffer(file);\n            }\n\n            function clearLog() {\n                if (!running) {\n                    log.innerHTML = '';\n                }\n            }\n\n            if (!('FileReader' in window) || !('File' in window) || !blobSlice) {\n                registerLog('<p><strong>Your browser does not support the FileAPI or slicing of files.</strong></p>', 'error');\n            } else {\n                registerLog('Keep your devtools closed otherwise this example will be a LOT slower', 'info');\n\n                if (/chrome/.test(ua)) {\n                    if (location.protocol === 'file:') {\n                        registerLog('<p><strong>This example might not work in chrome because you are using the file:// protocol.</strong><br/>You can try to start chrome with -allow-file-access-from-files argument or spawn a local server instead. This is a security measure introduced in chrome, please <a target=\\'_blank\\' href=\\'http://code.google.com/p/chromium/issues/detail?id=60889\\'>see</a>.</p>');\n                    }\n                }\n\n                document.getElementById('normal').addEventListener('click', doNormalTest);\n                document.getElementById('incremental').addEventListener('click', doIncrementalTest);\n                document.getElementById('clear').addEventListener('click', clearLog);\n            }\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "test/file_reader_binary.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 file reader test</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <link rel=\"stylesheet\" href=\"css/bootstrap-1.4.min.css\">\n        <script src=\"../spark-md5.js\" type=\"text/javascript\"></script>\n\n        <style type=\"text/css\" media=\"screen\">\n            .alert-message {\n                margin-bottom: 5px;\n            }\n\n            input.input-file {\n                padding: 5px;\n                margin-right: 25px;\n                background-color: transparent;\n                line-height: 1;\n                vertical-align: middle;\n            }\n        </style>\n    </head>\n    <body class=\"container\">\n        <h1>SparkMD5 file reader test, incremental and normal md5</h1>\n\n        <h4>Please note that the advantage of doing an incremental md5 is to keep memory usage low.</h4>\n        <p>\n            Keep an eye on the memory usage of your browser. If you got chrome, open about:memory to see all the browsers memory usage (you must refresh continuously).\n            <br/>With normal md5, you should observe slightly faster times but high memory usage (because the entire file need to be read into an array).\n            <br/>With incremental md5, you should observe stable memory usage but slightly higher times.\n            <br/>Be aware that while using normal md5, the browser can crash due to high memory usage.\n        </p>\n\n        <div class=\"actions\">\n            <input type=\"file\" id=\"file\" class=\"input-file span5\"/>\n            <input type=\"button\" id=\"normal\" value=\"Normal\" class=\"btn primary\"/>\n            <input type=\"button\" id=\"incremental\" value=\"Incremental\" class=\"btn primary\"/>\n            <input type=\"button\" id=\"clear\" value=\"Clear\" class=\"btn\"/>\n        </div>\n        <div id=\"log\"></div>\n\n        <script type=\"text/javascript\">\n            var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n                log = document.getElementById('log'),\n                input = document.getElementById('file'),\n                running = false,\n                ua = navigator.userAgent.toLowerCase();\n\n            function registerLog(str, className) {\n                var elem = document.createElement('div');\n\n                elem.innerHTML = str;\n                elem.className = 'alert-message' + (className ? ' '  + className : '');\n                log.appendChild(elem);\n            }\n\n            function doIncrementalTest() {\n                if (running) {\n                    return;\n                }\n\n                if (!input.files.length) {\n                    registerLog('<strong>Please select a file.</strong><br/>');\n                    return;\n                }\n\n                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n                    file = input.files[0],\n                    chunkSize = 2097152,                           // read in chunks of 2MB\n                    chunks = Math.ceil(file.size / chunkSize),\n                    currentChunk = 0,\n                    spark = new SparkMD5(),\n                    time,\n                    uniqueId = 'chunk_' + (new Date().getTime()),\n                    chunkId = null,\n                    fileReader = new FileReader();\n\n                fileReader.onload = function (e) {\n                    if (currentChunk === 0) {\n                        registerLog('Read chunk number <strong id=\"' + uniqueId + '\">' + (currentChunk + 1) + '</strong> of <strong>' + chunks + '</strong><br/>', 'info');\n                    } else {\n                        if (chunkId === null) {\n                            chunkId = document.getElementById(uniqueId);\n                        }\n\n                        chunkId.innerHTML = currentChunk + 1;\n                    }\n\n                    spark.appendBinary(e.target.result);                 // append array buffer\n                    currentChunk += 1;\n\n                    if (currentChunk < chunks) {\n                        loadNext();\n                    } else {\n                        running = false;\n                        registerLog('<strong>Finished loading!</strong><br/>', 'success');\n                        registerLog('<strong>Computed hash:</strong> ' + spark.end() + '<br/>', 'success'); // compute hash\n                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');\n                    }\n                };\n\n                fileReader.onerror = function () {\n                    running = false;\n                    registerLog('<strong>Oops, something went wrong.</strong>', 'error');\n                };\n\n                function loadNext() {\n                    var start = currentChunk * chunkSize,\n                        end = start + chunkSize >= file.size ? file.size : start + chunkSize;\n\n                    fileReader.readAsBinaryString(blobSlice.call(file, start, end));\n                }\n\n                running = true;\n                registerLog('<p></p><strong>Starting incremental test (' + file.name + ')</strong><br/>', 'info');\n                time = new Date().getTime();\n                loadNext();\n            }\n\n            function doNormalTest() {\n                if (running) {\n                    return;\n                }\n\n                if (!input.files.length) {\n                    registerLog('<strong>Please select a file.</strong><br/>');\n                    return;\n                }\n\n                var fileReader = new FileReader(),\n                    file = input.files[0],\n                    time;\n\n                fileReader.onload = function (e) {\n                    running = false;\n\n                    if (file.size != e.target.result.length) {\n                        registerLog('<strong>ERROR:</strong> Browser reported success but could not read the file until the end.<br/>', 'error');\n                    } else {\n                        registerLog('<strong>Finished loading!</strong><br/>', 'success');\n                        registerLog('<strong>Computed hash:</strong> ' + SparkMD5.hashBinary(e.target.result) + '<br/>', 'success'); // compute hash\n                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');\n                    }\n                };\n\n                fileReader.onerror = function () {\n                    running = false;\n                    registerLog('<strong>ERROR:</strong> FileReader onerror was triggered, maybe the browser aborted due to high memory usage.<br/>', 'error');\n                };\n\n                running = true;\n                registerLog('<strong>Starting normal test (' + file.name + ')</strong><br/>', 'info');\n                time = new Date().getTime();\n                fileReader.readAsBinaryString(file);\n            }\n\n            function clearLog() {\n                if (!running) {\n                    log.innerHTML = '';\n                }\n            }\n\n            if (!('FileReader' in window) || !('File' in window) || !blobSlice) {\n                registerLog('<p><strong>Your browser does not support the FileAPI or slicing of files.</strong></p>', 'error');\n            } else {\n                registerLog('Keep your devtools closed otherwise this example will be a LOT slower', 'info');\n\n                if (/chrome/.test(ua)) {\n                    if (location.protocol === 'file:') {\n                        registerLog('<p><strong>This example might not work in chrome because you are using the file:// protocol.</strong><br/>You can try to start chrome with -allow-file-access-from-files argument or spawn a local server instead. This is a security measure introduced in chrome, please <a target=\\'_blank\\' href=\\'http://code.google.com/p/chromium/issues/detail?id=60889\\'>see</a>.</p>');\n                    }\n                }\n\n                document.getElementById('normal').addEventListener('click', doNormalTest);\n                document.getElementById('incremental').addEventListener('click', doIncrementalTest);\n                document.getElementById('clear').addEventListener('click', clearLog);\n            }\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "test/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 tests</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <script type=\"text/javascript\" src=\"js/qunit-1.16.0.js\"></script>\n        <link rel=\"stylesheet\" href=\"css/qunit-1.16.0.css\" type=\"text/css\" media=\"screen\" />\n        <script src=\"../spark-md5.js\" type=\"text/javascript\"></script>\n    </head>\n    <body>\n\n        <h1 id=\"qunit-header\">SparkMD5 test</h1>\n        <h2 id=\"qunit-banner\"></h2>\n        <div id=\"qunit-testrunner-toolbar\"></div>\n        <h2 id=\"qunit-userAgent\"></h2>\n        <ol id=\"qunit-tests\"></ol>\n        <div id=\"qunit-fixture\"></div>\n        <div id=\"qunit-testresult\"></div>\n\n        <script src=\"specs.js\" type=\"text/javascript\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "test/index.min.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 tests (minified version)</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <script type=\"text/javascript\" src=\"js/qunit-1.16.0.js\"></script>\n        <link rel=\"stylesheet\" href=\"css/qunit-1.16.0.css\" type=\"text/css\" media=\"screen\" />\n        <script src=\"../spark-md5.js\" type=\"text/javascript\"></script>\n    </head>\n    <body>\n\n        <h1 id=\"qunit-header\">SparkMD5 test</h1>\n        <h2 id=\"qunit-banner\"></h2>\n        <div id=\"qunit-testrunner-toolbar\"></div>\n        <h2 id=\"qunit-userAgent\"></h2>\n        <ol id=\"qunit-tests\"></ol>\n        <div id=\"qunit-fixture\"></div>\n        <div id=\"qunit-testresult\"></div>\n\n        <script src=\"specs.js\" type=\"text/javascript\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "test/js/qunit-1.16.0.js",
    "content": "/*!\n * QUnit 1.16.0\n * http://qunitjs.com/\n *\n * Copyright 2006, 2014 jQuery Foundation and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-03T16:32Z\n */\n\n(function( window ) {\n\nvar QUnit,\n    config,\n    onErrorFnPrev,\n    loggingCallbacks = {},\n    fileName = ( sourceFromStacktrace( 0 ) || \"\" ).replace( /(:\\d+)+\\)?/, \"\" ).replace( /.+\\//, \"\" ),\n    toString = Object.prototype.toString,\n    hasOwn = Object.prototype.hasOwnProperty,\n    // Keep a local reference to Date (GH-283)\n    Date = window.Date,\n    now = Date.now || function() {\n        return new Date().getTime();\n    },\n    globalStartCalled = false,\n    runStarted = false,\n    setTimeout = window.setTimeout,\n    clearTimeout = window.clearTimeout,\n    defined = {\n        document: window.document !== undefined,\n        setTimeout: window.setTimeout !== undefined,\n        sessionStorage: (function() {\n            var x = \"qunit-test-string\";\n            try {\n                sessionStorage.setItem( x, x );\n                sessionStorage.removeItem( x );\n                return true;\n            } catch ( e ) {\n                return false;\n            }\n        }())\n    },\n    /**\n     * Provides a normalized error string, correcting an issue\n     * with IE 7 (and prior) where Error.prototype.toString is\n     * not properly implemented\n     *\n     * Based on http://es5.github.com/#x15.11.4.4\n     *\n     * @param {String|Error} error\n     * @return {String} error message\n     */\n    errorString = function( error ) {\n        var name, message,\n            errorString = error.toString();\n        if ( errorString.substring( 0, 7 ) === \"[object\" ) {\n            name = error.name ? error.name.toString() : \"Error\";\n            message = error.message ? error.message.toString() : \"\";\n            if ( name && message ) {\n                return name + \": \" + message;\n            } else if ( name ) {\n                return name;\n            } else if ( message ) {\n                return message;\n            } else {\n                return \"Error\";\n            }\n        } else {\n            return errorString;\n        }\n    },\n    /**\n     * Makes a clone of an object using only Array or Object as base,\n     * and copies over the own enumerable properties.\n     *\n     * @param {Object} obj\n     * @return {Object} New object with only the own properties (recursively).\n     */\n    objectValues = function( obj ) {\n        var key, val,\n            vals = QUnit.is( \"array\", obj ) ? [] : {};\n        for ( key in obj ) {\n            if ( hasOwn.call( obj, key ) ) {\n                val = obj[ key ];\n                vals[ key ] = val === Object( val ) ? objectValues( val ) : val;\n            }\n        }\n        return vals;\n    };\n\nQUnit = {};\n\n/**\n * Config object: Maintain internal state\n * Later exposed as QUnit.config\n * `config` initialized at top of scope\n */\nconfig = {\n    // The queue of tests to run\n    queue: [],\n\n    // block until document ready\n    blocking: true,\n\n    // when enabled, show only failing tests\n    // gets persisted through sessionStorage and can be changed in UI via checkbox\n    hidepassed: false,\n\n    // by default, run previously failed tests first\n    // very useful in combination with \"Hide passed tests\" checked\n    reorder: true,\n\n    // by default, modify document.title when suite is done\n    altertitle: true,\n\n    // by default, scroll to top of the page when suite is done\n    scrolltop: true,\n\n    // when enabled, all tests must call expect()\n    requireExpects: false,\n\n    // add checkboxes that are persisted in the query-string\n    // when enabled, the id is set to `true` as a `QUnit.config` property\n    urlConfig: [\n        {\n            id: \"hidepassed\",\n            label: \"Hide passed tests\",\n            tooltip: \"Only show tests and assertions that fail. Stored as query-strings.\"\n        },\n        {\n            id: \"noglobals\",\n            label: \"Check for Globals\",\n            tooltip: \"Enabling this will test if any test introduces new properties on the \" +\n                \"`window` object. Stored as query-strings.\"\n        },\n        {\n            id: \"notrycatch\",\n            label: \"No try-catch\",\n            tooltip: \"Enabling this will run tests outside of a try-catch block. Makes debugging \" +\n                \"exceptions in IE reasonable. Stored as query-strings.\"\n        }\n    ],\n\n    // Set of all modules.\n    modules: [],\n\n    // The first unnamed module\n    currentModule: {\n        name: \"\",\n        tests: []\n    },\n\n    callbacks: {}\n};\n\n// Push a loose unnamed module to the modules collection\nconfig.modules.push( config.currentModule );\n\n// Initialize more QUnit.config and QUnit.urlParams\n(function() {\n    var i, current,\n        location = window.location || { search: \"\", protocol: \"file:\" },\n        params = location.search.slice( 1 ).split( \"&\" ),\n        length = params.length,\n        urlParams = {};\n\n    if ( params[ 0 ] ) {\n        for ( i = 0; i < length; i++ ) {\n            current = params[ i ].split( \"=\" );\n            current[ 0 ] = decodeURIComponent( current[ 0 ] );\n\n            // allow just a key to turn on a flag, e.g., test.html?noglobals\n            current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;\n            if ( urlParams[ current[ 0 ] ] ) {\n                urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );\n            } else {\n                urlParams[ current[ 0 ] ] = current[ 1 ];\n            }\n        }\n    }\n\n    QUnit.urlParams = urlParams;\n\n    // String search anywhere in moduleName+testName\n    config.filter = urlParams.filter;\n\n    config.testId = [];\n    if ( urlParams.testId ) {\n\n        // Ensure that urlParams.testId is an array\n        urlParams.testId = [].concat( urlParams.testId );\n        for ( i = 0; i < urlParams.testId.length; i++ ) {\n            config.testId.push( urlParams.testId[ i ] );\n        }\n    }\n\n    // Figure out if we're running the tests from a server or not\n    QUnit.isLocal = location.protocol === \"file:\";\n}());\n\n// Root QUnit object.\n// `QUnit` initialized at top of scope\nextend( QUnit, {\n\n    // call on start of module test to prepend name to all tests\n    module: function( name, testEnvironment ) {\n        var currentModule = {\n            name: name,\n            testEnvironment: testEnvironment,\n            tests: []\n        };\n\n        // DEPRECATED: handles setup/teardown functions,\n        // beforeEach and afterEach should be used instead\n        if ( testEnvironment && testEnvironment.setup ) {\n            testEnvironment.beforeEach = testEnvironment.setup;\n            delete testEnvironment.setup;\n        }\n        if ( testEnvironment && testEnvironment.teardown ) {\n            testEnvironment.afterEach = testEnvironment.teardown;\n            delete testEnvironment.teardown;\n        }\n\n        config.modules.push( currentModule );\n        config.currentModule = currentModule;\n    },\n\n    // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.\n    asyncTest: function( testName, expected, callback ) {\n        if ( arguments.length === 2 ) {\n            callback = expected;\n            expected = null;\n        }\n\n        QUnit.test( testName, expected, callback, true );\n    },\n\n    test: function( testName, expected, callback, async ) {\n        var test;\n\n        if ( arguments.length === 2 ) {\n            callback = expected;\n            expected = null;\n        }\n\n        test = new Test({\n            testName: testName,\n            expected: expected,\n            async: async,\n            callback: callback\n        });\n\n        test.queue();\n    },\n\n    skip: function( testName ) {\n        var test = new Test({\n            testName: testName,\n            skip: true\n        });\n\n        test.queue();\n    },\n\n    // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.\n    // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.\n    start: function( count ) {\n        var globalStartAlreadyCalled = globalStartCalled;\n\n        if ( !config.current ) {\n            globalStartCalled = true;\n\n            if ( runStarted ) {\n                throw new Error( \"Called start() outside of a test context while already started\" );\n            } else if ( globalStartAlreadyCalled || count > 1 ) {\n                throw new Error( \"Called start() outside of a test context too many times\" );\n            } else if ( config.autostart ) {\n                throw new Error( \"Called start() outside of a test context when \" +\n                    \"QUnit.config.autostart was true\" );\n            } else if ( !config.pageLoaded ) {\n\n                // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it\n                config.autostart = true;\n                return;\n            }\n        } else {\n\n            // If a test is running, adjust its semaphore\n            config.current.semaphore -= count || 1;\n\n            // Don't start until equal number of stop-calls\n            if ( config.current.semaphore > 0 ) {\n                return;\n            }\n\n            // throw an Error if start is called more often than stop\n            if ( config.current.semaphore < 0 ) {\n                config.current.semaphore = 0;\n\n                QUnit.pushFailure(\n                    \"Called start() while already started (test's semaphore was 0 already)\",\n                    sourceFromStacktrace( 2 )\n                );\n                return;\n            }\n        }\n\n        resumeProcessing();\n    },\n\n    // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.\n    stop: function( count ) {\n\n        // If there isn't a test running, don't allow QUnit.stop() to be called\n        if ( !config.current ) {\n            throw new Error( \"Called stop() outside of a test context\" );\n        }\n\n        // If a test is running, adjust its semaphore\n        config.current.semaphore += count || 1;\n\n        pauseProcessing();\n    },\n\n    config: config,\n\n    // Safe object type checking\n    is: function( type, obj ) {\n        return QUnit.objectType( obj ) === type;\n    },\n\n    objectType: function( obj ) {\n        if ( typeof obj === \"undefined\" ) {\n            return \"undefined\";\n        }\n\n        // Consider: typeof null === object\n        if ( obj === null ) {\n            return \"null\";\n        }\n\n        var match = toString.call( obj ).match( /^\\[object\\s(.*)\\]$/ ),\n            type = match && match[ 1 ] || \"\";\n\n        switch ( type ) {\n            case \"Number\":\n                if ( isNaN( obj ) ) {\n                    return \"nan\";\n                }\n                return \"number\";\n            case \"String\":\n            case \"Boolean\":\n            case \"Array\":\n            case \"Date\":\n            case \"RegExp\":\n            case \"Function\":\n                return type.toLowerCase();\n        }\n        if ( typeof obj === \"object\" ) {\n            return \"object\";\n        }\n        return undefined;\n    },\n\n    url: function( params ) {\n        params = extend( extend( {}, QUnit.urlParams ), params );\n        var key,\n            querystring = \"?\";\n\n        for ( key in params ) {\n            if ( hasOwn.call( params, key ) ) {\n                querystring += encodeURIComponent( key );\n                if ( params[ key ] !== true ) {\n                    querystring += \"=\" + encodeURIComponent( params[ key ] );\n                }\n                querystring += \"&\";\n            }\n        }\n        return location.protocol + \"//\" + location.host +\n            location.pathname + querystring.slice( 0, -1 );\n    },\n\n    extend: extend,\n\n    load: function() {\n        config.pageLoaded = true;\n\n        // Initialize the configuration options\n        extend( config, {\n            stats: { all: 0, bad: 0 },\n            moduleStats: { all: 0, bad: 0 },\n            started: 0,\n            updateRate: 1000,\n            autostart: true,\n            filter: \"\"\n        }, true );\n\n        config.blocking = false;\n\n        if ( config.autostart ) {\n            resumeProcessing();\n        }\n    }\n});\n\n// Register logging callbacks\n(function() {\n    var i, l, key,\n        callbacks = [ \"begin\", \"done\", \"log\", \"testStart\", \"testDone\",\n            \"moduleStart\", \"moduleDone\" ];\n\n    function registerLoggingCallback( key ) {\n        var loggingCallback = function( callback ) {\n            if ( QUnit.objectType( callback ) !== \"function\" ) {\n                throw new Error(\n                    \"QUnit logging methods require a callback function as their first parameters.\"\n                );\n            }\n\n            config.callbacks[ key ].push( callback );\n        };\n\n        // DEPRECATED: This will be removed on QUnit 2.0.0+\n        // Stores the registered functions allowing restoring\n        // at verifyLoggingCallbacks() if modified\n        loggingCallbacks[ key ] = loggingCallback;\n\n        return loggingCallback;\n    }\n\n    for ( i = 0, l = callbacks.length; i < l; i++ ) {\n        key = callbacks[ i ];\n\n        // Initialize key collection of logging callback\n        if ( QUnit.objectType( config.callbacks[ key ] ) === \"undefined\" ) {\n            config.callbacks[ key ] = [];\n        }\n\n        QUnit[ key ] = registerLoggingCallback( key );\n    }\n})();\n\n// `onErrorFnPrev` initialized at top of scope\n// Preserve other handlers\nonErrorFnPrev = window.onerror;\n\n// Cover uncaught exceptions\n// Returning true will suppress the default browser handler,\n// returning false will let it run.\nwindow.onerror = function( error, filePath, linerNr ) {\n    var ret = false;\n    if ( onErrorFnPrev ) {\n        ret = onErrorFnPrev( error, filePath, linerNr );\n    }\n\n    // Treat return value as window.onerror itself does,\n    // Only do our handling if not suppressed.\n    if ( ret !== true ) {\n        if ( QUnit.config.current ) {\n            if ( QUnit.config.current.ignoreGlobalErrors ) {\n                return true;\n            }\n            QUnit.pushFailure( error, filePath + \":\" + linerNr );\n        } else {\n            QUnit.test( \"global failure\", extend(function() {\n                QUnit.pushFailure( error, filePath + \":\" + linerNr );\n            }, { validTest: true } ) );\n        }\n        return false;\n    }\n\n    return ret;\n};\n\nfunction done() {\n    var runtime, passed;\n\n    config.autorun = true;\n\n    // Log the last module results\n    if ( config.previousModule ) {\n        runLoggingCallbacks( \"moduleDone\", {\n            name: config.previousModule.name,\n            tests: config.previousModule.tests,\n            failed: config.moduleStats.bad,\n            passed: config.moduleStats.all - config.moduleStats.bad,\n            total: config.moduleStats.all,\n            runtime: now() - config.moduleStats.started\n        });\n    }\n    delete config.previousModule;\n\n    runtime = now() - config.started;\n    passed = config.stats.all - config.stats.bad;\n\n    runLoggingCallbacks( \"done\", {\n        failed: config.stats.bad,\n        passed: passed,\n        total: config.stats.all,\n        runtime: runtime\n    });\n}\n\n// Doesn't support IE6 to IE9\n// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack\nfunction extractStacktrace( e, offset ) {\n    offset = offset === undefined ? 4 : offset;\n\n    var stack, include, i;\n\n    if ( e.stacktrace ) {\n\n        // Opera 12.x\n        return e.stacktrace.split( \"\\n\" )[ offset + 3 ];\n    } else if ( e.stack ) {\n\n        // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node\n        stack = e.stack.split( \"\\n\" );\n        if ( /^error$/i.test( stack[ 0 ] ) ) {\n            stack.shift();\n        }\n        if ( fileName ) {\n            include = [];\n            for ( i = offset; i < stack.length; i++ ) {\n                if ( stack[ i ].indexOf( fileName ) !== -1 ) {\n                    break;\n                }\n                include.push( stack[ i ] );\n            }\n            if ( include.length ) {\n                return include.join( \"\\n\" );\n            }\n        }\n        return stack[ offset ];\n    } else if ( e.sourceURL ) {\n\n        // Safari < 6\n        // exclude useless self-reference for generated Error objects\n        if ( /qunit.js$/.test( e.sourceURL ) ) {\n            return;\n        }\n\n        // for actual exceptions, this is useful\n        return e.sourceURL + \":\" + e.line;\n    }\n}\n\nfunction sourceFromStacktrace( offset ) {\n    var e = new Error();\n    if ( !e.stack ) {\n        try {\n            throw e;\n        } catch ( err ) {\n            // This should already be true in most browsers\n            e = err;\n        }\n    }\n    return extractStacktrace( e, offset );\n}\n\nfunction synchronize( callback, last ) {\n    if ( QUnit.objectType( callback ) === \"array\" ) {\n        while ( callback.length ) {\n            synchronize( callback.shift() );\n        }\n        return;\n    }\n    config.queue.push( callback );\n\n    if ( config.autorun && !config.blocking ) {\n        process( last );\n    }\n}\n\nfunction process( last ) {\n    function next() {\n        process( last );\n    }\n    var start = now();\n    config.depth = config.depth ? config.depth + 1 : 1;\n\n    while ( config.queue.length && !config.blocking ) {\n        if ( !defined.setTimeout || config.updateRate <= 0 ||\n                ( ( now() - start ) < config.updateRate ) ) {\n            if ( config.current ) {\n\n                // Reset async tracking for each phase of the Test lifecycle\n                config.current.usedAsync = false;\n            }\n            config.queue.shift()();\n        } else {\n            setTimeout( next, 13 );\n            break;\n        }\n    }\n    config.depth--;\n    if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {\n        done();\n    }\n}\n\nfunction begin() {\n    var i, l,\n        modulesLog = [];\n\n    // If the test run hasn't officially begun yet\n    if ( !config.started ) {\n\n        // Record the time of the test run's beginning\n        config.started = now();\n\n        verifyLoggingCallbacks();\n\n        // Delete the loose unnamed module if unused.\n        if ( config.modules[ 0 ].name === \"\" && config.modules[ 0 ].tests.length === 0 ) {\n            config.modules.shift();\n        }\n\n        // Avoid unnecessary information by not logging modules' test environments\n        for ( i = 0, l = config.modules.length; i < l; i++ ) {\n            modulesLog.push({\n                name: config.modules[ i ].name,\n                tests: config.modules[ i ].tests\n            });\n        }\n\n        // The test run is officially beginning now\n        runLoggingCallbacks( \"begin\", {\n            totalTests: Test.count,\n            modules: modulesLog\n        });\n    }\n\n    config.blocking = false;\n    process( true );\n}\n\nfunction resumeProcessing() {\n    runStarted = true;\n\n    // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)\n    if ( defined.setTimeout ) {\n        setTimeout(function() {\n            if ( config.current && config.current.semaphore > 0 ) {\n                return;\n            }\n            if ( config.timeout ) {\n                clearTimeout( config.timeout );\n            }\n\n            begin();\n        }, 13 );\n    } else {\n        begin();\n    }\n}\n\nfunction pauseProcessing() {\n    config.blocking = true;\n\n    if ( config.testTimeout && defined.setTimeout ) {\n        clearTimeout( config.timeout );\n        config.timeout = setTimeout(function() {\n            if ( config.current ) {\n                config.current.semaphore = 0;\n                QUnit.pushFailure( \"Test timed out\", sourceFromStacktrace( 2 ) );\n            } else {\n                throw new Error( \"Test timed out\" );\n            }\n            resumeProcessing();\n        }, config.testTimeout );\n    }\n}\n\nfunction saveGlobal() {\n    config.pollution = [];\n\n    if ( config.noglobals ) {\n        for ( var key in window ) {\n            if ( hasOwn.call( window, key ) ) {\n                // in Opera sometimes DOM element ids show up here, ignore them\n                if ( /^qunit-test-output/.test( key ) ) {\n                    continue;\n                }\n                config.pollution.push( key );\n            }\n        }\n    }\n}\n\nfunction checkPollution() {\n    var newGlobals,\n        deletedGlobals,\n        old = config.pollution;\n\n    saveGlobal();\n\n    newGlobals = diff( config.pollution, old );\n    if ( newGlobals.length > 0 ) {\n        QUnit.pushFailure( \"Introduced global variable(s): \" + newGlobals.join( \", \" ) );\n    }\n\n    deletedGlobals = diff( old, config.pollution );\n    if ( deletedGlobals.length > 0 ) {\n        QUnit.pushFailure( \"Deleted global variable(s): \" + deletedGlobals.join( \", \" ) );\n    }\n}\n\n// returns a new Array with the elements that are in a but not in b\nfunction diff( a, b ) {\n    var i, j,\n        result = a.slice();\n\n    for ( i = 0; i < result.length; i++ ) {\n        for ( j = 0; j < b.length; j++ ) {\n            if ( result[ i ] === b[ j ] ) {\n                result.splice( i, 1 );\n                i--;\n                break;\n            }\n        }\n    }\n    return result;\n}\n\nfunction extend( a, b, undefOnly ) {\n    for ( var prop in b ) {\n        if ( hasOwn.call( b, prop ) ) {\n\n            // Avoid \"Member not found\" error in IE8 caused by messing with window.constructor\n            if ( !( prop === \"constructor\" && a === window ) ) {\n                if ( b[ prop ] === undefined ) {\n                    delete a[ prop ];\n                } else if ( !( undefOnly && typeof a[ prop ] !== \"undefined\" ) ) {\n                    a[ prop ] = b[ prop ];\n                }\n            }\n        }\n    }\n\n    return a;\n}\n\nfunction runLoggingCallbacks( key, args ) {\n    var i, l, callbacks;\n\n    callbacks = config.callbacks[ key ];\n    for ( i = 0, l = callbacks.length; i < l; i++ ) {\n        callbacks[ i ]( args );\n    }\n}\n\n// DEPRECATED: This will be removed on 2.0.0+\n// This function verifies if the loggingCallbacks were modified by the user\n// If so, it will restore it, assign the given callback and print a console warning\nfunction verifyLoggingCallbacks() {\n    var loggingCallback, userCallback;\n\n    for ( loggingCallback in loggingCallbacks ) {\n        if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {\n\n            userCallback = QUnit[ loggingCallback ];\n\n            // Restore the callback function\n            QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];\n\n            // Assign the deprecated given callback\n            QUnit[ loggingCallback ]( userCallback );\n\n            if ( window.console && window.console.warn ) {\n                window.console.warn(\n                    \"QUnit.\" + loggingCallback + \" was replaced with a new value.\\n\" +\n                    \"Please, check out the documentation on how to apply logging callbacks.\\n\" +\n                    \"Reference: http://api.qunitjs.com/category/callbacks/\"\n                );\n            }\n        }\n    }\n}\n\n// from jquery.js\nfunction inArray( elem, array ) {\n    if ( array.indexOf ) {\n        return array.indexOf( elem );\n    }\n\n    for ( var i = 0, length = array.length; i < length; i++ ) {\n        if ( array[ i ] === elem ) {\n            return i;\n        }\n    }\n\n    return -1;\n}\n\nfunction Test( settings ) {\n    var i, l;\n\n    ++Test.count;\n\n    extend( this, settings );\n    this.assertions = [];\n    this.semaphore = 0;\n    this.usedAsync = false;\n    this.module = config.currentModule;\n    this.stack = sourceFromStacktrace( 3 );\n\n    // Register unique strings\n    for ( i = 0, l = this.module.tests; i < l.length; i++ ) {\n        if ( this.module.tests[ i ].name === this.testName ) {\n            this.testName += \" \";\n        }\n    }\n\n    this.testId = generateHash( this.module.name, this.testName );\n\n    this.module.tests.push({\n        name: this.testName,\n        testId: this.testId\n    });\n\n    if ( settings.skip ) {\n\n        // Skipped tests will fully ignore any sent callback\n        this.callback = function() {};\n        this.async = false;\n        this.expected = 0;\n    } else {\n        this.assert = new Assert( this );\n    }\n}\n\nTest.count = 0;\n\nTest.prototype = {\n    before: function() {\n        if (\n\n            // Emit moduleStart when we're switching from one module to another\n            this.module !== config.previousModule ||\n\n                // They could be equal (both undefined) but if the previousModule property doesn't\n                // yet exist it means this is the first test in a suite that isn't wrapped in a\n                // module, in which case we'll just emit a moduleStart event for 'undefined'.\n                // Without this, reporters can get testStart before moduleStart  which is a problem.\n                !hasOwn.call( config, \"previousModule\" )\n        ) {\n            if ( hasOwn.call( config, \"previousModule\" ) ) {\n                runLoggingCallbacks( \"moduleDone\", {\n                    name: config.previousModule.name,\n                    tests: config.previousModule.tests,\n                    failed: config.moduleStats.bad,\n                    passed: config.moduleStats.all - config.moduleStats.bad,\n                    total: config.moduleStats.all,\n                    runtime: now() - config.moduleStats.started\n                });\n            }\n            config.previousModule = this.module;\n            config.moduleStats = { all: 0, bad: 0, started: now() };\n            runLoggingCallbacks( \"moduleStart\", {\n                name: this.module.name,\n                tests: this.module.tests\n            });\n        }\n\n        config.current = this;\n\n        this.testEnvironment = extend( {}, this.module.testEnvironment );\n        delete this.testEnvironment.beforeEach;\n        delete this.testEnvironment.afterEach;\n\n        this.started = now();\n        runLoggingCallbacks( \"testStart\", {\n            name: this.testName,\n            module: this.module.name,\n            testId: this.testId\n        });\n\n        if ( !config.pollution ) {\n            saveGlobal();\n        }\n    },\n\n    run: function() {\n        var promise;\n\n        config.current = this;\n\n        if ( this.async ) {\n            QUnit.stop();\n        }\n\n        this.callbackStarted = now();\n\n        if ( config.notrycatch ) {\n            promise = this.callback.call( this.testEnvironment, this.assert );\n            this.resolvePromise( promise );\n            return;\n        }\n\n        try {\n            promise = this.callback.call( this.testEnvironment, this.assert );\n            this.resolvePromise( promise );\n        } catch ( e ) {\n            this.pushFailure( \"Died on test #\" + ( this.assertions.length + 1 ) + \" \" +\n                this.stack + \": \" + ( e.message || e ), extractStacktrace( e, 0 ) );\n\n            // else next test will carry the responsibility\n            saveGlobal();\n\n            // Restart the tests if they're blocking\n            if ( config.blocking ) {\n                QUnit.start();\n            }\n        }\n    },\n\n    after: function() {\n        checkPollution();\n    },\n\n    queueHook: function( hook, hookName ) {\n        var promise,\n            test = this;\n        return function runHook() {\n            config.current = test;\n            if ( config.notrycatch ) {\n                promise = hook.call( test.testEnvironment, test.assert );\n                test.resolvePromise( promise, hookName );\n                return;\n            }\n            try {\n                promise = hook.call( test.testEnvironment, test.assert );\n                test.resolvePromise( promise, hookName );\n            } catch ( error ) {\n                test.pushFailure( hookName + \" failed on \" + test.testName + \": \" +\n                    ( error.message || error ), extractStacktrace( error, 0 ) );\n            }\n        };\n    },\n\n    // Currently only used for module level hooks, can be used to add global level ones\n    hooks: function( handler ) {\n        var hooks = [];\n\n        // Hooks are ignored on skipped tests\n        if ( this.skip ) {\n            return hooks;\n        }\n\n        if ( this.module.testEnvironment &&\n                QUnit.objectType( this.module.testEnvironment[ handler ] ) === \"function\" ) {\n            hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );\n        }\n\n        return hooks;\n    },\n\n    finish: function() {\n        config.current = this;\n        if ( config.requireExpects && this.expected === null ) {\n            this.pushFailure( \"Expected number of assertions to be defined, but expect() was \" +\n                \"not called.\", this.stack );\n        } else if ( this.expected !== null && this.expected !== this.assertions.length ) {\n            this.pushFailure( \"Expected \" + this.expected + \" assertions, but \" +\n                this.assertions.length + \" were run\", this.stack );\n        } else if ( this.expected === null && !this.assertions.length ) {\n            this.pushFailure( \"Expected at least one assertion, but none were run - call \" +\n                \"expect(0) to accept zero assertions.\", this.stack );\n        }\n\n        var i,\n            bad = 0;\n\n        this.runtime = now() - this.started;\n        config.stats.all += this.assertions.length;\n        config.moduleStats.all += this.assertions.length;\n\n        for ( i = 0; i < this.assertions.length; i++ ) {\n            if ( !this.assertions[ i ].result ) {\n                bad++;\n                config.stats.bad++;\n                config.moduleStats.bad++;\n            }\n        }\n\n        runLoggingCallbacks( \"testDone\", {\n            name: this.testName,\n            module: this.module.name,\n            skipped: !!this.skip,\n            failed: bad,\n            passed: this.assertions.length - bad,\n            total: this.assertions.length,\n            runtime: this.runtime,\n\n            // HTML Reporter use\n            assertions: this.assertions,\n            testId: this.testId,\n\n            // DEPRECATED: this property will be removed in 2.0.0, use runtime instead\n            duration: this.runtime\n        });\n\n        // QUnit.reset() is deprecated and will be replaced for a new\n        // fixture reset function on QUnit 2.0/2.1.\n        // It's still called here for backwards compatibility handling\n        QUnit.reset();\n\n        config.current = undefined;\n    },\n\n    queue: function() {\n        var bad,\n            test = this;\n\n        if ( !this.valid() ) {\n            return;\n        }\n\n        function run() {\n\n            // each of these can by async\n            synchronize([\n                function() {\n                    test.before();\n                },\n\n                test.hooks( \"beforeEach\" ),\n\n                function() {\n                    test.run();\n                },\n\n                test.hooks( \"afterEach\" ).reverse(),\n\n                function() {\n                    test.after();\n                },\n                function() {\n                    test.finish();\n                }\n            ]);\n        }\n\n        // `bad` initialized at top of scope\n        // defer when previous test run passed, if storage is available\n        bad = QUnit.config.reorder && defined.sessionStorage &&\n                +sessionStorage.getItem( \"qunit-test-\" + this.module.name + \"-\" + this.testName );\n\n        if ( bad ) {\n            run();\n        } else {\n            synchronize( run, true );\n        }\n    },\n\n    push: function( result, actual, expected, message ) {\n        var source,\n            details = {\n                module: this.module.name,\n                name: this.testName,\n                result: result,\n                message: message,\n                actual: actual,\n                expected: expected,\n                testId: this.testId,\n                runtime: now() - this.started\n            };\n\n        if ( !result ) {\n            source = sourceFromStacktrace();\n\n            if ( source ) {\n                details.source = source;\n            }\n        }\n\n        runLoggingCallbacks( \"log\", details );\n\n        this.assertions.push({\n            result: !!result,\n            message: message\n        });\n    },\n\n    pushFailure: function( message, source, actual ) {\n        if ( !this instanceof Test ) {\n            throw new Error( \"pushFailure() assertion outside test context, was \" +\n                sourceFromStacktrace( 2 ) );\n        }\n\n        var details = {\n                module: this.module.name,\n                name: this.testName,\n                result: false,\n                message: message || \"error\",\n                actual: actual || null,\n                testId: this.testId,\n                runtime: now() - this.started\n            };\n\n        if ( source ) {\n            details.source = source;\n        }\n\n        runLoggingCallbacks( \"log\", details );\n\n        this.assertions.push({\n            result: false,\n            message: message\n        });\n    },\n\n    resolvePromise: function( promise, phase ) {\n        var then, message,\n            test = this;\n        if ( promise != null ) {\n            then = promise.then;\n            if ( QUnit.objectType( then ) === \"function\" ) {\n                QUnit.stop();\n                then.call(\n                    promise,\n                    QUnit.start,\n                    function( error ) {\n                        message = \"Promise rejected \" +\n                            ( !phase ? \"during\" : phase.replace( /Each$/, \"\" ) ) +\n                            \" \" + test.testName + \": \" + ( error.message || error );\n                        test.pushFailure( message, extractStacktrace( error, 0 ) );\n\n                        // else next test will carry the responsibility\n                        saveGlobal();\n\n                        // Unblock\n                        QUnit.start();\n                    }\n                );\n            }\n        }\n    },\n\n    valid: function() {\n        var include,\n            filter = config.filter && config.filter.toLowerCase(),\n            module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),\n            fullName = ( this.module.name + \": \" + this.testName ).toLowerCase();\n\n        // Internally-generated tests are always valid\n        if ( this.callback && this.callback.validTest ) {\n            return true;\n        }\n\n        if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {\n            return false;\n        }\n\n        if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {\n            return false;\n        }\n\n        if ( !filter ) {\n            return true;\n        }\n\n        include = filter.charAt( 0 ) !== \"!\";\n        if ( !include ) {\n            filter = filter.slice( 1 );\n        }\n\n        // If the filter matches, we need to honour include\n        if ( fullName.indexOf( filter ) !== -1 ) {\n            return include;\n        }\n\n        // Otherwise, do the opposite\n        return !include;\n    }\n\n};\n\n// Resets the test setup. Useful for tests that modify the DOM.\n/*\nDEPRECATED: Use multiple tests instead of resetting inside a test.\nUse testStart or testDone for custom cleanup.\nThis method will throw an error in 2.0, and will be removed in 2.1\n*/\nQUnit.reset = function() {\n\n    // Return on non-browser environments\n    // This is necessary to not break on node tests\n    if ( typeof window === \"undefined\" ) {\n        return;\n    }\n\n    var fixture = defined.document && document.getElementById &&\n            document.getElementById( \"qunit-fixture\" );\n\n    if ( fixture ) {\n        fixture.innerHTML = config.fixture;\n    }\n};\n\nQUnit.pushFailure = function() {\n    if ( !QUnit.config.current ) {\n        throw new Error( \"pushFailure() assertion outside test context, in \" +\n            sourceFromStacktrace( 2 ) );\n    }\n\n    // Gets current test obj\n    var currentTest = QUnit.config.current;\n\n    return currentTest.pushFailure.apply( currentTest, arguments );\n};\n\n// Based on Java's String.hashCode, a simple but not\n// rigorously collision resistant hashing function\nfunction generateHash( module, testName ) {\n    var hex,\n        i = 0,\n        hash = 0,\n        str = module + \"\\x1C\" + testName,\n        len = str.length;\n\n    for ( ; i < len; i++ ) {\n        hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );\n        hash |= 0;\n    }\n\n    // Convert the possibly negative integer hash code into an 8 character hex string, which isn't\n    // strictly necessary but increases user understanding that the id is a SHA-like hash\n    hex = ( 0x100000000 + hash ).toString( 16 );\n    if ( hex.length < 8 ) {\n        hex = \"0000000\" + hex;\n    }\n\n    return hex.slice( -8 );\n}\n\nfunction Assert( testContext ) {\n    this.test = testContext;\n}\n\n// Assert helpers\nQUnit.assert = Assert.prototype = {\n\n    // Specify the number of expected assertions to guarantee that failed test\n    // (no assertions are run at all) don't slip through.\n    expect: function( asserts ) {\n        if ( arguments.length === 1 ) {\n            this.test.expected = asserts;\n        } else {\n            return this.test.expected;\n        }\n    },\n\n    // Increment this Test's semaphore counter, then return a single-use function that\n    // decrements that counter a maximum of once.\n    async: function() {\n        var test = this.test,\n            popped = false;\n\n        test.semaphore += 1;\n        test.usedAsync = true;\n        pauseProcessing();\n\n        return function done() {\n            if ( !popped ) {\n                test.semaphore -= 1;\n                popped = true;\n                resumeProcessing();\n            } else {\n                test.pushFailure( \"Called the callback returned from `assert.async` more than once\",\n                    sourceFromStacktrace( 2 ) );\n            }\n        };\n    },\n\n    // Exports test.push() to the user API\n    push: function( /* result, actual, expected, message */ ) {\n        var assert = this,\n            currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;\n\n        // Backwards compatibility fix.\n        // Allows the direct use of global exported assertions and QUnit.assert.*\n        // Although, it's use is not recommended as it can leak assertions\n        // to other tests from async tests, because we only get a reference to the current test,\n        // not exactly the test where assertion were intended to be called.\n        if ( !currentTest ) {\n            throw new Error( \"assertion outside test context, in \" + sourceFromStacktrace( 2 ) );\n        }\n\n        if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {\n            currentTest.pushFailure( \"Assertion after the final `assert.async` was resolved\",\n                sourceFromStacktrace( 2 ) );\n\n            // Allow this assertion to continue running anyway...\n        }\n\n        if ( !( assert instanceof Assert ) ) {\n            assert = currentTest.assert;\n        }\n        return assert.test.push.apply( assert.test, arguments );\n    },\n\n    /**\n     * Asserts rough true-ish result.\n     * @name ok\n     * @function\n     * @example ok( \"asdfasdf\".length > 5, \"There must be at least 5 chars\" );\n     */\n    ok: function( result, message ) {\n        message = message || ( result ? \"okay\" : \"failed, expected argument to be truthy, was: \" +\n            QUnit.dump.parse( result ) );\n        this.push( !!result, result, true, message );\n    },\n\n    /**\n     * Assert that the first two arguments are equal, with an optional message.\n     * Prints out both actual and expected values.\n     * @name equal\n     * @function\n     * @example equal( format( \"{0} bytes.\", 2), \"2 bytes.\", \"replaces {0} with next argument\" );\n     */\n    equal: function( actual, expected, message ) {\n        /*jshint eqeqeq:false */\n        this.push( expected == actual, actual, expected, message );\n    },\n\n    /**\n     * @name notEqual\n     * @function\n     */\n    notEqual: function( actual, expected, message ) {\n        /*jshint eqeqeq:false */\n        this.push( expected != actual, actual, expected, message );\n    },\n\n    /**\n     * @name propEqual\n     * @function\n     */\n    propEqual: function( actual, expected, message ) {\n        actual = objectValues( actual );\n        expected = objectValues( expected );\n        this.push( QUnit.equiv( actual, expected ), actual, expected, message );\n    },\n\n    /**\n     * @name notPropEqual\n     * @function\n     */\n    notPropEqual: function( actual, expected, message ) {\n        actual = objectValues( actual );\n        expected = objectValues( expected );\n        this.push( !QUnit.equiv( actual, expected ), actual, expected, message );\n    },\n\n    /**\n     * @name deepEqual\n     * @function\n     */\n    deepEqual: function( actual, expected, message ) {\n        this.push( QUnit.equiv( actual, expected ), actual, expected, message );\n    },\n\n    /**\n     * @name notDeepEqual\n     * @function\n     */\n    notDeepEqual: function( actual, expected, message ) {\n        this.push( !QUnit.equiv( actual, expected ), actual, expected, message );\n    },\n\n    /**\n     * @name strictEqual\n     * @function\n     */\n    strictEqual: function( actual, expected, message ) {\n        this.push( expected === actual, actual, expected, message );\n    },\n\n    /**\n     * @name notStrictEqual\n     * @function\n     */\n    notStrictEqual: function( actual, expected, message ) {\n        this.push( expected !== actual, actual, expected, message );\n    },\n\n    \"throws\": function( block, expected, message ) {\n        var actual, expectedType,\n            expectedOutput = expected,\n            ok = false;\n\n        // 'expected' is optional unless doing string comparison\n        if ( message == null && typeof expected === \"string\" ) {\n            message = expected;\n            expected = null;\n        }\n\n        this.test.ignoreGlobalErrors = true;\n        try {\n            block.call( this.test.testEnvironment );\n        } catch (e) {\n            actual = e;\n        }\n        this.test.ignoreGlobalErrors = false;\n\n        if ( actual ) {\n            expectedType = QUnit.objectType( expected );\n\n            // we don't want to validate thrown error\n            if ( !expected ) {\n                ok = true;\n                expectedOutput = null;\n\n            // expected is a regexp\n            } else if ( expectedType === \"regexp\" ) {\n                ok = expected.test( errorString( actual ) );\n\n            // expected is a string\n            } else if ( expectedType === \"string\" ) {\n                ok = expected === errorString( actual );\n\n            // expected is a constructor, maybe an Error constructor\n            } else if ( expectedType === \"function\" && actual instanceof expected ) {\n                ok = true;\n\n            // expected is an Error object\n            } else if ( expectedType === \"object\" ) {\n                ok = actual instanceof expected.constructor &&\n                    actual.name === expected.name &&\n                    actual.message === expected.message;\n\n            // expected is a validation function which returns true if validation passed\n            } else if ( expectedType === \"function\" && expected.call( {}, actual ) === true ) {\n                expectedOutput = null;\n                ok = true;\n            }\n\n            this.push( ok, actual, expectedOutput, message );\n        } else {\n            this.test.pushFailure( message, null, \"No exception was thrown.\" );\n        }\n    }\n};\n\n// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word\n// Known to us are: Closure Compiler, Narwhal\n(function() {\n    /*jshint sub:true */\n    Assert.prototype.raises = Assert.prototype[ \"throws\" ];\n}());\n\n// Test for equality any JavaScript type.\n// Author: Philippe Rathé <prathe@gmail.com>\nQUnit.equiv = (function() {\n\n    // Call the o related callback with the given arguments.\n    function bindCallbacks( o, callbacks, args ) {\n        var prop = QUnit.objectType( o );\n        if ( prop ) {\n            if ( QUnit.objectType( callbacks[ prop ] ) === \"function\" ) {\n                return callbacks[ prop ].apply( callbacks, args );\n            } else {\n                return callbacks[ prop ]; // or undefined\n            }\n        }\n    }\n\n    // the real equiv function\n    var innerEquiv,\n\n        // stack to decide between skip/abort functions\n        callers = [],\n\n        // stack to avoiding loops from circular referencing\n        parents = [],\n        parentsB = [],\n\n        getProto = Object.getPrototypeOf || function( obj ) {\n            /* jshint camelcase: false, proto: true */\n            return obj.__proto__;\n        },\n        callbacks = (function() {\n\n            // for string, boolean, number and null\n            function useStrictEquality( b, a ) {\n\n                /*jshint eqeqeq:false */\n                if ( b instanceof a.constructor || a instanceof b.constructor ) {\n\n                    // to catch short annotation VS 'new' annotation of a\n                    // declaration\n                    // e.g. var i = 1;\n                    // var j = new Number(1);\n                    return a == b;\n                } else {\n                    return a === b;\n                }\n            }\n\n            return {\n                \"string\": useStrictEquality,\n                \"boolean\": useStrictEquality,\n                \"number\": useStrictEquality,\n                \"null\": useStrictEquality,\n                \"undefined\": useStrictEquality,\n\n                \"nan\": function( b ) {\n                    return isNaN( b );\n                },\n\n                \"date\": function( b, a ) {\n                    return QUnit.objectType( b ) === \"date\" && a.valueOf() === b.valueOf();\n                },\n\n                \"regexp\": function( b, a ) {\n                    return QUnit.objectType( b ) === \"regexp\" &&\n\n                        // the regex itself\n                        a.source === b.source &&\n\n                        // and its modifiers\n                        a.global === b.global &&\n\n                        // (gmi) ...\n                        a.ignoreCase === b.ignoreCase &&\n                        a.multiline === b.multiline &&\n                        a.sticky === b.sticky;\n                },\n\n                // - skip when the property is a method of an instance (OOP)\n                // - abort otherwise,\n                // initial === would have catch identical references anyway\n                \"function\": function() {\n                    var caller = callers[ callers.length - 1 ];\n                    return caller !== Object && typeof caller !== \"undefined\";\n                },\n\n                \"array\": function( b, a ) {\n                    var i, j, len, loop, aCircular, bCircular;\n\n                    // b could be an object literal here\n                    if ( QUnit.objectType( b ) !== \"array\" ) {\n                        return false;\n                    }\n\n                    len = a.length;\n                    if ( len !== b.length ) {\n                        // safe and faster\n                        return false;\n                    }\n\n                    // track reference to avoid circular references\n                    parents.push( a );\n                    parentsB.push( b );\n                    for ( i = 0; i < len; i++ ) {\n                        loop = false;\n                        for ( j = 0; j < parents.length; j++ ) {\n                            aCircular = parents[ j ] === a[ i ];\n                            bCircular = parentsB[ j ] === b[ i ];\n                            if ( aCircular || bCircular ) {\n                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {\n                                    loop = true;\n                                } else {\n                                    parents.pop();\n                                    parentsB.pop();\n                                    return false;\n                                }\n                            }\n                        }\n                        if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {\n                            parents.pop();\n                            parentsB.pop();\n                            return false;\n                        }\n                    }\n                    parents.pop();\n                    parentsB.pop();\n                    return true;\n                },\n\n                \"object\": function( b, a ) {\n\n                    /*jshint forin:false */\n                    var i, j, loop, aCircular, bCircular,\n                        // Default to true\n                        eq = true,\n                        aProperties = [],\n                        bProperties = [];\n\n                    // comparing constructors is more strict than using\n                    // instanceof\n                    if ( a.constructor !== b.constructor ) {\n\n                        // Allow objects with no prototype to be equivalent to\n                        // objects with Object as their constructor.\n                        if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||\n                            ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {\n                            return false;\n                        }\n                    }\n\n                    // stack constructor before traversing properties\n                    callers.push( a.constructor );\n\n                    // track reference to avoid circular references\n                    parents.push( a );\n                    parentsB.push( b );\n\n                    // be strict: don't ensure hasOwnProperty and go deep\n                    for ( i in a ) {\n                        loop = false;\n                        for ( j = 0; j < parents.length; j++ ) {\n                            aCircular = parents[ j ] === a[ i ];\n                            bCircular = parentsB[ j ] === b[ i ];\n                            if ( aCircular || bCircular ) {\n                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {\n                                    loop = true;\n                                } else {\n                                    eq = false;\n                                    break;\n                                }\n                            }\n                        }\n                        aProperties.push( i );\n                        if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {\n                            eq = false;\n                            break;\n                        }\n                    }\n\n                    parents.pop();\n                    parentsB.pop();\n                    callers.pop(); // unstack, we are done\n\n                    for ( i in b ) {\n                        bProperties.push( i ); // collect b's properties\n                    }\n\n                    // Ensures identical properties name\n                    return eq && innerEquiv( aProperties.sort(), bProperties.sort() );\n                }\n            };\n        }());\n\n    innerEquiv = function() { // can take multiple arguments\n        var args = [].slice.apply( arguments );\n        if ( args.length < 2 ) {\n            return true; // end transition\n        }\n\n        return ( (function( a, b ) {\n            if ( a === b ) {\n                return true; // catch the most you can\n            } else if ( a === null || b === null || typeof a === \"undefined\" ||\n                    typeof b === \"undefined\" ||\n                    QUnit.objectType( a ) !== QUnit.objectType( b ) ) {\n\n                // don't lose time with error prone cases\n                return false;\n            } else {\n                return bindCallbacks( a, callbacks, [ b, a ] );\n            }\n\n            // apply transition with (1..n) arguments\n        }( args[ 0 ], args[ 1 ] ) ) &&\n            innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );\n    };\n\n    return innerEquiv;\n}());\n\n// Based on jsDump by Ariel Flesler\n// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html\nQUnit.dump = (function() {\n    function quote( str ) {\n        return \"\\\"\" + str.toString().replace( /\"/g, \"\\\\\\\"\" ) + \"\\\"\";\n    }\n    function literal( o ) {\n        return o + \"\";\n    }\n    function join( pre, arr, post ) {\n        var s = dump.separator(),\n            base = dump.indent(),\n            inner = dump.indent( 1 );\n        if ( arr.join ) {\n            arr = arr.join( \",\" + s + inner );\n        }\n        if ( !arr ) {\n            return pre + post;\n        }\n        return [ pre, inner + arr, base + post ].join( s );\n    }\n    function array( arr, stack ) {\n        var i = arr.length,\n            ret = new Array( i );\n\n        if ( dump.maxDepth && dump.depth > dump.maxDepth ) {\n            return \"[object Array]\";\n        }\n\n        this.up();\n        while ( i-- ) {\n            ret[ i ] = this.parse( arr[ i ], undefined, stack );\n        }\n        this.down();\n        return join( \"[\", ret, \"]\" );\n    }\n\n    var reName = /^function (\\w+)/,\n        dump = {\n\n            // objType is used mostly internally, you can fix a (custom) type in advance\n            parse: function( obj, objType, stack ) {\n                stack = stack || [];\n                var res, parser, parserType,\n                    inStack = inArray( obj, stack );\n\n                if ( inStack !== -1 ) {\n                    return \"recursion(\" + ( inStack - stack.length ) + \")\";\n                }\n\n                objType = objType || this.typeOf( obj  );\n                parser = this.parsers[ objType ];\n                parserType = typeof parser;\n\n                if ( parserType === \"function\" ) {\n                    stack.push( obj );\n                    res = parser.call( this, obj, stack );\n                    stack.pop();\n                    return res;\n                }\n                return ( parserType === \"string\" ) ? parser : this.parsers.error;\n            },\n            typeOf: function( obj ) {\n                var type;\n                if ( obj === null ) {\n                    type = \"null\";\n                } else if ( typeof obj === \"undefined\" ) {\n                    type = \"undefined\";\n                } else if ( QUnit.is( \"regexp\", obj ) ) {\n                    type = \"regexp\";\n                } else if ( QUnit.is( \"date\", obj ) ) {\n                    type = \"date\";\n                } else if ( QUnit.is( \"function\", obj ) ) {\n                    type = \"function\";\n                } else if ( obj.setInterval !== undefined &&\n                        obj.document !== undefined &&\n                        obj.nodeType === undefined ) {\n                    type = \"window\";\n                } else if ( obj.nodeType === 9 ) {\n                    type = \"document\";\n                } else if ( obj.nodeType ) {\n                    type = \"node\";\n                } else if (\n\n                    // native arrays\n                    toString.call( obj ) === \"[object Array]\" ||\n\n                    // NodeList objects\n                    ( typeof obj.length === \"number\" && obj.item !== undefined &&\n                    ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&\n                    obj[ 0 ] === undefined ) ) )\n                ) {\n                    type = \"array\";\n                } else if ( obj.constructor === Error.prototype.constructor ) {\n                    type = \"error\";\n                } else {\n                    type = typeof obj;\n                }\n                return type;\n            },\n            separator: function() {\n                return this.multiline ? this.HTML ? \"<br />\" : \"\\n\" : this.HTML ? \"&#160;\" : \" \";\n            },\n            // extra can be a number, shortcut for increasing-calling-decreasing\n            indent: function( extra ) {\n                if ( !this.multiline ) {\n                    return \"\";\n                }\n                var chr = this.indentChar;\n                if ( this.HTML ) {\n                    chr = chr.replace( /\\t/g, \"   \" ).replace( / /g, \"&#160;\" );\n                }\n                return new Array( this.depth + ( extra || 0 ) ).join( chr );\n            },\n            up: function( a ) {\n                this.depth += a || 1;\n            },\n            down: function( a ) {\n                this.depth -= a || 1;\n            },\n            setParser: function( name, parser ) {\n                this.parsers[ name ] = parser;\n            },\n            // The next 3 are exposed so you can use them\n            quote: quote,\n            literal: literal,\n            join: join,\n            //\n            depth: 1,\n            maxDepth: 5,\n\n            // This is the list of parsers, to modify them, use dump.setParser\n            parsers: {\n                window: \"[Window]\",\n                document: \"[Document]\",\n                error: function( error ) {\n                    return \"Error(\\\"\" + error.message + \"\\\")\";\n                },\n                unknown: \"[Unknown]\",\n                \"null\": \"null\",\n                \"undefined\": \"undefined\",\n                \"function\": function( fn ) {\n                    var ret = \"function\",\n\n                        // functions never have name in IE\n                        name = \"name\" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];\n\n                    if ( name ) {\n                        ret += \" \" + name;\n                    }\n                    ret += \"( \";\n\n                    ret = [ ret, dump.parse( fn, \"functionArgs\" ), \"){\" ].join( \"\" );\n                    return join( ret, dump.parse( fn, \"functionCode\" ), \"}\" );\n                },\n                array: array,\n                nodelist: array,\n                \"arguments\": array,\n                object: function( map, stack ) {\n                    var keys, key, val, i, nonEnumerableProperties,\n                        ret = [];\n\n                    if ( dump.maxDepth && dump.depth > dump.maxDepth ) {\n                        return \"[object Object]\";\n                    }\n\n                    dump.up();\n                    keys = [];\n                    for ( key in map ) {\n                        keys.push( key );\n                    }\n\n                    // Some properties are not always enumerable on Error objects.\n                    nonEnumerableProperties = [ \"message\", \"name\" ];\n                    for ( i in nonEnumerableProperties ) {\n                        key = nonEnumerableProperties[ i ];\n                        if ( key in map && !( key in keys ) ) {\n                            keys.push( key );\n                        }\n                    }\n                    keys.sort();\n                    for ( i = 0; i < keys.length; i++ ) {\n                        key = keys[ i ];\n                        val = map[ key ];\n                        ret.push( dump.parse( key, \"key\" ) + \": \" +\n                            dump.parse( val, undefined, stack ) );\n                    }\n                    dump.down();\n                    return join( \"{\", ret, \"}\" );\n                },\n                node: function( node ) {\n                    var len, i, val,\n                        open = dump.HTML ? \"&lt;\" : \"<\",\n                        close = dump.HTML ? \"&gt;\" : \">\",\n                        tag = node.nodeName.toLowerCase(),\n                        ret = open + tag,\n                        attrs = node.attributes;\n\n                    if ( attrs ) {\n                        for ( i = 0, len = attrs.length; i < len; i++ ) {\n                            val = attrs[ i ].nodeValue;\n\n                            // IE6 includes all attributes in .attributes, even ones not explicitly\n                            // set. Those have values like undefined, null, 0, false, \"\" or\n                            // \"inherit\".\n                            if ( val && val !== \"inherit\" ) {\n                                ret += \" \" + attrs[ i ].nodeName + \"=\" +\n                                    dump.parse( val, \"attribute\" );\n                            }\n                        }\n                    }\n                    ret += close;\n\n                    // Show content of TextNode or CDATASection\n                    if ( node.nodeType === 3 || node.nodeType === 4 ) {\n                        ret += node.nodeValue;\n                    }\n\n                    return ret + open + \"/\" + tag + close;\n                },\n\n                // function calls it internally, it's the arguments part of the function\n                functionArgs: function( fn ) {\n                    var args,\n                        l = fn.length;\n\n                    if ( !l ) {\n                        return \"\";\n                    }\n\n                    args = new Array( l );\n                    while ( l-- ) {\n\n                        // 97 is 'a'\n                        args[ l ] = String.fromCharCode( 97 + l );\n                    }\n                    return \" \" + args.join( \", \" ) + \" \";\n                },\n                // object calls it internally, the key part of an item in a map\n                key: quote,\n                // function calls it internally, it's the content of the function\n                functionCode: \"[code]\",\n                // node calls it internally, it's an html attribute value\n                attribute: quote,\n                string: quote,\n                date: quote,\n                regexp: literal,\n                number: literal,\n                \"boolean\": literal\n            },\n            // if true, entities are escaped ( <, >, \\t, space and \\n )\n            HTML: false,\n            // indentation unit\n            indentChar: \"  \",\n            // if true, items in a collection, are separated by a \\n, else just a space.\n            multiline: true\n        };\n\n    return dump;\n}());\n\n// back compat\nQUnit.jsDump = QUnit.dump;\n\n// For browser, export only select globals\nif ( typeof window !== \"undefined\" ) {\n\n    // Deprecated\n    // Extend assert methods to QUnit and Global scope through Backwards compatibility\n    (function() {\n        var i,\n            assertions = Assert.prototype;\n\n        function applyCurrent( current ) {\n            return function() {\n                var assert = new Assert( QUnit.config.current );\n                current.apply( assert, arguments );\n            };\n        }\n\n        for ( i in assertions ) {\n            QUnit[ i ] = applyCurrent( assertions[ i ] );\n        }\n    })();\n\n    (function() {\n        var i, l,\n            keys = [\n                \"test\",\n                \"module\",\n                \"expect\",\n                \"asyncTest\",\n                \"start\",\n                \"stop\",\n                \"ok\",\n                \"equal\",\n                \"notEqual\",\n                \"propEqual\",\n                \"notPropEqual\",\n                \"deepEqual\",\n                \"notDeepEqual\",\n                \"strictEqual\",\n                \"notStrictEqual\",\n                \"throws\"\n            ];\n\n        for ( i = 0, l = keys.length; i < l; i++ ) {\n            window[ keys[ i ] ] = QUnit[ keys[ i ] ];\n        }\n    })();\n\n    window.QUnit = QUnit;\n}\n\n// For nodejs\nif ( typeof module !== \"undefined\" && module.exports ) {\n    module.exports = QUnit;\n}\n\n// For CommonJS with exports, but without module.exports, like Rhino\nif ( typeof exports !== \"undefined\" ) {\n    exports.QUnit = QUnit;\n}\n\n// Get a reference to the global object, like window in browsers\n}( (function() {\n    return this;\n})() ));\n\n/*istanbul ignore next */\n// jscs:disable maximumLineLength\n/*\n * Javascript Diff Algorithm\n *  By John Resig (http://ejohn.org/)\n *  Modified by Chu Alan \"sprite\"\n *\n * Released under the MIT license.\n *\n * More Info:\n *  http://ejohn.org/projects/javascript-diff-algorithm/\n *\n * Usage: QUnit.diff(expected, actual)\n *\n * 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\"\n */\nQUnit.diff = (function() {\n    var hasOwn = Object.prototype.hasOwnProperty;\n\n    /*jshint eqeqeq:false, eqnull:true */\n    function diff( o, n ) {\n        var i,\n            ns = {},\n            os = {};\n\n        for ( i = 0; i < n.length; i++ ) {\n            if ( !hasOwn.call( ns, n[ i ] ) ) {\n                ns[ n[ i ] ] = {\n                    rows: [],\n                    o: null\n                };\n            }\n            ns[ n[ i ] ].rows.push( i );\n        }\n\n        for ( i = 0; i < o.length; i++ ) {\n            if ( !hasOwn.call( os, o[ i ] ) ) {\n                os[ o[ i ] ] = {\n                    rows: [],\n                    n: null\n                };\n            }\n            os[ o[ i ] ].rows.push( i );\n        }\n\n        for ( i in ns ) {\n            if ( hasOwn.call( ns, i ) ) {\n                if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {\n                    n[ ns[ i ].rows[ 0 ] ] = {\n                        text: n[ ns[ i ].rows[ 0 ] ],\n                        row: os[ i ].rows[ 0 ]\n                    };\n                    o[ os[ i ].rows[ 0 ] ] = {\n                        text: o[ os[ i ].rows[ 0 ] ],\n                        row: ns[ i ].rows[ 0 ]\n                    };\n                }\n            }\n        }\n\n        for ( i = 0; i < n.length - 1; i++ ) {\n            if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&\n                n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {\n\n                n[ i + 1 ] = {\n                    text: n[ i + 1 ],\n                    row: n[ i ].row + 1\n                };\n                o[ n[ i ].row + 1 ] = {\n                    text: o[ n[ i ].row + 1 ],\n                    row: i + 1\n                };\n            }\n        }\n\n        for ( i = n.length - 1; i > 0; i-- ) {\n            if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&\n                n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {\n\n                n[ i - 1 ] = {\n                    text: n[ i - 1 ],\n                    row: n[ i ].row - 1\n                };\n                o[ n[ i ].row - 1 ] = {\n                    text: o[ n[ i ].row - 1 ],\n                    row: i - 1\n                };\n            }\n        }\n\n        return {\n            o: o,\n            n: n\n        };\n    }\n\n    return function( o, n ) {\n        o = o.replace( /\\s+$/, \"\" );\n        n = n.replace( /\\s+$/, \"\" );\n\n        var i, pre,\n            str = \"\",\n            out = diff( o === \"\" ? [] : o.split( /\\s+/ ), n === \"\" ? [] : n.split( /\\s+/ ) ),\n            oSpace = o.match( /\\s+/g ),\n            nSpace = n.match( /\\s+/g );\n\n        if ( oSpace == null ) {\n            oSpace = [ \" \" ];\n        } else {\n            oSpace.push( \" \" );\n        }\n\n        if ( nSpace == null ) {\n            nSpace = [ \" \" ];\n        } else {\n            nSpace.push( \" \" );\n        }\n\n        if ( out.n.length === 0 ) {\n            for ( i = 0; i < out.o.length; i++ ) {\n                str += \"<del>\" + out.o[ i ] + oSpace[ i ] + \"</del>\";\n            }\n        } else {\n            if ( out.n[ 0 ].text == null ) {\n                for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {\n                    str += \"<del>\" + out.o[ n ] + oSpace[ n ] + \"</del>\";\n                }\n            }\n\n            for ( i = 0; i < out.n.length; i++ ) {\n                if ( out.n[ i ].text == null ) {\n                    str += \"<ins>\" + out.n[ i ] + nSpace[ i ] + \"</ins>\";\n                } else {\n\n                    // `pre` initialized at top of scope\n                    pre = \"\";\n\n                    for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {\n                        pre += \"<del>\" + out.o[ n ] + oSpace[ n ] + \"</del>\";\n                    }\n                    str += \" \" + out.n[ i ].text + nSpace[ i ] + pre;\n                }\n            }\n        }\n\n        return str;\n    };\n}());\n// jscs:enable\n\n(function() {\n\n// Deprecated QUnit.init - Ref #530\n// Re-initialize the configuration options\nQUnit.init = function() {\n    var tests, banner, result, qunit,\n        config = QUnit.config;\n\n    config.stats = { all: 0, bad: 0 };\n    config.moduleStats = { all: 0, bad: 0 };\n    config.started = 0;\n    config.updateRate = 1000;\n    config.blocking = false;\n    config.autostart = true;\n    config.autorun = false;\n    config.filter = \"\";\n    config.queue = [];\n\n    // Return on non-browser environments\n    // This is necessary to not break on node tests\n    if ( typeof window === \"undefined\" ) {\n        return;\n    }\n\n    qunit = id( \"qunit\" );\n    if ( qunit ) {\n        qunit.innerHTML =\n            \"<h1 id='qunit-header'>\" + escapeText( document.title ) + \"</h1>\" +\n            \"<h2 id='qunit-banner'></h2>\" +\n            \"<div id='qunit-testrunner-toolbar'></div>\" +\n            \"<h2 id='qunit-userAgent'></h2>\" +\n            \"<ol id='qunit-tests'></ol>\";\n    }\n\n    tests = id( \"qunit-tests\" );\n    banner = id( \"qunit-banner\" );\n    result = id( \"qunit-testresult\" );\n\n    if ( tests ) {\n        tests.innerHTML = \"\";\n    }\n\n    if ( banner ) {\n        banner.className = \"\";\n    }\n\n    if ( result ) {\n        result.parentNode.removeChild( result );\n    }\n\n    if ( tests ) {\n        result = document.createElement( \"p\" );\n        result.id = \"qunit-testresult\";\n        result.className = \"result\";\n        tests.parentNode.insertBefore( result, tests );\n        result.innerHTML = \"Running...<br />&#160;\";\n    }\n};\n\n// Don't load the HTML Reporter on non-Browser environments\nif ( typeof window === \"undefined\" ) {\n    return;\n}\n\nvar config = QUnit.config,\n    hasOwn = Object.prototype.hasOwnProperty,\n    defined = {\n        document: window.document !== undefined,\n        sessionStorage: (function() {\n            var x = \"qunit-test-string\";\n            try {\n                sessionStorage.setItem( x, x );\n                sessionStorage.removeItem( x );\n                return true;\n            } catch ( e ) {\n                return false;\n            }\n        }())\n    },\n    modulesList = [];\n\n/**\n* Escape text for attribute or text content.\n*/\nfunction escapeText( s ) {\n    if ( !s ) {\n        return \"\";\n    }\n    s = s + \"\";\n\n    // Both single quotes and double quotes (for attributes)\n    return s.replace( /['\"<>&]/g, function( s ) {\n        switch ( s ) {\n        case \"'\":\n            return \"&#039;\";\n        case \"\\\"\":\n            return \"&quot;\";\n        case \"<\":\n            return \"&lt;\";\n        case \">\":\n            return \"&gt;\";\n        case \"&\":\n            return \"&amp;\";\n        }\n    });\n}\n\n/**\n * @param {HTMLElement} elem\n * @param {string} type\n * @param {Function} fn\n */\nfunction addEvent( elem, type, fn ) {\n    if ( elem.addEventListener ) {\n\n        // Standards-based browsers\n        elem.addEventListener( type, fn, false );\n    } else if ( elem.attachEvent ) {\n\n        // support: IE <9\n        elem.attachEvent( \"on\" + type, fn );\n    }\n}\n\n/**\n * @param {Array|NodeList} elems\n * @param {string} type\n * @param {Function} fn\n */\nfunction addEvents( elems, type, fn ) {\n    var i = elems.length;\n    while ( i-- ) {\n        addEvent( elems[ i ], type, fn );\n    }\n}\n\nfunction hasClass( elem, name ) {\n    return ( \" \" + elem.className + \" \" ).indexOf( \" \" + name + \" \" ) >= 0;\n}\n\nfunction addClass( elem, name ) {\n    if ( !hasClass( elem, name ) ) {\n        elem.className += ( elem.className ? \" \" : \"\" ) + name;\n    }\n}\n\nfunction toggleClass( elem, name ) {\n    if ( hasClass( elem, name ) ) {\n        removeClass( elem, name );\n    } else {\n        addClass( elem, name );\n    }\n}\n\nfunction removeClass( elem, name ) {\n    var set = \" \" + elem.className + \" \";\n\n    // Class name may appear multiple times\n    while ( set.indexOf( \" \" + name + \" \" ) >= 0 ) {\n        set = set.replace( \" \" + name + \" \", \" \" );\n    }\n\n    // trim for prettiness\n    elem.className = typeof set.trim === \"function\" ? set.trim() : set.replace( /^\\s+|\\s+$/g, \"\" );\n}\n\nfunction id( name ) {\n    return defined.document && document.getElementById && document.getElementById( name );\n}\n\nfunction getUrlConfigHtml() {\n    var i, j, val,\n        escaped, escapedTooltip,\n        selection = false,\n        len = config.urlConfig.length,\n        urlConfigHtml = \"\";\n\n    for ( i = 0; i < len; i++ ) {\n        val = config.urlConfig[ i ];\n        if ( typeof val === \"string\" ) {\n            val = {\n                id: val,\n                label: val\n            };\n        }\n\n        escaped = escapeText( val.id );\n        escapedTooltip = escapeText( val.tooltip );\n\n        config[ val.id ] = QUnit.urlParams[ val.id ];\n        if ( !val.value || typeof val.value === \"string\" ) {\n            urlConfigHtml += \"<input id='qunit-urlconfig-\" + escaped +\n                \"' name='\" + escaped + \"' type='checkbox'\" +\n                ( val.value ? \" value='\" + escapeText( val.value ) + \"'\" : \"\" ) +\n                ( config[ val.id ] ? \" checked='checked'\" : \"\" ) +\n                \" title='\" + escapedTooltip + \"' /><label for='qunit-urlconfig-\" + escaped +\n                \"' title='\" + escapedTooltip + \"'>\" + val.label + \"</label>\";\n        } else {\n            urlConfigHtml += \"<label for='qunit-urlconfig-\" + escaped +\n                \"' title='\" + escapedTooltip + \"'>\" + val.label +\n                \": </label><select id='qunit-urlconfig-\" + escaped +\n                \"' name='\" + escaped + \"' title='\" + escapedTooltip + \"'><option></option>\";\n\n            if ( QUnit.is( \"array\", val.value ) ) {\n                for ( j = 0; j < val.value.length; j++ ) {\n                    escaped = escapeText( val.value[ j ] );\n                    urlConfigHtml += \"<option value='\" + escaped + \"'\" +\n                        ( config[ val.id ] === val.value[ j ] ?\n                            ( selection = true ) && \" selected='selected'\" : \"\" ) +\n                        \">\" + escaped + \"</option>\";\n                }\n            } else {\n                for ( j in val.value ) {\n                    if ( hasOwn.call( val.value, j ) ) {\n                        urlConfigHtml += \"<option value='\" + escapeText( j ) + \"'\" +\n                            ( config[ val.id ] === j ?\n                                ( selection = true ) && \" selected='selected'\" : \"\" ) +\n                            \">\" + escapeText( val.value[ j ] ) + \"</option>\";\n                    }\n                }\n            }\n            if ( config[ val.id ] && !selection ) {\n                escaped = escapeText( config[ val.id ] );\n                urlConfigHtml += \"<option value='\" + escaped +\n                    \"' selected='selected' disabled='disabled'>\" + escaped + \"</option>\";\n            }\n            urlConfigHtml += \"</select>\";\n        }\n    }\n\n    return urlConfigHtml;\n}\n\n// Handle \"click\" events on toolbar checkboxes and \"change\" for select menus.\n// Updates the URL with the new state of `config.urlConfig` values.\nfunction toolbarChanged() {\n    var updatedUrl, value,\n        field = this,\n        params = {};\n\n    // Detect if field is a select menu or a checkbox\n    if ( \"selectedIndex\" in field ) {\n        value = field.options[ field.selectedIndex ].value || undefined;\n    } else {\n        value = field.checked ? ( field.defaultValue || true ) : undefined;\n    }\n\n    params[ field.name ] = value;\n    updatedUrl = QUnit.url( params );\n\n    if ( \"hidepassed\" === field.name && \"replaceState\" in window.history ) {\n        config[ field.name ] = value || false;\n        if ( value ) {\n            addClass( id( \"qunit-tests\" ), \"hidepass\" );\n        } else {\n            removeClass( id( \"qunit-tests\" ), \"hidepass\" );\n        }\n\n        // It is not necessary to refresh the whole page\n        window.history.replaceState( null, \"\", updatedUrl );\n    } else {\n        window.location = updatedUrl;\n    }\n}\n\nfunction toolbarUrlConfigContainer() {\n    var urlConfigContainer = document.createElement( \"span\" );\n\n    urlConfigContainer.innerHTML = getUrlConfigHtml();\n\n    // For oldIE support:\n    // * Add handlers to the individual elements instead of the container\n    // * Use \"click\" instead of \"change\" for checkboxes\n    addEvents( urlConfigContainer.getElementsByTagName( \"input\" ), \"click\", toolbarChanged );\n    addEvents( urlConfigContainer.getElementsByTagName( \"select\" ), \"change\", toolbarChanged );\n\n    return urlConfigContainer;\n}\n\nfunction toolbarModuleFilterHtml() {\n    var i,\n        moduleFilterHtml = \"\";\n\n    if ( !modulesList.length ) {\n        return false;\n    }\n\n    modulesList.sort(function( a, b ) {\n        return a.localeCompare( b );\n    });\n\n    moduleFilterHtml += \"<label for='qunit-modulefilter'>Module: </label>\" +\n        \"<select id='qunit-modulefilter' name='modulefilter'><option value='' \" +\n        ( QUnit.urlParams.module === undefined ? \"selected='selected'\" : \"\" ) +\n        \">< All Modules ></option>\";\n\n    for ( i = 0; i < modulesList.length; i++ ) {\n        moduleFilterHtml += \"<option value='\" +\n            escapeText( encodeURIComponent( modulesList[ i ] ) ) + \"' \" +\n            ( QUnit.urlParams.module === modulesList[ i ] ? \"selected='selected'\" : \"\" ) +\n            \">\" + escapeText( modulesList[ i ] ) + \"</option>\";\n    }\n    moduleFilterHtml += \"</select>\";\n\n    return moduleFilterHtml;\n}\n\nfunction toolbarModuleFilter() {\n    var toolbar = id( \"qunit-testrunner-toolbar\" ),\n        moduleFilter = document.createElement( \"span\" ),\n        moduleFilterHtml = toolbarModuleFilterHtml();\n\n    if ( !moduleFilterHtml ) {\n        return false;\n    }\n\n    moduleFilter.setAttribute( \"id\", \"qunit-modulefilter-container\" );\n    moduleFilter.innerHTML = moduleFilterHtml;\n\n    addEvent( moduleFilter.lastChild, \"change\", function() {\n        var selectBox = moduleFilter.getElementsByTagName( \"select\" )[ 0 ],\n            selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );\n\n        window.location = QUnit.url({\n            module: ( selection === \"\" ) ? undefined : selection,\n\n            // Remove any existing filters\n            filter: undefined,\n            testId: undefined\n        });\n    });\n\n    toolbar.appendChild( moduleFilter );\n}\n\nfunction appendToolbar() {\n    var toolbar = id( \"qunit-testrunner-toolbar\" );\n\n    if ( toolbar ) {\n        toolbar.appendChild( toolbarUrlConfigContainer() );\n    }\n}\n\nfunction appendBanner() {\n    var banner = id( \"qunit-banner\" );\n\n    if ( banner ) {\n        banner.className = \"\";\n        banner.innerHTML = \"<a href='\" +\n            QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +\n            \"'>\" + banner.innerHTML + \"</a> \";\n    }\n}\n\nfunction appendTestResults() {\n    var tests = id( \"qunit-tests\" ),\n        result = id( \"qunit-testresult\" );\n\n    if ( result ) {\n        result.parentNode.removeChild( result );\n    }\n\n    if ( tests ) {\n        tests.innerHTML = \"\";\n        result = document.createElement( \"p\" );\n        result.id = \"qunit-testresult\";\n        result.className = \"result\";\n        tests.parentNode.insertBefore( result, tests );\n        result.innerHTML = \"Running...<br />&#160;\";\n    }\n}\n\nfunction storeFixture() {\n    var fixture = id( \"qunit-fixture\" );\n    if ( fixture ) {\n        config.fixture = fixture.innerHTML;\n    }\n}\n\nfunction appendUserAgent() {\n    var userAgent = id( \"qunit-userAgent\" );\n    if ( userAgent ) {\n        userAgent.innerHTML = navigator.userAgent;\n    }\n}\n\nfunction appendTestsList( modules ) {\n    var i, l, x, z, test, moduleObj;\n\n    for ( i = 0, l = modules.length; i < l; i++ ) {\n        moduleObj = modules[ i ];\n\n        if ( moduleObj.name ) {\n            modulesList.push( moduleObj.name );\n        }\n\n        for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {\n            test = moduleObj.tests[ x ];\n\n            appendTest( test.name, test.testId, moduleObj.name );\n        }\n    }\n}\n\nfunction appendTest( name, testId, moduleName ) {\n    var title, rerunTrigger, testBlock, assertList,\n        tests = id( \"qunit-tests\" );\n\n    if ( !tests ) {\n        return;\n    }\n\n    title = document.createElement( \"strong\" );\n    title.innerHTML = getNameHtml( name, moduleName );\n\n    rerunTrigger = document.createElement( \"a\" );\n    rerunTrigger.innerHTML = \"Rerun\";\n    rerunTrigger.href = QUnit.url({ testId: testId });\n\n    testBlock = document.createElement( \"li\" );\n    testBlock.appendChild( title );\n    testBlock.appendChild( rerunTrigger );\n    testBlock.id = \"qunit-test-output-\" + testId;\n\n    assertList = document.createElement( \"ol\" );\n    assertList.className = \"qunit-assert-list\";\n\n    testBlock.appendChild( assertList );\n\n    tests.appendChild( testBlock );\n}\n\n// HTML Reporter initialization and load\nQUnit.begin(function( details ) {\n    var qunit = id( \"qunit\" );\n\n    // Fixture is the only one necessary to run without the #qunit element\n    storeFixture();\n\n    if ( !qunit ) {\n        return;\n    }\n\n    qunit.innerHTML =\n        \"<h1 id='qunit-header'>\" + escapeText( document.title ) + \"</h1>\" +\n        \"<h2 id='qunit-banner'></h2>\" +\n        \"<div id='qunit-testrunner-toolbar'></div>\" +\n        \"<h2 id='qunit-userAgent'></h2>\" +\n        \"<ol id='qunit-tests'></ol>\";\n\n    appendBanner();\n    appendTestResults();\n    appendUserAgent();\n    appendToolbar();\n    appendTestsList( details.modules );\n    toolbarModuleFilter();\n\n    if ( config.hidepassed ) {\n        addClass( qunit.lastChild, \"hidepass\" );\n    }\n});\n\nQUnit.done(function( details ) {\n    var i, key,\n        banner = id( \"qunit-banner\" ),\n        tests = id( \"qunit-tests\" ),\n        html = [\n            \"Tests completed in \",\n            details.runtime,\n            \" milliseconds.<br />\",\n            \"<span class='passed'>\",\n            details.passed,\n            \"</span> assertions of <span class='total'>\",\n            details.total,\n            \"</span> passed, <span class='failed'>\",\n            details.failed,\n            \"</span> failed.\"\n        ].join( \"\" );\n\n    if ( banner ) {\n        banner.className = details.failed ? \"qunit-fail\" : \"qunit-pass\";\n    }\n\n    if ( tests ) {\n        id( \"qunit-testresult\" ).innerHTML = html;\n    }\n\n    if ( config.altertitle && defined.document && document.title ) {\n\n        // show ✖ for good, ✔ for bad suite result in title\n        // use escape sequences in case file gets loaded with non-utf-8-charset\n        document.title = [\n            ( details.failed ? \"\\u2716\" : \"\\u2714\" ),\n            document.title.replace( /^[\\u2714\\u2716] /i, \"\" )\n        ].join( \" \" );\n    }\n\n    // clear own sessionStorage items if all tests passed\n    if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {\n        for ( i = 0; i < sessionStorage.length; i++ ) {\n            key = sessionStorage.key( i++ );\n            if ( key.indexOf( \"qunit-test-\" ) === 0 ) {\n                sessionStorage.removeItem( key );\n            }\n        }\n    }\n\n    // scroll back to top to show results\n    if ( config.scrolltop && window.scrollTo ) {\n        window.scrollTo( 0, 0 );\n    }\n});\n\nfunction getNameHtml( name, module ) {\n    var nameHtml = \"\";\n\n    if ( module ) {\n        nameHtml = \"<span class='module-name'>\" + escapeText( module ) + \"</span>: \";\n    }\n\n    nameHtml += \"<span class='test-name'>\" + escapeText( name ) + \"</span>\";\n\n    return nameHtml;\n}\n\nQUnit.testStart(function( details ) {\n    var running, testBlock;\n\n    testBlock = id( \"qunit-test-output-\" + details.testId );\n    if ( testBlock ) {\n        testBlock.className = \"running\";\n    } else {\n\n        // Report later registered tests\n        appendTest( details.name, details.testId, details.module );\n    }\n\n    running = id( \"qunit-testresult\" );\n    if ( running ) {\n        running.innerHTML = \"Running: <br />\" + getNameHtml( details.name, details.module );\n    }\n\n});\n\nQUnit.log(function( details ) {\n    var assertList, assertLi,\n        message, expected, actual,\n        testItem = id( \"qunit-test-output-\" + details.testId );\n\n    if ( !testItem ) {\n        return;\n    }\n\n    message = escapeText( details.message ) || ( details.result ? \"okay\" : \"failed\" );\n    message = \"<span class='test-message'>\" + message + \"</span>\";\n    message += \"<span class='runtime'>@ \" + details.runtime + \" ms</span>\";\n\n    // pushFailure doesn't provide details.expected\n    // when it calls, it's implicit to also not show expected and diff stuff\n    // Also, we need to check details.expected existence, as it can exist and be undefined\n    if ( !details.result && hasOwn.call( details, \"expected\" ) ) {\n        expected = escapeText( QUnit.dump.parse( details.expected ) );\n        actual = escapeText( QUnit.dump.parse( details.actual ) );\n        message += \"<table><tr class='test-expected'><th>Expected: </th><td><pre>\" +\n            expected +\n            \"</pre></td></tr>\";\n\n        if ( actual !== expected ) {\n            message += \"<tr class='test-actual'><th>Result: </th><td><pre>\" +\n                actual + \"</pre></td></tr>\" +\n                \"<tr class='test-diff'><th>Diff: </th><td><pre>\" +\n                QUnit.diff( expected, actual ) + \"</pre></td></tr>\";\n        }\n\n        if ( details.source ) {\n            message += \"<tr class='test-source'><th>Source: </th><td><pre>\" +\n                escapeText( details.source ) + \"</pre></td></tr>\";\n        }\n\n        message += \"</table>\";\n\n    // this occours when pushFailure is set and we have an extracted stack trace\n    } else if ( !details.result && details.source ) {\n        message += \"<table>\" +\n            \"<tr class='test-source'><th>Source: </th><td><pre>\" +\n            escapeText( details.source ) + \"</pre></td></tr>\" +\n            \"</table>\";\n    }\n\n    assertList = testItem.getElementsByTagName( \"ol\" )[ 0 ];\n\n    assertLi = document.createElement( \"li\" );\n    assertLi.className = details.result ? \"pass\" : \"fail\";\n    assertLi.innerHTML = message;\n    assertList.appendChild( assertLi );\n});\n\nQUnit.testDone(function( details ) {\n    var testTitle, time, testItem, assertList,\n        good, bad, testCounts, skipped,\n        tests = id( \"qunit-tests\" );\n\n    if ( !tests ) {\n        return;\n    }\n\n    testItem = id( \"qunit-test-output-\" + details.testId );\n\n    assertList = testItem.getElementsByTagName( \"ol\" )[ 0 ];\n\n    good = details.passed;\n    bad = details.failed;\n\n    // store result when possible\n    if ( config.reorder && defined.sessionStorage ) {\n        if ( bad ) {\n            sessionStorage.setItem( \"qunit-test-\" + details.module + \"-\" + details.name, bad );\n        } else {\n            sessionStorage.removeItem( \"qunit-test-\" + details.module + \"-\" + details.name );\n        }\n    }\n\n    if ( bad === 0 ) {\n        addClass( assertList, \"qunit-collapsed\" );\n    }\n\n    // testItem.firstChild is the test name\n    testTitle = testItem.firstChild;\n\n    testCounts = bad ?\n        \"<b class='failed'>\" + bad + \"</b>, \" + \"<b class='passed'>\" + good + \"</b>, \" :\n        \"\";\n\n    testTitle.innerHTML += \" <b class='counts'>(\" + testCounts +\n        details.assertions.length + \")</b>\";\n\n    if ( details.skipped ) {\n        addClass( testItem, \"skipped\" );\n        skipped = document.createElement( \"em\" );\n        skipped.className = \"qunit-skipped-label\";\n        skipped.innerHTML = \"skipped\";\n        testItem.insertBefore( skipped, testTitle );\n    } else {\n        addEvent( testTitle, \"click\", function() {\n            toggleClass( assertList, \"qunit-collapsed\" );\n        });\n\n        testItem.className = bad ? \"fail\" : \"pass\";\n\n        time = document.createElement( \"span\" );\n        time.className = \"runtime\";\n        time.innerHTML = details.runtime + \" ms\";\n        testItem.insertBefore( time, assertList );\n    }\n});\n\nif ( !defined.document || document.readyState === \"complete\" ) {\n    config.pageLoaded = true;\n    config.autorun = true;\n}\n\nif ( defined.document ) {\n    addEvent( window, \"load\", QUnit.load );\n}\n\n})();\n"
  },
  {
    "path": "test/readme_example.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 readme example</title>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n        <script src=\"../spark-md5.js\"></script>\n    </head>\n    <body onload=\"init()\">\n        <input type=\"file\" id=\"file\" />\n        <script>\n            function init() {\n                document.getElementById('file').addEventListener('change', function () {\n                    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,\n                        file = this.files[0],\n                        chunkSize = 2097152,                             // Read in chunks of 2MB\n                        chunks = Math.ceil(file.size / chunkSize),\n                        currentChunk = 0,\n                        spark = new SparkMD5.ArrayBuffer(),\n                        fileReader = new FileReader();\n\n                    fileReader.onload = function (e) {\n                        console.log('read chunk nr', currentChunk + 1, 'of', chunks);\n                        spark.append(e.target.result);                   // Append array buffer\n                        currentChunk++;\n\n                        if (currentChunk < chunks) {\n                            loadNext();\n                        } else {\n                            console.log('finished loading');\n                            console.info('computed hash', spark.end());  // Compute hash\n                        }\n                    };\n\n                    fileReader.onerror = function () {\n                        console.warn('oops, something went wrong.');\n                    };\n\n                    function loadNext() {\n                        var start = currentChunk * chunkSize,\n                            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;\n\n                        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));\n                    }\n\n                    loadNext();\n                });\n            }\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "test/specs.js",
    "content": "/*global test, equal*/\n\nvar hasher = new SparkMD5(),\n    buffHasher = new SparkMD5.ArrayBuffer();\n\nfunction unicodeStringToArrayBuffer(str) {\n    if (/[\\u0080-\\uFFFF]/.test(str)) {\n        str = unescape(encodeURIComponent(str));\n    }\n\n    return stringToArrayBuffer(str);\n}\n\nfunction stringToArrayBuffer(str) {\n    var length = str.length,\n       buff = new ArrayBuffer(length),\n       arr = new Uint8Array(buff),\n       i;\n\n    for (i = 0; i < length; i += 1) {\n        arr[i] = str.charCodeAt(i);\n    }\n\n    return buff;\n}\n\nfunction binaryStringToHex(str) {\n    var hex = '',\n        ch,\n        i,\n        length = str.length;\n\n    for (i = 0; i < length; i += 1) {\n        ch = str.charCodeAt(i);\n        hex += (ch >> 4).toString(16);\n        hex += (ch & 0xF).toString(16);\n    }\n\n    return hex;\n}\n\ntest('Hash of \"hello\"', function () {\n    var str = 'hello',\n        hash = '5d41402abc4b2a76b9719d911017c592';\n\n    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');\n    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), hash, 'Incremental (normal)');\n    hasher.appendBinary(str);\n    equal(hasher.end(), hash, 'Incremental (binary)');\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), hash, 'Incremental (array buffer)');\n});\n\ntest('Hash of \"hello\" (raw)', function () {\n    var str = 'hello',\n        hash = '5d41402abc4b2a76b9719d911017c592';\n\n    equal(binaryStringToHex(SparkMD5.hash(str, true)), hash, 'SparkMD5.hash()');\n    equal(binaryStringToHex(SparkMD5.hashBinary(str, true)), hash, 'SparkMD5.hashBinary()');\n    equal(binaryStringToHex(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str), true)), hash, 'SparkMD5.ArrayBuffer.hash()');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(binaryStringToHex(hasher.end(true)), hash, 'Incremental (normal)');\n    hasher.appendBinary(str);\n    equal(binaryStringToHex(hasher.end(true)), hash, 'Incremental (binary)');\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(binaryStringToHex(buffHasher.end(true)), hash, 'Incremental (array buffer)');\n});\n\ntest('Hash of 64 bytes', function () {\n    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c592',\n        hash = 'e0b153045b08d59d4e18a98ab823ac42';\n\n    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');\n    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), hash, 'Incremental (normal)');\n    hasher.appendBinary(str);\n    equal(hasher.end(), hash, 'Incremental (binary)');\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), hash, 'Incremental (array buffer)');\n});\n\ntest('Hash of 128 bytes', function () {\n    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c592',\n        hash = 'b12bc24f5507eba4ee27092f70148415';\n\n    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');\n    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), hash, 'Incremental (normal)');\n    hasher.appendBinary(str);\n    equal(hasher.end(), hash, 'Incremental (binary)');\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), hash, 'Incremental (array buffer)');\n});\n\ntest('Hash of 160 bytes', function () {\n    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a765d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76',\n        hash = '66a1e6b119bf30ade63378f770e52549';\n\n    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');\n    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), hash, 'Incremental (normal)');\n    hasher.appendBinary(str);\n    equal(hasher.end(), hash, 'Incremental (binary)');\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), hash, 'Incremental (array buffer)');\n});\n\ntest('Incremental usage', function () {\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456a234');\n\n    equal(hasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (normal) of 20+20+24');\n\n    hasher.reset();\n    hasher.appendBinary('5d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a421456a234');\n\n    equal(hasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (binary) of 20+20+24');\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n\n    equal(buffHasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (array buffer) of 20+20+24');\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n\n    equal(hasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (normal) of 20+20+20');\n\n    hasher.reset();\n    hasher.appendBinary('5d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a421456');\n\n    equal(hasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (binary) of 20+20+20');\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n\n    equal(buffHasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (array buffer) of 20+20+20');\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n\n    equal(hasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (normal) of 20+60+20');\n\n    hasher.reset();\n    hasher.appendBinary('5d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456');\n    hasher.appendBinary('5d41402abc4b2a421456');\n\n    equal(hasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (binary) of 20+60+20');\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n\n    equal(buffHasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (array buffer) of 20+60+20');\n\n});\n\ntest('Incremental usage (resume)', function () {\n    var md5,\n        state;\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456a234');\n    md5 = hasher.end();\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    state = hasher.getState();\n    hasher.reset();\n    hasher.setState(state);\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456a234');\n\n    equal(hasher.end(), md5, 'MD5 should be the same');\n    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');\n\n    // Same tests but for buffers\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n    md5 = buffHasher.end();\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    state = buffHasher.getState();\n\n    buffHasher.reset();\n    buffHasher.setState(state);\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n\n    equal(buffHasher.end(), md5, 'MD5 should be the same');\n    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');\n});\n\ntest('Incremental usage (rolling)', function () {\n    var md5,\n        state;\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456a234');\n    md5 = hasher.end();\n\n    hasher.reset();\n    hasher.append('5d41402abc4b2a421456');\n    state = hasher.getState();\n    hasher.end();\n    hasher.setState(state);\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456');\n    hasher.append('5d41402abc4b2a421456a234');\n\n    equal(hasher.end(), md5, 'MD5 should be the same');\n    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');\n\n    // Same tests but for buffers\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n    md5 = buffHasher.end();\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    state = buffHasher.getState();\n\n    buffHasher.end();\n    buffHasher.setState(state);\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n\n    equal(buffHasher.end(), md5, 'MD5 should be the same');\n    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');\n});\n\ntest('Incremental usage (resume with JSON.stringify)', function () {\n    var md5,\n        state;\n\n    // Same tests but for buffers\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n    md5 = buffHasher.end();\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    state = JSON.stringify(buffHasher.getState());\n\n    buffHasher.reset();\n    buffHasher.setState(JSON.parse(state));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));\n    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));\n\n    equal(buffHasher.end(), md5, 'MD5 should be the same');\n    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');\n});\n\ntest('UTF-8', function () {\n    var str = 'räksmörgås';\n\n    equal(SparkMD5.hash(str), 'e462805dcf84413d5eddca45a4b88a5e', 'SparkMD5.hash() of \"' + str + '\"');\n    equal(SparkMD5.hashBinary(str), '09d9d71ec8a8e3bc74e51ebd587154f3', 'SparkMD5.hashBinary() of \"' + str + '\"');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), 'e462805dcf84413d5eddca45a4b88a5e', 'SparkMD5.ArrayBuffer.hash() of \"' + str + '\"');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), 'e462805dcf84413d5eddca45a4b88a5e', 'Incremental (normal) of \"' + str + '\"');\n\n    hasher.reset();\n    hasher.appendBinary(str);\n    equal(hasher.end(), '09d9d71ec8a8e3bc74e51ebd587154f3', 'Incremental (binary) of \"' + str + '\"');\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), 'e462805dcf84413d5eddca45a4b88a5e', 'Incremental (array buffer) of \"' + str + '\"');\n\n    str = '\\u30b9\\u3092\\u98df';\n\n    equal(SparkMD5.hash(str), '453931ab48a4a5af69f3da3c21064fc9', 'SparkMD5.hash() of \"' + str + '\"');\n    equal(SparkMD5.hashBinary(str), '24e3399be06b7cf59dbd848e18d9246c', 'SparkMD5.hashBinary() of \"' + str + '\"');\n    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), '453931ab48a4a5af69f3da3c21064fc9', 'SparkMD5.ArrayBuffer.hash() of \"' + str + '\"');\n\n    hasher.reset();\n    hasher.append(str);\n    equal(hasher.end(), '453931ab48a4a5af69f3da3c21064fc9', 'Incremental (normal) of \"' + str + '\"');\n\n    hasher.reset();\n    hasher.appendBinary(str);\n    equal(hasher.end(), '24e3399be06b7cf59dbd848e18d9246c', 'Incremental (binary) of \"' + str + '\"');\n\n    buffHasher.reset();\n    buffHasher.append(unicodeStringToArrayBuffer(str));\n    equal(buffHasher.end(), '453931ab48a4a5af69f3da3c21064fc9', 'Incremental (array buffer) of \"' + str + '\"');\n});\n\ntest('Hashing a PNG - ArrayBuffer vs binary string', function () {\n    var binString,\n        buffer;\n\n    // 1x1 transparent PNG\n    binString = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=');\n    buffer = stringToArrayBuffer(binString);\n\n    buffHasher.reset();\n    buffHasher.append(buffer);\n\n    hasher.reset();\n    hasher.appendBinary(binString);\n\n    equal(buffHasher.end(), hasher.end(), 'md5 sum should be the same for both binary strings and ArrayBuffers');\n});\n"
  }
]