Full Code of satazor/SparkMD5 for AI

master 9315385868fe cached
19 files
159.6 KB
41.3k tokens
67 symbols
1 requests
Download .txt
Repository: satazor/SparkMD5
Branch: master
Commit: 9315385868fe
Files: 19
Total size: 159.6 KB

Directory structure:
gitextract_24fyzl0z/

├── .editorconfig
├── .gitignore
├── .jscsrc
├── .jshintrc
├── LICENSE
├── LICENSE2
├── README.md
├── bower.json
├── component.json
├── package.json
├── spark-md5.js
└── test/
    ├── css/
    │   └── qunit-1.16.0.css
    ├── file_reader.html
    ├── file_reader_binary.html
    ├── index.html
    ├── index.min.html
    ├── js/
    │   └── qunit-1.16.0.js
    ├── readme_example.html
    └── specs.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[package.json]
indent_size = 2


================================================
FILE: .gitignore
================================================
node_modules
npm-debug.*

================================================
FILE: .jscsrc
================================================
{
    "disallowKeywordsOnNewLine": [
        "else",
        "catch"
    ],
    "disallowMixedSpacesAndTabs": true,
    "disallowSpacesInCallExpression": true,
    "disallowSpacesInFunctionDeclaration": {
        "beforeOpeningRoundBrace": true
    },
    "disallowTrailingComma": true,
    "disallowTrailingWhitespace": true,
    "excludeFiles": [
        "bower_components/**",
        "node_modules/**"
    ],
    "fileExtensions": [
        ".js",
        "jscs"
    ],
    "requireCommaBeforeLineBreak": true,
    "requireCurlyBraces": [
        "if",
        "else",
        "for",
        "while",
        "do",
        "try",
        "catch"
    ],
    "requireMultipleVarDecl": "onevar",
    "requireSpaceAfterBinaryOperators": true,
    "requireSpaceAfterKeywords": [
        "if",
        "else",
        "for",
        "while",
        "do",
        "switch",
        "return",
        "try",
        "catch",
        "function"
    ],
    "requireSpaceBeforeBinaryOperators": [
        "=",
        "+=",
        "-=",
        "*=",
        "/=",
        "%=",
        "<<=",
        ">>=",
        ">>>=",
        "&=",
        "|=",
        "^=",
        "+=",
        "+",
        "-",
        "*",
        "/",
        "%",
        "<<",
        ">>",
        ">>>",
        "&",
        "|",
        "^",
        "&&",
        "||",
        "===",
        "==",
        ">=",
        "<=",
        "<",
        ">",
        "!=",
        "!=="
    ],
    "requireSpaceBeforeBlockStatements": true,
    "requireSpaceBeforeKeywords": [
        "else"
    ],
    "requireSpaceBeforeObjectValues": true,
    "requireSpacesInConditionalExpression": true,
    "requireSpacesInFunctionExpression": {
        "beforeOpeningCurlyBrace": true,
        "beforeOpeningRoundBrace": true
    },
    "requireSpacesInsideObjectBrackets": "allButNested",
    "validateIndentation": 4,
    "validateParameterSeparator": ", ",
    "validateQuoteMarks": "'"
}


================================================
FILE: .jshintrc
================================================
{
    "predef": [
        "console",
        "define",
        "self",
        "unescape",
        "module",
        "File",
        "SparkMD5"
    ],

    "browser": true,
    "devel": true,

    "bitwise": false,
    "curly": true,
    "eqeqeq": false,
    "forin": false,
    "immed": true,
    "latedef": false,
    "newcap": true,
    "noarg": true,
    "noempty": false,
    "nonew": true,
    "plusplus": false,
    "regexp": true,
    "undef": true,
    "unused": true,
    "quotmark": "single",
    "strict": false,
    "trailing": true,

    "asi": false,
    "boss": false,
    "debug": false,
    "eqnull": true,
    "es5": true,
    "esnext": false,
    "evil": false,
    "expr": true,
    "funcscope": false,
    "globalstrict": false,
    "iterator": false,
    "lastsemic": false,
    "laxbreak": false,
    "laxcomma": false,
    "loopfunc": true,
    "multistr": true,
    "onecase": true,
    "regexdash": false,
    "scripturl": false,
    "smarttabs": false,
    "shadow": false,
    "sub": false,
    "supernew": false,
    "validthis": false,

    "nomen": false,
    "onevar": false,
    "white": true
}


================================================
FILE: LICENSE
================================================
            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                    Version 2, December 2004

 Copyright (C) 2015 André Cruz <amdfcruz@gmail.com>

 Everyone is permitted to copy and distribute verbatim or modified
 copies of this license document, and changing it is allowed as long
 as the name is changed.

            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. You just DO WHAT THE FUCK YOU WANT TO.


================================================
FILE: LICENSE2
================================================
Copyright (c) 2015 André Cruz <amdfcruz@gmail.com>

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.


================================================
FILE: README.md
================================================
# SparkMD5

SparkMD5 is a fast md5 implementation of the MD5 algorithm.
This 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.

NOTE: Please disable Firebug while performing the test!
      Firebug consumes a lot of memory and CPU and slows the test by a great margin.


**[Demo](http://9px.ir/demo/incremental-md5.html)**

## Install

```sh
npm install --save spark-md5
```

## Improvements over the JKM md5 library

 * Strings are converted to utf8, like most server side algorithms
 * Fix computation for large amounts of data (overflow)
 * Incremental md5 (see below)
 * Support for array buffers (typed arrays)
 * Functionality wrapped in a closure, to avoid global assignments
 * Object oriented library
 * CommonJS (it can be used in node) and AMD integration
 * Code passed through JSHint and JSCS


Incremental md5 performs a lot better for hashing large amounts of data, such as
files. One could read files in chunks, using the FileReader & Blob's, and append
each chunk for md5 hashing while keeping memory usage low. See example below.


## Usage

### Normal usage

```js
var hexHash = SparkMD5.hash('Hi there');        // hex hash
var rawHash = SparkMD5.hash('Hi there', true);  // OR raw hash (binary string)
```

### Incremental usage

```js
var spark = new SparkMD5();
spark.append('Hi');
spark.append(' there');
var hexHash = spark.end();                      // hex hash
var rawHash = spark.end(true);                  // OR raw hash (binary string)
```

### Hash a file incrementally

NOTE: If you test the code bellow using the file:// protocol in chrome you must start the browser with -allow-file-access-from-files argument.
      Please see: http://code.google.com/p/chromium/issues/detail?id=60889

```js
document.getElementById('file').addEventListener('change', function () {
    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
        file = this.files[0],
        chunkSize = 2097152,                             // Read in chunks of 2MB
        chunks = Math.ceil(file.size / chunkSize),
        currentChunk = 0,
        spark = new SparkMD5.ArrayBuffer(),
        fileReader = new FileReader();

    fileReader.onload = function (e) {
        console.log('read chunk nr', currentChunk + 1, 'of', chunks);
        spark.append(e.target.result);                   // Append array buffer
        currentChunk++;

        if (currentChunk < chunks) {
            loadNext();
        } else {
            console.log('finished loading');
            console.info('computed hash', spark.end());  // Compute hash
        }
    };

    fileReader.onerror = function () {
        console.warn('oops, something went wrong.');
    };

    function loadNext() {
        var start = currentChunk * chunkSize,
            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;

        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
    }

    loadNext();
});
```

You can see some more examples in the test folder.

## Documentation


### SparkMD5 class

#### SparkMD5#append(str)

Appends a string, encoding it to UTF8 if necessary.

#### SparkMD5#appendBinary(str)

Appends a binary string (e.g.: string returned from the deprecated [readAsBinaryString](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString)).

#### SparkMD5#end(raw)

Finishes the computation of the md5, returning the hex result.
If `raw` is true, the result as a binary string will be returned instead.

#### SparkMD5#reset()

Resets the internal state of the computation.

#### SparkMD5#getState()

Returns an object representing the internal computation state.
You can pass this state to setState(). This feature is useful to resume an incremental md5.

#### SparkMD5#setState(state)

Sets the internal computation state. See: getState().

#### SparkMD5#destroy()

Releases memory used by the incremental buffer and other additional resources.

#### SparkMD5.hash(str, raw)

Hashes a string directly, returning the hex result.
If `raw` is true, the result as a binary string will be returned instead.
Note that this function is `static`.

#### SparkMD5.hashBinary(str, raw)

Hashes 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.
If `raw` is true, the result as a binary string will be returned instead.
Note that this function is `static`.


### SparkMD5.ArrayBuffer class

#### SparkMD5.ArrayBuffer#append(arr)

Appends an array buffer.

#### SparkMD5.ArrayBuffer#end(raw)

Finishes the computation of the md5, returning the hex result.
If `raw` is true, the result as a binary string will be returned instead.

#### SparkMD5.ArrayBuffer#reset()

Resets the internal state of the computation.

#### SparkMD5.ArrayBuffer#destroy()

Releases memory used by the incremental buffer and other additional resources.

#### SparkMD5.ArrayBuffer#getState()

Returns an object representing the internal computation state.
You can pass this state to setState(). This feature is useful to resume an incremental md5.

#### SparkMD5.ArrayBuffer#setState(state)

Sets the internal computation state. See: getState().

#### SparkMD5.ArrayBuffer.hash(arr, raw)

Hashes an array buffer directly, returning the hex result.
If `raw` is true, the result as a binary string will be returned instead.
Note that this function is `static`.


## License

The project is double licensed, being [WTF2](./LICENSE) the master license and [MIT](./LICENSE2) the alternative license.
The reason to have two licenses is that some entities refuse to use the master license (WTF2) due to
bad language. If that's also your case, you can choose the alternative license.


## Credits

[Joseph Myers](http://www.myersdaily.org/joseph/javascript/md5-text.html)


================================================
FILE: bower.json
================================================
{
  "name": "SparkMD5",
  "version": "3.0.0",
  "homepage": "https://github.com/satazor/js-spark-md5",
  "authors": [
    "André Cruz <andremiguelcruz@msn.com>"
  ],
  "description": "Lightning fast normal and incremental md5 for javascript",
  "main": "spark-md5.js",
  "keywords": [
    "md5",
    "spark",
    "incremental",
    "fast"
  ],
  "license": "http://www.wtfpl.net/",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}


================================================
FILE: component.json
================================================
{
  "name": "md5",
  "repo": "satazor/js-spark-md5",
  "description": "Lightning fast normal and incremental md5 for javascript",
  "version": "2.0.0",
  "keywords": [
    "md5",
    "fast",
    "spark",
    "incremental"
  ],
  "dependencies": {},
  "development": {},
  "license": "WTFPL",
  "main": "spark-md5.js",
  "scripts": [
    "spark-md5.js"
  ]
}


================================================
FILE: package.json
================================================
{
  "name": "spark-md5",
  "version": "3.0.2",
  "description": "Lightning fast normal and incremental md5 for javascript",
  "main": "spark-md5.js",
  "files": [
    "spark-md5.js",
    "spark-md5.min.js"
  ],
  "directories": {
    "test": "test"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:satazor/js-spark-md5.git"
  },
  "keywords": [
    "md5",
    "fast",
    "spark",
    "incremental"
  ],
  "author": "André Cruz <andremiguelcruz@msn.com>",
  "license": "(WTFPL OR MIT)",
  "bugs": {
    "url": "https://github.com/satazor/js-spark-md5/issues"
  },
  "scripts": {
    "min": "uglifyjs spark-md5.js > spark-md5.min.js",
    "test": "open test/index.html"
  },
  "devDependencies": {
    "uglify-js": "^3.0.0"
  }
}


================================================
FILE: spark-md5.js
================================================
(function (factory) {
    if (typeof exports === 'object') {
        // Node/CommonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // Browser globals (with support for web workers)
        var glob;

        try {
            glob = window;
        } catch (e) {
            glob = self;
        }

        glob.SparkMD5 = factory();
    }
}(function (undefined) {

    'use strict';

    /*
     * Fastest md5 implementation around (JKM md5).
     * Credits: Joseph Myers
     *
     * @see http://www.myersdaily.org/joseph/javascript/md5-text.html
     * @see http://jsperf.com/md5-shootout/7
     */

    /* this function is much faster,
      so if possible we use it. Some IEs
      are the only ones I know of that
      need the idiotic second function,
      generated by an if clause.  */
    var add32 = function (a, b) {
        return (a + b) & 0xFFFFFFFF;
    },
        hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];


    function cmn(q, a, b, x, s, t) {
        a = add32(add32(a, q), add32(x, t));
        return add32((a << s) | (a >>> (32 - s)), b);
    }

    function md5cycle(x, k) {
        var a = x[0],
            b = x[1],
            c = x[2],
            d = x[3];

        a += (b & c | ~b & d) + k[0] - 680876936 | 0;
        a  = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[1] - 389564586 | 0;
        d  = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[2] + 606105819 | 0;
        c  = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[3] - 1044525330 | 0;
        b  = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[4] - 176418897 | 0;
        a  = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[5] + 1200080426 | 0;
        d  = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[6] - 1473231341 | 0;
        c  = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[7] - 45705983 | 0;
        b  = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[8] + 1770035416 | 0;
        a  = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[9] - 1958414417 | 0;
        d  = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[10] - 42063 | 0;
        c  = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[11] - 1990404162 | 0;
        b  = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[12] + 1804603682 | 0;
        a  = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[13] - 40341101 | 0;
        d  = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[14] - 1502002290 | 0;
        c  = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[15] + 1236535329 | 0;
        b  = (b << 22 | b >>> 10) + c | 0;

        a += (b & d | c & ~d) + k[1] - 165796510 | 0;
        a  = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[6] - 1069501632 | 0;
        d  = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[11] + 643717713 | 0;
        c  = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[0] - 373897302 | 0;
        b  = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[5] - 701558691 | 0;
        a  = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[10] + 38016083 | 0;
        d  = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[15] - 660478335 | 0;
        c  = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[4] - 405537848 | 0;
        b  = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[9] + 568446438 | 0;
        a  = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[14] - 1019803690 | 0;
        d  = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[3] - 187363961 | 0;
        c  = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[8] + 1163531501 | 0;
        b  = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[13] - 1444681467 | 0;
        a  = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[2] - 51403784 | 0;
        d  = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[7] + 1735328473 | 0;
        c  = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[12] - 1926607734 | 0;
        b  = (b << 20 | b >>> 12) + c | 0;

        a += (b ^ c ^ d) + k[5] - 378558 | 0;
        a  = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[8] - 2022574463 | 0;
        d  = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[11] + 1839030562 | 0;
        c  = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[14] - 35309556 | 0;
        b  = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[1] - 1530992060 | 0;
        a  = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[4] + 1272893353 | 0;
        d  = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[7] - 155497632 | 0;
        c  = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[10] - 1094730640 | 0;
        b  = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[13] + 681279174 | 0;
        a  = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[0] - 358537222 | 0;
        d  = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[3] - 722521979 | 0;
        c  = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[6] + 76029189 | 0;
        b  = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[9] - 640364487 | 0;
        a  = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[12] - 421815835 | 0;
        d  = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[15] + 530742520 | 0;
        c  = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[2] - 995338651 | 0;
        b  = (b << 23 | b >>> 9) + c | 0;

        a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;
        a  = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;
        d  = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;
        c  = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;
        b  = (b << 21 |b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;
        a  = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;
        d  = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;
        c  = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;
        b  = (b << 21 |b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;
        a  = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;
        d  = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;
        c  = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;
        b  = (b << 21 |b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;
        a  = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;
        d  = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;
        c  = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;
        b  = (b << 21 | b >>> 11) + c | 0;

        x[0] = a + x[0] | 0;
        x[1] = b + x[1] | 0;
        x[2] = c + x[2] | 0;
        x[3] = d + x[3] | 0;
    }

    function md5blk(s) {
        var md5blks = [],
            i; /* Andy King said do it this way. */

        for (i = 0; i < 64; i += 4) {
            md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
        }
        return md5blks;
    }

    function md5blk_array(a) {
        var md5blks = [],
            i; /* Andy King said do it this way. */

        for (i = 0; i < 64; i += 4) {
            md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
        }
        return md5blks;
    }

    function md51(s) {
        var n = s.length,
            state = [1732584193, -271733879, -1732584194, 271733878],
            i,
            length,
            tail,
            tmp,
            lo,
            hi;

        for (i = 64; i <= n; i += 64) {
            md5cycle(state, md5blk(s.substring(i - 64, i)));
        }
        s = s.substring(i - 64);
        length = s.length;
        tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        for (i = 0; i < length; i += 1) {
            tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
        }
        tail[i >> 2] |= 0x80 << ((i % 4) << 3);
        if (i > 55) {
            md5cycle(state, tail);
            for (i = 0; i < 16; i += 1) {
                tail[i] = 0;
            }
        }

        // Beware that the final length might not fit in 32 bits so we take care of that
        tmp = n * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;

        tail[14] = lo;
        tail[15] = hi;

        md5cycle(state, tail);
        return state;
    }

    function md51_array(a) {
        var n = a.length,
            state = [1732584193, -271733879, -1732584194, 271733878],
            i,
            length,
            tail,
            tmp,
            lo,
            hi;

        for (i = 64; i <= n; i += 64) {
            md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
        }

        // Not sure if it is a bug, however IE10 will always produce a sub array of length 1
        // containing the last element of the parent array if the sub array specified starts
        // beyond the length of the parent array - weird.
        // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
        a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);

        length = a.length;
        tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        for (i = 0; i < length; i += 1) {
            tail[i >> 2] |= a[i] << ((i % 4) << 3);
        }

        tail[i >> 2] |= 0x80 << ((i % 4) << 3);
        if (i > 55) {
            md5cycle(state, tail);
            for (i = 0; i < 16; i += 1) {
                tail[i] = 0;
            }
        }

        // Beware that the final length might not fit in 32 bits so we take care of that
        tmp = n * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;

        tail[14] = lo;
        tail[15] = hi;

        md5cycle(state, tail);

        return state;
    }

    function rhex(n) {
        var s = '',
            j;
        for (j = 0; j < 4; j += 1) {
            s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
        }
        return s;
    }

    function hex(x) {
        var i;
        for (i = 0; i < x.length; i += 1) {
            x[i] = rhex(x[i]);
        }
        return x.join('');
    }

    // In some cases the fast add32 function cannot be used..
    if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') {
        add32 = function (x, y) {
            var lsw = (x & 0xFFFF) + (y & 0xFFFF),
                msw = (x >> 16) + (y >> 16) + (lsw >> 16);
            return (msw << 16) | (lsw & 0xFFFF);
        };
    }

    // ---------------------------------------------------

    /**
     * ArrayBuffer slice polyfill.
     *
     * @see https://github.com/ttaubert/node-arraybuffer-slice
     */

    if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
        (function () {
            function clamp(val, length) {
                val = (val | 0) || 0;

                if (val < 0) {
                    return Math.max(val + length, 0);
                }

                return Math.min(val, length);
            }

            ArrayBuffer.prototype.slice = function (from, to) {
                var length = this.byteLength,
                    begin = clamp(from, length),
                    end = length,
                    num,
                    target,
                    targetArray,
                    sourceArray;

                if (to !== undefined) {
                    end = clamp(to, length);
                }

                if (begin > end) {
                    return new ArrayBuffer(0);
                }

                num = end - begin;
                target = new ArrayBuffer(num);
                targetArray = new Uint8Array(target);

                sourceArray = new Uint8Array(this, begin, num);
                targetArray.set(sourceArray);

                return target;
            };
        })();
    }

    // ---------------------------------------------------

    /**
     * Helpers.
     */

    function toUtf8(str) {
        if (/[\u0080-\uFFFF]/.test(str)) {
            str = unescape(encodeURIComponent(str));
        }

        return str;
    }

    function utf8Str2ArrayBuffer(str, returnUInt8Array) {
        var length = str.length,
           buff = new ArrayBuffer(length),
           arr = new Uint8Array(buff),
           i;

        for (i = 0; i < length; i += 1) {
            arr[i] = str.charCodeAt(i);
        }

        return returnUInt8Array ? arr : buff;
    }

    function arrayBuffer2Utf8Str(buff) {
        return String.fromCharCode.apply(null, new Uint8Array(buff));
    }

    function concatenateArrayBuffers(first, second, returnUInt8Array) {
        var result = new Uint8Array(first.byteLength + second.byteLength);

        result.set(new Uint8Array(first));
        result.set(new Uint8Array(second), first.byteLength);

        return returnUInt8Array ? result : result.buffer;
    }

    function hexToBinaryString(hex) {
        var bytes = [],
            length = hex.length,
            x;

        for (x = 0; x < length - 1; x += 2) {
            bytes.push(parseInt(hex.substr(x, 2), 16));
        }

        return String.fromCharCode.apply(String, bytes);
    }

    // ---------------------------------------------------

    /**
     * SparkMD5 OOP implementation.
     *
     * Use this class to perform an incremental md5, otherwise use the
     * static methods instead.
     */

    function SparkMD5() {
        // call reset to init the instance
        this.reset();
    }

    /**
     * Appends a string.
     * A conversion will be applied if an utf8 string is detected.
     *
     * @param {String} str The string to be appended
     *
     * @return {SparkMD5} The instance itself
     */
    SparkMD5.prototype.append = function (str) {
        // Converts the string to utf8 bytes if necessary
        // Then append as binary
        this.appendBinary(toUtf8(str));

        return this;
    };

    /**
     * Appends a binary string.
     *
     * @param {String} contents The binary string to be appended
     *
     * @return {SparkMD5} The instance itself
     */
    SparkMD5.prototype.appendBinary = function (contents) {
        this._buff += contents;
        this._length += contents.length;

        var length = this._buff.length,
            i;

        for (i = 64; i <= length; i += 64) {
            md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));
        }

        this._buff = this._buff.substring(i - 64);

        return this;
    };

    /**
     * Finishes the incremental computation, reseting the internal state and
     * returning the result.
     *
     * @param {Boolean} raw True to get the raw string, false to get the hex string
     *
     * @return {String} The result
     */
    SparkMD5.prototype.end = function (raw) {
        var buff = this._buff,
            length = buff.length,
            i,
            tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            ret;

        for (i = 0; i < length; i += 1) {
            tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);
        }

        this._finish(tail, length);
        ret = hex(this._hash);

        if (raw) {
            ret = hexToBinaryString(ret);
        }

        this.reset();

        return ret;
    };

    /**
     * Resets the internal state of the computation.
     *
     * @return {SparkMD5} The instance itself
     */
    SparkMD5.prototype.reset = function () {
        this._buff = '';
        this._length = 0;
        this._hash = [1732584193, -271733879, -1732584194, 271733878];

        return this;
    };

    /**
     * Gets the internal state of the computation.
     *
     * @return {Object} The state
     */
    SparkMD5.prototype.getState = function () {
        return {
            buff: this._buff,
            length: this._length,
            hash: this._hash.slice()
        };
    };

    /**
     * Gets the internal state of the computation.
     *
     * @param {Object} state The state
     *
     * @return {SparkMD5} The instance itself
     */
    SparkMD5.prototype.setState = function (state) {
        this._buff = state.buff;
        this._length = state.length;
        this._hash = state.hash;

        return this;
    };

    /**
     * Releases memory used by the incremental buffer and other additional
     * resources. If you plan to use the instance again, use reset instead.
     */
    SparkMD5.prototype.destroy = function () {
        delete this._hash;
        delete this._buff;
        delete this._length;
    };

    /**
     * Finish the final calculation based on the tail.
     *
     * @param {Array}  tail   The tail (will be modified)
     * @param {Number} length The length of the remaining buffer
     */
    SparkMD5.prototype._finish = function (tail, length) {
        var i = length,
            tmp,
            lo,
            hi;

        tail[i >> 2] |= 0x80 << ((i % 4) << 3);
        if (i > 55) {
            md5cycle(this._hash, tail);
            for (i = 0; i < 16; i += 1) {
                tail[i] = 0;
            }
        }

        // Do the final computation based on the tail and length
        // Beware that the final length may not fit in 32 bits so we take care of that
        tmp = this._length * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;

        tail[14] = lo;
        tail[15] = hi;
        md5cycle(this._hash, tail);
    };

    /**
     * Performs the md5 hash on a string.
     * A conversion will be applied if utf8 string is detected.
     *
     * @param {String}  str The string
     * @param {Boolean} [raw] True to get the raw string, false to get the hex string
     *
     * @return {String} The result
     */
    SparkMD5.hash = function (str, raw) {
        // Converts the string to utf8 bytes if necessary
        // Then compute it using the binary function
        return SparkMD5.hashBinary(toUtf8(str), raw);
    };

    /**
     * Performs the md5 hash on a binary string.
     *
     * @param {String}  content The binary string
     * @param {Boolean} [raw]     True to get the raw string, false to get the hex string
     *
     * @return {String} The result
     */
    SparkMD5.hashBinary = function (content, raw) {
        var hash = md51(content),
            ret = hex(hash);

        return raw ? hexToBinaryString(ret) : ret;
    };

    // ---------------------------------------------------

    /**
     * SparkMD5 OOP implementation for array buffers.
     *
     * Use this class to perform an incremental md5 ONLY for array buffers.
     */
    SparkMD5.ArrayBuffer = function () {
        // call reset to init the instance
        this.reset();
    };

    /**
     * Appends an array buffer.
     *
     * @param {ArrayBuffer} arr The array to be appended
     *
     * @return {SparkMD5.ArrayBuffer} The instance itself
     */
    SparkMD5.ArrayBuffer.prototype.append = function (arr) {
        var buff = concatenateArrayBuffers(this._buff.buffer, arr, true),
            length = buff.length,
            i;

        this._length += arr.byteLength;

        for (i = 64; i <= length; i += 64) {
            md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
        }

        this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);

        return this;
    };

    /**
     * Finishes the incremental computation, reseting the internal state and
     * returning the result.
     *
     * @param {Boolean} raw True to get the raw string, false to get the hex string
     *
     * @return {String} The result
     */
    SparkMD5.ArrayBuffer.prototype.end = function (raw) {
        var buff = this._buff,
            length = buff.length,
            tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            i,
            ret;

        for (i = 0; i < length; i += 1) {
            tail[i >> 2] |= buff[i] << ((i % 4) << 3);
        }

        this._finish(tail, length);
        ret = hex(this._hash);

        if (raw) {
            ret = hexToBinaryString(ret);
        }

        this.reset();

        return ret;
    };

    /**
     * Resets the internal state of the computation.
     *
     * @return {SparkMD5.ArrayBuffer} The instance itself
     */
    SparkMD5.ArrayBuffer.prototype.reset = function () {
        this._buff = new Uint8Array(0);
        this._length = 0;
        this._hash = [1732584193, -271733879, -1732584194, 271733878];

        return this;
    };

    /**
     * Gets the internal state of the computation.
     *
     * @return {Object} The state
     */
    SparkMD5.ArrayBuffer.prototype.getState = function () {
        var state = SparkMD5.prototype.getState.call(this);

        // Convert buffer to a string
        state.buff = arrayBuffer2Utf8Str(state.buff);

        return state;
    };

    /**
     * Gets the internal state of the computation.
     *
     * @param {Object} state The state
     *
     * @return {SparkMD5.ArrayBuffer} The instance itself
     */
    SparkMD5.ArrayBuffer.prototype.setState = function (state) {
        // Convert string to buffer
        state.buff = utf8Str2ArrayBuffer(state.buff, true);

        return SparkMD5.prototype.setState.call(this, state);
    };

    SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;

    SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;

    /**
     * Performs the md5 hash on an array buffer.
     *
     * @param {ArrayBuffer} arr The array buffer
     * @param {Boolean}     [raw] True to get the raw string, false to get the hex one
     *
     * @return {String} The result
     */
    SparkMD5.ArrayBuffer.hash = function (arr, raw) {
        var hash = md51_array(new Uint8Array(arr)),
            ret = hex(hash);

        return raw ? hexToBinaryString(ret) : ret;
    };

    return SparkMD5;
}));


================================================
FILE: test/css/qunit-1.16.0.css
================================================
/*!
 * QUnit 1.16.0
 * http://qunitjs.com/
 *
 * Copyright 2006, 2014 jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-12-03T16:32Z
 */

/** Font Family and Sizes */

#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
    font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}

#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }


/** Resets */

#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
    margin: 0;
    padding: 0;
}


/** Header */

#qunit-header {
    padding: 0.5em 0 0.5em 1em;

    color: #8699A4;
    background-color: #0D3349;

    font-size: 1.5em;
    line-height: 1em;
    font-weight: 400;

    border-radius: 5px 5px 0 0;
}

#qunit-header a {
    text-decoration: none;
    color: #C2CCD1;
}

#qunit-header a:hover,
#qunit-header a:focus {
    color: #FFF;
}

#qunit-testrunner-toolbar label {
    display: inline-block;
    padding: 0 0.5em 0 0.1em;
}

#qunit-banner {
    height: 5px;
}

#qunit-testrunner-toolbar {
    padding: 0.5em 1em 0.5em 1em;
    color: #5E740B;
    background-color: #EEE;
    overflow: hidden;
}

#qunit-userAgent {
    padding: 0.5em 1em 0.5em 1em;
    background-color: #2B81AF;
    color: #FFF;
    text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}

#qunit-modulefilter-container {
    float: right;
}

/** Tests: Pass/Fail */

#qunit-tests {
    list-style-position: inside;
}

#qunit-tests li {
    padding: 0.4em 1em 0.4em 1em;
    border-bottom: 1px solid #FFF;
    list-style-position: inside;
}

#qunit-tests > li {
    display: none;
}

#qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail {
    display: list-item;
}

#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
    display: none;
}

#qunit-tests li strong {
    cursor: pointer;
}

#qunit-tests li.skipped strong {
    cursor: default;
}

#qunit-tests li a {
    padding: 0.5em;
    color: #C2CCD1;
    text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
    color: #000;
}

#qunit-tests li .runtime {
    float: right;
    font-size: smaller;
}

.qunit-assert-list {
    margin-top: 0.5em;
    padding: 0.5em;

    background-color: #FFF;

    border-radius: 5px;
}

.qunit-collapsed {
    display: none;
}

#qunit-tests table {
    border-collapse: collapse;
    margin-top: 0.2em;
}

#qunit-tests th {
    text-align: right;
    vertical-align: top;
    padding: 0 0.5em 0 0;
}

#qunit-tests td {
    vertical-align: top;
}

#qunit-tests pre {
    margin: 0;
    white-space: pre-wrap;
    word-wrap: break-word;
}

#qunit-tests del {
    background-color: #E0F2BE;
    color: #374E0C;
    text-decoration: none;
}

#qunit-tests ins {
    background-color: #FFCACA;
    color: #500;
    text-decoration: none;
}

/*** Test Counts */

#qunit-tests b.counts                       { color: #000; }
#qunit-tests b.passed                       { color: #5E740B; }
#qunit-tests b.failed                       { color: #710909; }

#qunit-tests li li {
    padding: 5px;
    background-color: #FFF;
    border-bottom: none;
    list-style-position: inside;
}

/*** Passing Styles */

#qunit-tests li li.pass {
    color: #3C510C;
    background-color: #FFF;
    border-left: 10px solid #C6E746;
}

#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name               { color: #366097; }

#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected           { color: #999; }

#qunit-banner.qunit-pass                    { background-color: #C6E746; }

/*** Failing Styles */

#qunit-tests li li.fail {
    color: #710909;
    background-color: #FFF;
    border-left: 10px solid #EE5757;
    white-space: pre;
}

#qunit-tests > li:last-child {
    border-radius: 0 0 5px 5px;
}

#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name             { color: #000; }

#qunit-tests .fail .test-actual             { color: #EE5757; }
#qunit-tests .fail .test-expected           { color: #008000; }

#qunit-banner.qunit-fail                    { background-color: #EE5757; }

/*** Skipped tests */

#qunit-tests .skipped {
    background-color: #EBECE9;
}

#qunit-tests .qunit-skipped-label {
    background-color: #F4FF77;
    display: inline-block;
    font-style: normal;
    color: #366097;
    line-height: 1.8em;
    padding: 0 0.5em;
    margin: -0.4em 0.4em -0.4em 0;
}

/** Result */

#qunit-testresult {
    padding: 0.5em 1em 0.5em 1em;

    color: #2B81AF;
    background-color: #D2E0E6;

    border-bottom: 1px solid #FFF;
}
#qunit-testresult .module-name {
    font-weight: 700;
}

/** Fixture */

#qunit-fixture {
    position: absolute;
    top: -10000px;
    left: -10000px;
    width: 1000px;
    height: 1000px;
}


================================================
FILE: test/file_reader.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>SparkMD5 file reader test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="css/bootstrap-1.4.min.css">
        <script src="../spark-md5.js" type="text/javascript"></script>

        <style type="text/css" media="screen">
            .alert-message {
                margin-bottom: 5px;
            }

            input.input-file {
                padding: 5px;
                margin-right: 25px;
                background-color: transparent;
                line-height: 1;
                vertical-align: middle;
            }
        </style>
    </head>
    <body class="container">
        <h1>SparkMD5 file reader test, incremental and normal md5</h1>

        <h4>Please note that the advantage of doing an incremental md5 is to keep memory usage low.</h4>
        <p>
            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).
            <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).
            <br/>With incremental md5, you should observe stable memory usage but slightly higher times.
            <br/>Be aware that while using normal md5, the browser can crash due to high memory usage.
        </p>

        <div class="actions">
            <input type="file" id="file" class="input-file span5"/>
            <input type="button" id="normal" value="Normal" class="btn primary"/>
            <input type="button" id="incremental" value="Incremental" class="btn primary"/>
            <input type="button" id="clear" value="Clear" class="btn"/>
        </div>
        <div id="log"></div>

        <script type="text/javascript">
            var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                log = document.getElementById('log'),
                input = document.getElementById('file'),
                running = false,
                ua = navigator.userAgent.toLowerCase();

            function registerLog(str, className) {
                var elem = document.createElement('div');

                elem.innerHTML = str;
                elem.className = 'alert-message' + (className ? ' '  + className : '');
                log.appendChild(elem);
            }

            function doIncrementalTest() {
                if (running) {
                    return;
                }

                if (!input.files.length) {
                    registerLog('<strong>Please select a file.</strong><br/>');
                    return;
                }

                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                    file = input.files[0],
                    chunkSize = 2097152,                           // read in chunks of 2MB
                    chunks = Math.ceil(file.size / chunkSize),
                    currentChunk = 0,
                    spark = new SparkMD5.ArrayBuffer(),
                    time,
                    uniqueId = 'chunk_' + (new Date().getTime()),
                    chunkId = null,
                    fileReader = new FileReader();

                fileReader.onload = function (e) {
                    if (currentChunk === 0) {
                        registerLog('Read chunk number <strong id="' + uniqueId + '">' + (currentChunk + 1) + '</strong> of <strong>' + chunks + '</strong><br/>', 'info');
                    } else {
                        if (chunkId === null) {
                            chunkId = document.getElementById(uniqueId);
                        }

                        chunkId.innerHTML = currentChunk + 1;
                    }

                    spark.append(e.target.result);                 // append array buffer
                    currentChunk += 1;

                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        running = false;
                        registerLog('<strong>Finished loading!</strong><br/>', 'success');
                        registerLog('<strong>Computed hash:</strong> ' + spark.end() + '<br/>', 'success'); // compute hash
                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');
                    }
                };

                fileReader.onerror = function () {
                    running = false;
                    registerLog('<strong>Oops, something went wrong.</strong>', 'error');
                };

                function loadNext() {
                    var start = currentChunk * chunkSize,
                        end = start + chunkSize >= file.size ? file.size : start + chunkSize;

                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                }

                running = true;
                registerLog('<p></p><strong>Starting incremental test (' + file.name + ')</strong><br/>', 'info');
                time = new Date().getTime();
                loadNext();
            }

            function doNormalTest() {
                if (running) {
                    return;
                }

                if (!input.files.length) {
                    registerLog('<strong>Please select a file.</strong><br/>');
                    return;
                }

                var fileReader = new FileReader(),
                    file = input.files[0],
                    time;

                fileReader.onload = function (e) {
                    running = false;

                    if (file.size != e.target.result.byteLength) {
                        registerLog('<strong>ERROR:</strong> Browser reported success but could not read the file until the end.<br/>', 'error');
                    } else {
                        registerLog('<strong>Finished loading!</strong><br/>', 'success');
                        registerLog('<strong>Computed hash:</strong> ' + SparkMD5.ArrayBuffer.hash(e.target.result) + '<br/>', 'success'); // compute hash
                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');
                    }
                };

                fileReader.onerror = function () {
                    running = false;
                    registerLog('<strong>ERROR:</strong> FileReader onerror was triggered, maybe the browser aborted due to high memory usage.<br/>', 'error');
                };

                running = true;
                registerLog('<strong>Starting normal test (' + file.name + ')</strong><br/>', 'info');
                time = new Date().getTime();
                fileReader.readAsArrayBuffer(file);
            }

            function clearLog() {
                if (!running) {
                    log.innerHTML = '';
                }
            }

            if (!('FileReader' in window) || !('File' in window) || !blobSlice) {
                registerLog('<p><strong>Your browser does not support the FileAPI or slicing of files.</strong></p>', 'error');
            } else {
                registerLog('Keep your devtools closed otherwise this example will be a LOT slower', 'info');

                if (/chrome/.test(ua)) {
                    if (location.protocol === 'file:') {
                        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>');
                    }
                }

                document.getElementById('normal').addEventListener('click', doNormalTest);
                document.getElementById('incremental').addEventListener('click', doIncrementalTest);
                document.getElementById('clear').addEventListener('click', clearLog);
            }
        </script>
    </body>
</html>


================================================
FILE: test/file_reader_binary.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>SparkMD5 file reader test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="css/bootstrap-1.4.min.css">
        <script src="../spark-md5.js" type="text/javascript"></script>

        <style type="text/css" media="screen">
            .alert-message {
                margin-bottom: 5px;
            }

            input.input-file {
                padding: 5px;
                margin-right: 25px;
                background-color: transparent;
                line-height: 1;
                vertical-align: middle;
            }
        </style>
    </head>
    <body class="container">
        <h1>SparkMD5 file reader test, incremental and normal md5</h1>

        <h4>Please note that the advantage of doing an incremental md5 is to keep memory usage low.</h4>
        <p>
            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).
            <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).
            <br/>With incremental md5, you should observe stable memory usage but slightly higher times.
            <br/>Be aware that while using normal md5, the browser can crash due to high memory usage.
        </p>

        <div class="actions">
            <input type="file" id="file" class="input-file span5"/>
            <input type="button" id="normal" value="Normal" class="btn primary"/>
            <input type="button" id="incremental" value="Incremental" class="btn primary"/>
            <input type="button" id="clear" value="Clear" class="btn"/>
        </div>
        <div id="log"></div>

        <script type="text/javascript">
            var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                log = document.getElementById('log'),
                input = document.getElementById('file'),
                running = false,
                ua = navigator.userAgent.toLowerCase();

            function registerLog(str, className) {
                var elem = document.createElement('div');

                elem.innerHTML = str;
                elem.className = 'alert-message' + (className ? ' '  + className : '');
                log.appendChild(elem);
            }

            function doIncrementalTest() {
                if (running) {
                    return;
                }

                if (!input.files.length) {
                    registerLog('<strong>Please select a file.</strong><br/>');
                    return;
                }

                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                    file = input.files[0],
                    chunkSize = 2097152,                           // read in chunks of 2MB
                    chunks = Math.ceil(file.size / chunkSize),
                    currentChunk = 0,
                    spark = new SparkMD5(),
                    time,
                    uniqueId = 'chunk_' + (new Date().getTime()),
                    chunkId = null,
                    fileReader = new FileReader();

                fileReader.onload = function (e) {
                    if (currentChunk === 0) {
                        registerLog('Read chunk number <strong id="' + uniqueId + '">' + (currentChunk + 1) + '</strong> of <strong>' + chunks + '</strong><br/>', 'info');
                    } else {
                        if (chunkId === null) {
                            chunkId = document.getElementById(uniqueId);
                        }

                        chunkId.innerHTML = currentChunk + 1;
                    }

                    spark.appendBinary(e.target.result);                 // append array buffer
                    currentChunk += 1;

                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        running = false;
                        registerLog('<strong>Finished loading!</strong><br/>', 'success');
                        registerLog('<strong>Computed hash:</strong> ' + spark.end() + '<br/>', 'success'); // compute hash
                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');
                    }
                };

                fileReader.onerror = function () {
                    running = false;
                    registerLog('<strong>Oops, something went wrong.</strong>', 'error');
                };

                function loadNext() {
                    var start = currentChunk * chunkSize,
                        end = start + chunkSize >= file.size ? file.size : start + chunkSize;

                    fileReader.readAsBinaryString(blobSlice.call(file, start, end));
                }

                running = true;
                registerLog('<p></p><strong>Starting incremental test (' + file.name + ')</strong><br/>', 'info');
                time = new Date().getTime();
                loadNext();
            }

            function doNormalTest() {
                if (running) {
                    return;
                }

                if (!input.files.length) {
                    registerLog('<strong>Please select a file.</strong><br/>');
                    return;
                }

                var fileReader = new FileReader(),
                    file = input.files[0],
                    time;

                fileReader.onload = function (e) {
                    running = false;

                    if (file.size != e.target.result.length) {
                        registerLog('<strong>ERROR:</strong> Browser reported success but could not read the file until the end.<br/>', 'error');
                    } else {
                        registerLog('<strong>Finished loading!</strong><br/>', 'success');
                        registerLog('<strong>Computed hash:</strong> ' + SparkMD5.hashBinary(e.target.result) + '<br/>', 'success'); // compute hash
                        registerLog('<strong>Total time:</strong> ' + (new Date().getTime() - time) + 'ms<br/>', 'success');
                    }
                };

                fileReader.onerror = function () {
                    running = false;
                    registerLog('<strong>ERROR:</strong> FileReader onerror was triggered, maybe the browser aborted due to high memory usage.<br/>', 'error');
                };

                running = true;
                registerLog('<strong>Starting normal test (' + file.name + ')</strong><br/>', 'info');
                time = new Date().getTime();
                fileReader.readAsBinaryString(file);
            }

            function clearLog() {
                if (!running) {
                    log.innerHTML = '';
                }
            }

            if (!('FileReader' in window) || !('File' in window) || !blobSlice) {
                registerLog('<p><strong>Your browser does not support the FileAPI or slicing of files.</strong></p>', 'error');
            } else {
                registerLog('Keep your devtools closed otherwise this example will be a LOT slower', 'info');

                if (/chrome/.test(ua)) {
                    if (location.protocol === 'file:') {
                        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>');
                    }
                }

                document.getElementById('normal').addEventListener('click', doNormalTest);
                document.getElementById('incremental').addEventListener('click', doIncrementalTest);
                document.getElementById('clear').addEventListener('click', clearLog);
            }
        </script>
    </body>
</html>


================================================
FILE: test/index.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>SparkMD5 tests</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="js/qunit-1.16.0.js"></script>
        <link rel="stylesheet" href="css/qunit-1.16.0.css" type="text/css" media="screen" />
        <script src="../spark-md5.js" type="text/javascript"></script>
    </head>
    <body>

        <h1 id="qunit-header">SparkMD5 test</h1>
        <h2 id="qunit-banner"></h2>
        <div id="qunit-testrunner-toolbar"></div>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
        <div id="qunit-fixture"></div>
        <div id="qunit-testresult"></div>

        <script src="specs.js" type="text/javascript"></script>
    </body>
</html>


================================================
FILE: test/index.min.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>SparkMD5 tests (minified version)</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="js/qunit-1.16.0.js"></script>
        <link rel="stylesheet" href="css/qunit-1.16.0.css" type="text/css" media="screen" />
        <script src="../spark-md5.js" type="text/javascript"></script>
    </head>
    <body>

        <h1 id="qunit-header">SparkMD5 test</h1>
        <h2 id="qunit-banner"></h2>
        <div id="qunit-testrunner-toolbar"></div>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
        <div id="qunit-fixture"></div>
        <div id="qunit-testresult"></div>

        <script src="specs.js" type="text/javascript"></script>
    </body>
</html>


================================================
FILE: test/js/qunit-1.16.0.js
================================================
/*!
 * QUnit 1.16.0
 * http://qunitjs.com/
 *
 * Copyright 2006, 2014 jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-12-03T16:32Z
 */

(function( window ) {

var QUnit,
    config,
    onErrorFnPrev,
    loggingCallbacks = {},
    fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
    toString = Object.prototype.toString,
    hasOwn = Object.prototype.hasOwnProperty,
    // Keep a local reference to Date (GH-283)
    Date = window.Date,
    now = Date.now || function() {
        return new Date().getTime();
    },
    globalStartCalled = false,
    runStarted = false,
    setTimeout = window.setTimeout,
    clearTimeout = window.clearTimeout,
    defined = {
        document: window.document !== undefined,
        setTimeout: window.setTimeout !== undefined,
        sessionStorage: (function() {
            var x = "qunit-test-string";
            try {
                sessionStorage.setItem( x, x );
                sessionStorage.removeItem( x );
                return true;
            } catch ( e ) {
                return false;
            }
        }())
    },
    /**
     * Provides a normalized error string, correcting an issue
     * with IE 7 (and prior) where Error.prototype.toString is
     * not properly implemented
     *
     * Based on http://es5.github.com/#x15.11.4.4
     *
     * @param {String|Error} error
     * @return {String} error message
     */
    errorString = function( error ) {
        var name, message,
            errorString = error.toString();
        if ( errorString.substring( 0, 7 ) === "[object" ) {
            name = error.name ? error.name.toString() : "Error";
            message = error.message ? error.message.toString() : "";
            if ( name && message ) {
                return name + ": " + message;
            } else if ( name ) {
                return name;
            } else if ( message ) {
                return message;
            } else {
                return "Error";
            }
        } else {
            return errorString;
        }
    },
    /**
     * Makes a clone of an object using only Array or Object as base,
     * and copies over the own enumerable properties.
     *
     * @param {Object} obj
     * @return {Object} New object with only the own properties (recursively).
     */
    objectValues = function( obj ) {
        var key, val,
            vals = QUnit.is( "array", obj ) ? [] : {};
        for ( key in obj ) {
            if ( hasOwn.call( obj, key ) ) {
                val = obj[ key ];
                vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
            }
        }
        return vals;
    };

QUnit = {};

/**
 * Config object: Maintain internal state
 * Later exposed as QUnit.config
 * `config` initialized at top of scope
 */
config = {
    // The queue of tests to run
    queue: [],

    // block until document ready
    blocking: true,

    // when enabled, show only failing tests
    // gets persisted through sessionStorage and can be changed in UI via checkbox
    hidepassed: false,

    // by default, run previously failed tests first
    // very useful in combination with "Hide passed tests" checked
    reorder: true,

    // by default, modify document.title when suite is done
    altertitle: true,

    // by default, scroll to top of the page when suite is done
    scrolltop: true,

    // when enabled, all tests must call expect()
    requireExpects: false,

    // add checkboxes that are persisted in the query-string
    // when enabled, the id is set to `true` as a `QUnit.config` property
    urlConfig: [
        {
            id: "hidepassed",
            label: "Hide passed tests",
            tooltip: "Only show tests and assertions that fail. Stored as query-strings."
        },
        {
            id: "noglobals",
            label: "Check for Globals",
            tooltip: "Enabling this will test if any test introduces new properties on the " +
                "`window` object. Stored as query-strings."
        },
        {
            id: "notrycatch",
            label: "No try-catch",
            tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
                "exceptions in IE reasonable. Stored as query-strings."
        }
    ],

    // Set of all modules.
    modules: [],

    // The first unnamed module
    currentModule: {
        name: "",
        tests: []
    },

    callbacks: {}
};

// Push a loose unnamed module to the modules collection
config.modules.push( config.currentModule );

// Initialize more QUnit.config and QUnit.urlParams
(function() {
    var i, current,
        location = window.location || { search: "", protocol: "file:" },
        params = location.search.slice( 1 ).split( "&" ),
        length = params.length,
        urlParams = {};

    if ( params[ 0 ] ) {
        for ( i = 0; i < length; i++ ) {
            current = params[ i ].split( "=" );
            current[ 0 ] = decodeURIComponent( current[ 0 ] );

            // allow just a key to turn on a flag, e.g., test.html?noglobals
            current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
            if ( urlParams[ current[ 0 ] ] ) {
                urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
            } else {
                urlParams[ current[ 0 ] ] = current[ 1 ];
            }
        }
    }

    QUnit.urlParams = urlParams;

    // String search anywhere in moduleName+testName
    config.filter = urlParams.filter;

    config.testId = [];
    if ( urlParams.testId ) {

        // Ensure that urlParams.testId is an array
        urlParams.testId = [].concat( urlParams.testId );
        for ( i = 0; i < urlParams.testId.length; i++ ) {
            config.testId.push( urlParams.testId[ i ] );
        }
    }

    // Figure out if we're running the tests from a server or not
    QUnit.isLocal = location.protocol === "file:";
}());

// Root QUnit object.
// `QUnit` initialized at top of scope
extend( QUnit, {

    // call on start of module test to prepend name to all tests
    module: function( name, testEnvironment ) {
        var currentModule = {
            name: name,
            testEnvironment: testEnvironment,
            tests: []
        };

        // DEPRECATED: handles setup/teardown functions,
        // beforeEach and afterEach should be used instead
        if ( testEnvironment && testEnvironment.setup ) {
            testEnvironment.beforeEach = testEnvironment.setup;
            delete testEnvironment.setup;
        }
        if ( testEnvironment && testEnvironment.teardown ) {
            testEnvironment.afterEach = testEnvironment.teardown;
            delete testEnvironment.teardown;
        }

        config.modules.push( currentModule );
        config.currentModule = currentModule;
    },

    // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
    asyncTest: function( testName, expected, callback ) {
        if ( arguments.length === 2 ) {
            callback = expected;
            expected = null;
        }

        QUnit.test( testName, expected, callback, true );
    },

    test: function( testName, expected, callback, async ) {
        var test;

        if ( arguments.length === 2 ) {
            callback = expected;
            expected = null;
        }

        test = new Test({
            testName: testName,
            expected: expected,
            async: async,
            callback: callback
        });

        test.queue();
    },

    skip: function( testName ) {
        var test = new Test({
            testName: testName,
            skip: true
        });

        test.queue();
    },

    // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
    // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
    start: function( count ) {
        var globalStartAlreadyCalled = globalStartCalled;

        if ( !config.current ) {
            globalStartCalled = true;

            if ( runStarted ) {
                throw new Error( "Called start() outside of a test context while already started" );
            } else if ( globalStartAlreadyCalled || count > 1 ) {
                throw new Error( "Called start() outside of a test context too many times" );
            } else if ( config.autostart ) {
                throw new Error( "Called start() outside of a test context when " +
                    "QUnit.config.autostart was true" );
            } else if ( !config.pageLoaded ) {

                // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
                config.autostart = true;
                return;
            }
        } else {

            // If a test is running, adjust its semaphore
            config.current.semaphore -= count || 1;

            // Don't start until equal number of stop-calls
            if ( config.current.semaphore > 0 ) {
                return;
            }

            // throw an Error if start is called more often than stop
            if ( config.current.semaphore < 0 ) {
                config.current.semaphore = 0;

                QUnit.pushFailure(
                    "Called start() while already started (test's semaphore was 0 already)",
                    sourceFromStacktrace( 2 )
                );
                return;
            }
        }

        resumeProcessing();
    },

    // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
    stop: function( count ) {

        // If there isn't a test running, don't allow QUnit.stop() to be called
        if ( !config.current ) {
            throw new Error( "Called stop() outside of a test context" );
        }

        // If a test is running, adjust its semaphore
        config.current.semaphore += count || 1;

        pauseProcessing();
    },

    config: config,

    // Safe object type checking
    is: function( type, obj ) {
        return QUnit.objectType( obj ) === type;
    },

    objectType: function( obj ) {
        if ( typeof obj === "undefined" ) {
            return "undefined";
        }

        // Consider: typeof null === object
        if ( obj === null ) {
            return "null";
        }

        var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
            type = match && match[ 1 ] || "";

        switch ( type ) {
            case "Number":
                if ( isNaN( obj ) ) {
                    return "nan";
                }
                return "number";
            case "String":
            case "Boolean":
            case "Array":
            case "Date":
            case "RegExp":
            case "Function":
                return type.toLowerCase();
        }
        if ( typeof obj === "object" ) {
            return "object";
        }
        return undefined;
    },

    url: function( params ) {
        params = extend( extend( {}, QUnit.urlParams ), params );
        var key,
            querystring = "?";

        for ( key in params ) {
            if ( hasOwn.call( params, key ) ) {
                querystring += encodeURIComponent( key );
                if ( params[ key ] !== true ) {
                    querystring += "=" + encodeURIComponent( params[ key ] );
                }
                querystring += "&";
            }
        }
        return location.protocol + "//" + location.host +
            location.pathname + querystring.slice( 0, -1 );
    },

    extend: extend,

    load: function() {
        config.pageLoaded = true;

        // Initialize the configuration options
        extend( config, {
            stats: { all: 0, bad: 0 },
            moduleStats: { all: 0, bad: 0 },
            started: 0,
            updateRate: 1000,
            autostart: true,
            filter: ""
        }, true );

        config.blocking = false;

        if ( config.autostart ) {
            resumeProcessing();
        }
    }
});

// Register logging callbacks
(function() {
    var i, l, key,
        callbacks = [ "begin", "done", "log", "testStart", "testDone",
            "moduleStart", "moduleDone" ];

    function registerLoggingCallback( key ) {
        var loggingCallback = function( callback ) {
            if ( QUnit.objectType( callback ) !== "function" ) {
                throw new Error(
                    "QUnit logging methods require a callback function as their first parameters."
                );
            }

            config.callbacks[ key ].push( callback );
        };

        // DEPRECATED: This will be removed on QUnit 2.0.0+
        // Stores the registered functions allowing restoring
        // at verifyLoggingCallbacks() if modified
        loggingCallbacks[ key ] = loggingCallback;

        return loggingCallback;
    }

    for ( i = 0, l = callbacks.length; i < l; i++ ) {
        key = callbacks[ i ];

        // Initialize key collection of logging callback
        if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
            config.callbacks[ key ] = [];
        }

        QUnit[ key ] = registerLoggingCallback( key );
    }
})();

// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
onErrorFnPrev = window.onerror;

// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window.onerror = function( error, filePath, linerNr ) {
    var ret = false;
    if ( onErrorFnPrev ) {
        ret = onErrorFnPrev( error, filePath, linerNr );
    }

    // Treat return value as window.onerror itself does,
    // Only do our handling if not suppressed.
    if ( ret !== true ) {
        if ( QUnit.config.current ) {
            if ( QUnit.config.current.ignoreGlobalErrors ) {
                return true;
            }
            QUnit.pushFailure( error, filePath + ":" + linerNr );
        } else {
            QUnit.test( "global failure", extend(function() {
                QUnit.pushFailure( error, filePath + ":" + linerNr );
            }, { validTest: true } ) );
        }
        return false;
    }

    return ret;
};

function done() {
    var runtime, passed;

    config.autorun = true;

    // Log the last module results
    if ( config.previousModule ) {
        runLoggingCallbacks( "moduleDone", {
            name: config.previousModule.name,
            tests: config.previousModule.tests,
            failed: config.moduleStats.bad,
            passed: config.moduleStats.all - config.moduleStats.bad,
            total: config.moduleStats.all,
            runtime: now() - config.moduleStats.started
        });
    }
    delete config.previousModule;

    runtime = now() - config.started;
    passed = config.stats.all - config.stats.bad;

    runLoggingCallbacks( "done", {
        failed: config.stats.bad,
        passed: passed,
        total: config.stats.all,
        runtime: runtime
    });
}

// Doesn't support IE6 to IE9
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
    offset = offset === undefined ? 4 : offset;

    var stack, include, i;

    if ( e.stacktrace ) {

        // Opera 12.x
        return e.stacktrace.split( "\n" )[ offset + 3 ];
    } else if ( e.stack ) {

        // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
        stack = e.stack.split( "\n" );
        if ( /^error$/i.test( stack[ 0 ] ) ) {
            stack.shift();
        }
        if ( fileName ) {
            include = [];
            for ( i = offset; i < stack.length; i++ ) {
                if ( stack[ i ].indexOf( fileName ) !== -1 ) {
                    break;
                }
                include.push( stack[ i ] );
            }
            if ( include.length ) {
                return include.join( "\n" );
            }
        }
        return stack[ offset ];
    } else if ( e.sourceURL ) {

        // Safari < 6
        // exclude useless self-reference for generated Error objects
        if ( /qunit.js$/.test( e.sourceURL ) ) {
            return;
        }

        // for actual exceptions, this is useful
        return e.sourceURL + ":" + e.line;
    }
}

function sourceFromStacktrace( offset ) {
    var e = new Error();
    if ( !e.stack ) {
        try {
            throw e;
        } catch ( err ) {
            // This should already be true in most browsers
            e = err;
        }
    }
    return extractStacktrace( e, offset );
}

function synchronize( callback, last ) {
    if ( QUnit.objectType( callback ) === "array" ) {
        while ( callback.length ) {
            synchronize( callback.shift() );
        }
        return;
    }
    config.queue.push( callback );

    if ( config.autorun && !config.blocking ) {
        process( last );
    }
}

function process( last ) {
    function next() {
        process( last );
    }
    var start = now();
    config.depth = config.depth ? config.depth + 1 : 1;

    while ( config.queue.length && !config.blocking ) {
        if ( !defined.setTimeout || config.updateRate <= 0 ||
                ( ( now() - start ) < config.updateRate ) ) {
            if ( config.current ) {

                // Reset async tracking for each phase of the Test lifecycle
                config.current.usedAsync = false;
            }
            config.queue.shift()();
        } else {
            setTimeout( next, 13 );
            break;
        }
    }
    config.depth--;
    if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
        done();
    }
}

function begin() {
    var i, l,
        modulesLog = [];

    // If the test run hasn't officially begun yet
    if ( !config.started ) {

        // Record the time of the test run's beginning
        config.started = now();

        verifyLoggingCallbacks();

        // Delete the loose unnamed module if unused.
        if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
            config.modules.shift();
        }

        // Avoid unnecessary information by not logging modules' test environments
        for ( i = 0, l = config.modules.length; i < l; i++ ) {
            modulesLog.push({
                name: config.modules[ i ].name,
                tests: config.modules[ i ].tests
            });
        }

        // The test run is officially beginning now
        runLoggingCallbacks( "begin", {
            totalTests: Test.count,
            modules: modulesLog
        });
    }

    config.blocking = false;
    process( true );
}

function resumeProcessing() {
    runStarted = true;

    // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
    if ( defined.setTimeout ) {
        setTimeout(function() {
            if ( config.current && config.current.semaphore > 0 ) {
                return;
            }
            if ( config.timeout ) {
                clearTimeout( config.timeout );
            }

            begin();
        }, 13 );
    } else {
        begin();
    }
}

function pauseProcessing() {
    config.blocking = true;

    if ( config.testTimeout && defined.setTimeout ) {
        clearTimeout( config.timeout );
        config.timeout = setTimeout(function() {
            if ( config.current ) {
                config.current.semaphore = 0;
                QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
            } else {
                throw new Error( "Test timed out" );
            }
            resumeProcessing();
        }, config.testTimeout );
    }
}

function saveGlobal() {
    config.pollution = [];

    if ( config.noglobals ) {
        for ( var key in window ) {
            if ( hasOwn.call( window, key ) ) {
                // in Opera sometimes DOM element ids show up here, ignore them
                if ( /^qunit-test-output/.test( key ) ) {
                    continue;
                }
                config.pollution.push( key );
            }
        }
    }
}

function checkPollution() {
    var newGlobals,
        deletedGlobals,
        old = config.pollution;

    saveGlobal();

    newGlobals = diff( config.pollution, old );
    if ( newGlobals.length > 0 ) {
        QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
    }

    deletedGlobals = diff( old, config.pollution );
    if ( deletedGlobals.length > 0 ) {
        QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
    }
}

// returns a new Array with the elements that are in a but not in b
function diff( a, b ) {
    var i, j,
        result = a.slice();

    for ( i = 0; i < result.length; i++ ) {
        for ( j = 0; j < b.length; j++ ) {
            if ( result[ i ] === b[ j ] ) {
                result.splice( i, 1 );
                i--;
                break;
            }
        }
    }
    return result;
}

function extend( a, b, undefOnly ) {
    for ( var prop in b ) {
        if ( hasOwn.call( b, prop ) ) {

            // Avoid "Member not found" error in IE8 caused by messing with window.constructor
            if ( !( prop === "constructor" && a === window ) ) {
                if ( b[ prop ] === undefined ) {
                    delete a[ prop ];
                } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
                    a[ prop ] = b[ prop ];
                }
            }
        }
    }

    return a;
}

function runLoggingCallbacks( key, args ) {
    var i, l, callbacks;

    callbacks = config.callbacks[ key ];
    for ( i = 0, l = callbacks.length; i < l; i++ ) {
        callbacks[ i ]( args );
    }
}

// DEPRECATED: This will be removed on 2.0.0+
// This function verifies if the loggingCallbacks were modified by the user
// If so, it will restore it, assign the given callback and print a console warning
function verifyLoggingCallbacks() {
    var loggingCallback, userCallback;

    for ( loggingCallback in loggingCallbacks ) {
        if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {

            userCallback = QUnit[ loggingCallback ];

            // Restore the callback function
            QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];

            // Assign the deprecated given callback
            QUnit[ loggingCallback ]( userCallback );

            if ( window.console && window.console.warn ) {
                window.console.warn(
                    "QUnit." + loggingCallback + " was replaced with a new value.\n" +
                    "Please, check out the documentation on how to apply logging callbacks.\n" +
                    "Reference: http://api.qunitjs.com/category/callbacks/"
                );
            }
        }
    }
}

// from jquery.js
function inArray( elem, array ) {
    if ( array.indexOf ) {
        return array.indexOf( elem );
    }

    for ( var i = 0, length = array.length; i < length; i++ ) {
        if ( array[ i ] === elem ) {
            return i;
        }
    }

    return -1;
}

function Test( settings ) {
    var i, l;

    ++Test.count;

    extend( this, settings );
    this.assertions = [];
    this.semaphore = 0;
    this.usedAsync = false;
    this.module = config.currentModule;
    this.stack = sourceFromStacktrace( 3 );

    // Register unique strings
    for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
        if ( this.module.tests[ i ].name === this.testName ) {
            this.testName += " ";
        }
    }

    this.testId = generateHash( this.module.name, this.testName );

    this.module.tests.push({
        name: this.testName,
        testId: this.testId
    });

    if ( settings.skip ) {

        // Skipped tests will fully ignore any sent callback
        this.callback = function() {};
        this.async = false;
        this.expected = 0;
    } else {
        this.assert = new Assert( this );
    }
}

Test.count = 0;

Test.prototype = {
    before: function() {
        if (

            // Emit moduleStart when we're switching from one module to another
            this.module !== config.previousModule ||

                // They could be equal (both undefined) but if the previousModule property doesn't
                // yet exist it means this is the first test in a suite that isn't wrapped in a
                // module, in which case we'll just emit a moduleStart event for 'undefined'.
                // Without this, reporters can get testStart before moduleStart  which is a problem.
                !hasOwn.call( config, "previousModule" )
        ) {
            if ( hasOwn.call( config, "previousModule" ) ) {
                runLoggingCallbacks( "moduleDone", {
                    name: config.previousModule.name,
                    tests: config.previousModule.tests,
                    failed: config.moduleStats.bad,
                    passed: config.moduleStats.all - config.moduleStats.bad,
                    total: config.moduleStats.all,
                    runtime: now() - config.moduleStats.started
                });
            }
            config.previousModule = this.module;
            config.moduleStats = { all: 0, bad: 0, started: now() };
            runLoggingCallbacks( "moduleStart", {
                name: this.module.name,
                tests: this.module.tests
            });
        }

        config.current = this;

        this.testEnvironment = extend( {}, this.module.testEnvironment );
        delete this.testEnvironment.beforeEach;
        delete this.testEnvironment.afterEach;

        this.started = now();
        runLoggingCallbacks( "testStart", {
            name: this.testName,
            module: this.module.name,
            testId: this.testId
        });

        if ( !config.pollution ) {
            saveGlobal();
        }
    },

    run: function() {
        var promise;

        config.current = this;

        if ( this.async ) {
            QUnit.stop();
        }

        this.callbackStarted = now();

        if ( config.notrycatch ) {
            promise = this.callback.call( this.testEnvironment, this.assert );
            this.resolvePromise( promise );
            return;
        }

        try {
            promise = this.callback.call( this.testEnvironment, this.assert );
            this.resolvePromise( promise );
        } catch ( e ) {
            this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
                this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );

            // else next test will carry the responsibility
            saveGlobal();

            // Restart the tests if they're blocking
            if ( config.blocking ) {
                QUnit.start();
            }
        }
    },

    after: function() {
        checkPollution();
    },

    queueHook: function( hook, hookName ) {
        var promise,
            test = this;
        return function runHook() {
            config.current = test;
            if ( config.notrycatch ) {
                promise = hook.call( test.testEnvironment, test.assert );
                test.resolvePromise( promise, hookName );
                return;
            }
            try {
                promise = hook.call( test.testEnvironment, test.assert );
                test.resolvePromise( promise, hookName );
            } catch ( error ) {
                test.pushFailure( hookName + " failed on " + test.testName + ": " +
                    ( error.message || error ), extractStacktrace( error, 0 ) );
            }
        };
    },

    // Currently only used for module level hooks, can be used to add global level ones
    hooks: function( handler ) {
        var hooks = [];

        // Hooks are ignored on skipped tests
        if ( this.skip ) {
            return hooks;
        }

        if ( this.module.testEnvironment &&
                QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
            hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
        }

        return hooks;
    },

    finish: function() {
        config.current = this;
        if ( config.requireExpects && this.expected === null ) {
            this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
                "not called.", this.stack );
        } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
            this.pushFailure( "Expected " + this.expected + " assertions, but " +
                this.assertions.length + " were run", this.stack );
        } else if ( this.expected === null && !this.assertions.length ) {
            this.pushFailure( "Expected at least one assertion, but none were run - call " +
                "expect(0) to accept zero assertions.", this.stack );
        }

        var i,
            bad = 0;

        this.runtime = now() - this.started;
        config.stats.all += this.assertions.length;
        config.moduleStats.all += this.assertions.length;

        for ( i = 0; i < this.assertions.length; i++ ) {
            if ( !this.assertions[ i ].result ) {
                bad++;
                config.stats.bad++;
                config.moduleStats.bad++;
            }
        }

        runLoggingCallbacks( "testDone", {
            name: this.testName,
            module: this.module.name,
            skipped: !!this.skip,
            failed: bad,
            passed: this.assertions.length - bad,
            total: this.assertions.length,
            runtime: this.runtime,

            // HTML Reporter use
            assertions: this.assertions,
            testId: this.testId,

            // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
            duration: this.runtime
        });

        // QUnit.reset() is deprecated and will be replaced for a new
        // fixture reset function on QUnit 2.0/2.1.
        // It's still called here for backwards compatibility handling
        QUnit.reset();

        config.current = undefined;
    },

    queue: function() {
        var bad,
            test = this;

        if ( !this.valid() ) {
            return;
        }

        function run() {

            // each of these can by async
            synchronize([
                function() {
                    test.before();
                },

                test.hooks( "beforeEach" ),

                function() {
                    test.run();
                },

                test.hooks( "afterEach" ).reverse(),

                function() {
                    test.after();
                },
                function() {
                    test.finish();
                }
            ]);
        }

        // `bad` initialized at top of scope
        // defer when previous test run passed, if storage is available
        bad = QUnit.config.reorder && defined.sessionStorage &&
                +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );

        if ( bad ) {
            run();
        } else {
            synchronize( run, true );
        }
    },

    push: function( result, actual, expected, message ) {
        var source,
            details = {
                module: this.module.name,
                name: this.testName,
                result: result,
                message: message,
                actual: actual,
                expected: expected,
                testId: this.testId,
                runtime: now() - this.started
            };

        if ( !result ) {
            source = sourceFromStacktrace();

            if ( source ) {
                details.source = source;
            }
        }

        runLoggingCallbacks( "log", details );

        this.assertions.push({
            result: !!result,
            message: message
        });
    },

    pushFailure: function( message, source, actual ) {
        if ( !this instanceof Test ) {
            throw new Error( "pushFailure() assertion outside test context, was " +
                sourceFromStacktrace( 2 ) );
        }

        var details = {
                module: this.module.name,
                name: this.testName,
                result: false,
                message: message || "error",
                actual: actual || null,
                testId: this.testId,
                runtime: now() - this.started
            };

        if ( source ) {
            details.source = source;
        }

        runLoggingCallbacks( "log", details );

        this.assertions.push({
            result: false,
            message: message
        });
    },

    resolvePromise: function( promise, phase ) {
        var then, message,
            test = this;
        if ( promise != null ) {
            then = promise.then;
            if ( QUnit.objectType( then ) === "function" ) {
                QUnit.stop();
                then.call(
                    promise,
                    QUnit.start,
                    function( error ) {
                        message = "Promise rejected " +
                            ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
                            " " + test.testName + ": " + ( error.message || error );
                        test.pushFailure( message, extractStacktrace( error, 0 ) );

                        // else next test will carry the responsibility
                        saveGlobal();

                        // Unblock
                        QUnit.start();
                    }
                );
            }
        }
    },

    valid: function() {
        var include,
            filter = config.filter && config.filter.toLowerCase(),
            module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
            fullName = ( this.module.name + ": " + this.testName ).toLowerCase();

        // Internally-generated tests are always valid
        if ( this.callback && this.callback.validTest ) {
            return true;
        }

        if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
            return false;
        }

        if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
            return false;
        }

        if ( !filter ) {
            return true;
        }

        include = filter.charAt( 0 ) !== "!";
        if ( !include ) {
            filter = filter.slice( 1 );
        }

        // If the filter matches, we need to honour include
        if ( fullName.indexOf( filter ) !== -1 ) {
            return include;
        }

        // Otherwise, do the opposite
        return !include;
    }

};

// Resets the test setup. Useful for tests that modify the DOM.
/*
DEPRECATED: Use multiple tests instead of resetting inside a test.
Use testStart or testDone for custom cleanup.
This method will throw an error in 2.0, and will be removed in 2.1
*/
QUnit.reset = function() {

    // Return on non-browser environments
    // This is necessary to not break on node tests
    if ( typeof window === "undefined" ) {
        return;
    }

    var fixture = defined.document && document.getElementById &&
            document.getElementById( "qunit-fixture" );

    if ( fixture ) {
        fixture.innerHTML = config.fixture;
    }
};

QUnit.pushFailure = function() {
    if ( !QUnit.config.current ) {
        throw new Error( "pushFailure() assertion outside test context, in " +
            sourceFromStacktrace( 2 ) );
    }

    // Gets current test obj
    var currentTest = QUnit.config.current;

    return currentTest.pushFailure.apply( currentTest, arguments );
};

// Based on Java's String.hashCode, a simple but not
// rigorously collision resistant hashing function
function generateHash( module, testName ) {
    var hex,
        i = 0,
        hash = 0,
        str = module + "\x1C" + testName,
        len = str.length;

    for ( ; i < len; i++ ) {
        hash  = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
        hash |= 0;
    }

    // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
    // strictly necessary but increases user understanding that the id is a SHA-like hash
    hex = ( 0x100000000 + hash ).toString( 16 );
    if ( hex.length < 8 ) {
        hex = "0000000" + hex;
    }

    return hex.slice( -8 );
}

function Assert( testContext ) {
    this.test = testContext;
}

// Assert helpers
QUnit.assert = Assert.prototype = {

    // Specify the number of expected assertions to guarantee that failed test
    // (no assertions are run at all) don't slip through.
    expect: function( asserts ) {
        if ( arguments.length === 1 ) {
            this.test.expected = asserts;
        } else {
            return this.test.expected;
        }
    },

    // Increment this Test's semaphore counter, then return a single-use function that
    // decrements that counter a maximum of once.
    async: function() {
        var test = this.test,
            popped = false;

        test.semaphore += 1;
        test.usedAsync = true;
        pauseProcessing();

        return function done() {
            if ( !popped ) {
                test.semaphore -= 1;
                popped = true;
                resumeProcessing();
            } else {
                test.pushFailure( "Called the callback returned from `assert.async` more than once",
                    sourceFromStacktrace( 2 ) );
            }
        };
    },

    // Exports test.push() to the user API
    push: function( /* result, actual, expected, message */ ) {
        var assert = this,
            currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;

        // Backwards compatibility fix.
        // Allows the direct use of global exported assertions and QUnit.assert.*
        // Although, it's use is not recommended as it can leak assertions
        // to other tests from async tests, because we only get a reference to the current test,
        // not exactly the test where assertion were intended to be called.
        if ( !currentTest ) {
            throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
        }

        if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
            currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
                sourceFromStacktrace( 2 ) );

            // Allow this assertion to continue running anyway...
        }

        if ( !( assert instanceof Assert ) ) {
            assert = currentTest.assert;
        }
        return assert.test.push.apply( assert.test, arguments );
    },

    /**
     * Asserts rough true-ish result.
     * @name ok
     * @function
     * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
     */
    ok: function( result, message ) {
        message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
            QUnit.dump.parse( result ) );
        this.push( !!result, result, true, message );
    },

    /**
     * Assert that the first two arguments are equal, with an optional message.
     * Prints out both actual and expected values.
     * @name equal
     * @function
     * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
     */
    equal: function( actual, expected, message ) {
        /*jshint eqeqeq:false */
        this.push( expected == actual, actual, expected, message );
    },

    /**
     * @name notEqual
     * @function
     */
    notEqual: function( actual, expected, message ) {
        /*jshint eqeqeq:false */
        this.push( expected != actual, actual, expected, message );
    },

    /**
     * @name propEqual
     * @function
     */
    propEqual: function( actual, expected, message ) {
        actual = objectValues( actual );
        expected = objectValues( expected );
        this.push( QUnit.equiv( actual, expected ), actual, expected, message );
    },

    /**
     * @name notPropEqual
     * @function
     */
    notPropEqual: function( actual, expected, message ) {
        actual = objectValues( actual );
        expected = objectValues( expected );
        this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
    },

    /**
     * @name deepEqual
     * @function
     */
    deepEqual: function( actual, expected, message ) {
        this.push( QUnit.equiv( actual, expected ), actual, expected, message );
    },

    /**
     * @name notDeepEqual
     * @function
     */
    notDeepEqual: function( actual, expected, message ) {
        this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
    },

    /**
     * @name strictEqual
     * @function
     */
    strictEqual: function( actual, expected, message ) {
        this.push( expected === actual, actual, expected, message );
    },

    /**
     * @name notStrictEqual
     * @function
     */
    notStrictEqual: function( actual, expected, message ) {
        this.push( expected !== actual, actual, expected, message );
    },

    "throws": function( block, expected, message ) {
        var actual, expectedType,
            expectedOutput = expected,
            ok = false;

        // 'expected' is optional unless doing string comparison
        if ( message == null && typeof expected === "string" ) {
            message = expected;
            expected = null;
        }

        this.test.ignoreGlobalErrors = true;
        try {
            block.call( this.test.testEnvironment );
        } catch (e) {
            actual = e;
        }
        this.test.ignoreGlobalErrors = false;

        if ( actual ) {
            expectedType = QUnit.objectType( expected );

            // we don't want to validate thrown error
            if ( !expected ) {
                ok = true;
                expectedOutput = null;

            // expected is a regexp
            } else if ( expectedType === "regexp" ) {
                ok = expected.test( errorString( actual ) );

            // expected is a string
            } else if ( expectedType === "string" ) {
                ok = expected === errorString( actual );

            // expected is a constructor, maybe an Error constructor
            } else if ( expectedType === "function" && actual instanceof expected ) {
                ok = true;

            // expected is an Error object
            } else if ( expectedType === "object" ) {
                ok = actual instanceof expected.constructor &&
                    actual.name === expected.name &&
                    actual.message === expected.message;

            // expected is a validation function which returns true if validation passed
            } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
                expectedOutput = null;
                ok = true;
            }

            this.push( ok, actual, expectedOutput, message );
        } else {
            this.test.pushFailure( message, null, "No exception was thrown." );
        }
    }
};

// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
// Known to us are: Closure Compiler, Narwhal
(function() {
    /*jshint sub:true */
    Assert.prototype.raises = Assert.prototype[ "throws" ];
}());

// Test for equality any JavaScript type.
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = (function() {

    // Call the o related callback with the given arguments.
    function bindCallbacks( o, callbacks, args ) {
        var prop = QUnit.objectType( o );
        if ( prop ) {
            if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
                return callbacks[ prop ].apply( callbacks, args );
            } else {
                return callbacks[ prop ]; // or undefined
            }
        }
    }

    // the real equiv function
    var innerEquiv,

        // stack to decide between skip/abort functions
        callers = [],

        // stack to avoiding loops from circular referencing
        parents = [],
        parentsB = [],

        getProto = Object.getPrototypeOf || function( obj ) {
            /* jshint camelcase: false, proto: true */
            return obj.__proto__;
        },
        callbacks = (function() {

            // for string, boolean, number and null
            function useStrictEquality( b, a ) {

                /*jshint eqeqeq:false */
                if ( b instanceof a.constructor || a instanceof b.constructor ) {

                    // to catch short annotation VS 'new' annotation of a
                    // declaration
                    // e.g. var i = 1;
                    // var j = new Number(1);
                    return a == b;
                } else {
                    return a === b;
                }
            }

            return {
                "string": useStrictEquality,
                "boolean": useStrictEquality,
                "number": useStrictEquality,
                "null": useStrictEquality,
                "undefined": useStrictEquality,

                "nan": function( b ) {
                    return isNaN( b );
                },

                "date": function( b, a ) {
                    return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
                },

                "regexp": function( b, a ) {
                    return QUnit.objectType( b ) === "regexp" &&

                        // the regex itself
                        a.source === b.source &&

                        // and its modifiers
                        a.global === b.global &&

                        // (gmi) ...
                        a.ignoreCase === b.ignoreCase &&
                        a.multiline === b.multiline &&
                        a.sticky === b.sticky;
                },

                // - skip when the property is a method of an instance (OOP)
                // - abort otherwise,
                // initial === would have catch identical references anyway
                "function": function() {
                    var caller = callers[ callers.length - 1 ];
                    return caller !== Object && typeof caller !== "undefined";
                },

                "array": function( b, a ) {
                    var i, j, len, loop, aCircular, bCircular;

                    // b could be an object literal here
                    if ( QUnit.objectType( b ) !== "array" ) {
                        return false;
                    }

                    len = a.length;
                    if ( len !== b.length ) {
                        // safe and faster
                        return false;
                    }

                    // track reference to avoid circular references
                    parents.push( a );
                    parentsB.push( b );
                    for ( i = 0; i < len; i++ ) {
                        loop = false;
                        for ( j = 0; j < parents.length; j++ ) {
                            aCircular = parents[ j ] === a[ i ];
                            bCircular = parentsB[ j ] === b[ i ];
                            if ( aCircular || bCircular ) {
                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
                                    loop = true;
                                } else {
                                    parents.pop();
                                    parentsB.pop();
                                    return false;
                                }
                            }
                        }
                        if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
                            parents.pop();
                            parentsB.pop();
                            return false;
                        }
                    }
                    parents.pop();
                    parentsB.pop();
                    return true;
                },

                "object": function( b, a ) {

                    /*jshint forin:false */
                    var i, j, loop, aCircular, bCircular,
                        // Default to true
                        eq = true,
                        aProperties = [],
                        bProperties = [];

                    // comparing constructors is more strict than using
                    // instanceof
                    if ( a.constructor !== b.constructor ) {

                        // Allow objects with no prototype to be equivalent to
                        // objects with Object as their constructor.
                        if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
                            ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
                            return false;
                        }
                    }

                    // stack constructor before traversing properties
                    callers.push( a.constructor );

                    // track reference to avoid circular references
                    parents.push( a );
                    parentsB.push( b );

                    // be strict: don't ensure hasOwnProperty and go deep
                    for ( i in a ) {
                        loop = false;
                        for ( j = 0; j < parents.length; j++ ) {
                            aCircular = parents[ j ] === a[ i ];
                            bCircular = parentsB[ j ] === b[ i ];
                            if ( aCircular || bCircular ) {
                                if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
                                    loop = true;
                                } else {
                                    eq = false;
                                    break;
                                }
                            }
                        }
                        aProperties.push( i );
                        if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
                            eq = false;
                            break;
                        }
                    }

                    parents.pop();
                    parentsB.pop();
                    callers.pop(); // unstack, we are done

                    for ( i in b ) {
                        bProperties.push( i ); // collect b's properties
                    }

                    // Ensures identical properties name
                    return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
                }
            };
        }());

    innerEquiv = function() { // can take multiple arguments
        var args = [].slice.apply( arguments );
        if ( args.length < 2 ) {
            return true; // end transition
        }

        return ( (function( a, b ) {
            if ( a === b ) {
                return true; // catch the most you can
            } else if ( a === null || b === null || typeof a === "undefined" ||
                    typeof b === "undefined" ||
                    QUnit.objectType( a ) !== QUnit.objectType( b ) ) {

                // don't lose time with error prone cases
                return false;
            } else {
                return bindCallbacks( a, callbacks, [ b, a ] );
            }

            // apply transition with (1..n) arguments
        }( args[ 0 ], args[ 1 ] ) ) &&
            innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
    };

    return innerEquiv;
}());

// Based on jsDump by Ariel Flesler
// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
QUnit.dump = (function() {
    function quote( str ) {
        return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
    }
    function literal( o ) {
        return o + "";
    }
    function join( pre, arr, post ) {
        var s = dump.separator(),
            base = dump.indent(),
            inner = dump.indent( 1 );
        if ( arr.join ) {
            arr = arr.join( "," + s + inner );
        }
        if ( !arr ) {
            return pre + post;
        }
        return [ pre, inner + arr, base + post ].join( s );
    }
    function array( arr, stack ) {
        var i = arr.length,
            ret = new Array( i );

        if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
            return "[object Array]";
        }

        this.up();
        while ( i-- ) {
            ret[ i ] = this.parse( arr[ i ], undefined, stack );
        }
        this.down();
        return join( "[", ret, "]" );
    }

    var reName = /^function (\w+)/,
        dump = {

            // objType is used mostly internally, you can fix a (custom) type in advance
            parse: function( obj, objType, stack ) {
                stack = stack || [];
                var res, parser, parserType,
                    inStack = inArray( obj, stack );

                if ( inStack !== -1 ) {
                    return "recursion(" + ( inStack - stack.length ) + ")";
                }

                objType = objType || this.typeOf( obj  );
                parser = this.parsers[ objType ];
                parserType = typeof parser;

                if ( parserType === "function" ) {
                    stack.push( obj );
                    res = parser.call( this, obj, stack );
                    stack.pop();
                    return res;
                }
                return ( parserType === "string" ) ? parser : this.parsers.error;
            },
            typeOf: function( obj ) {
                var type;
                if ( obj === null ) {
                    type = "null";
                } else if ( typeof obj === "undefined" ) {
                    type = "undefined";
                } else if ( QUnit.is( "regexp", obj ) ) {
                    type = "regexp";
                } else if ( QUnit.is( "date", obj ) ) {
                    type = "date";
                } else if ( QUnit.is( "function", obj ) ) {
                    type = "function";
                } else if ( obj.setInterval !== undefined &&
                        obj.document !== undefined &&
                        obj.nodeType === undefined ) {
                    type = "window";
                } else if ( obj.nodeType === 9 ) {
                    type = "document";
                } else if ( obj.nodeType ) {
                    type = "node";
                } else if (

                    // native arrays
                    toString.call( obj ) === "[object Array]" ||

                    // NodeList objects
                    ( typeof obj.length === "number" && obj.item !== undefined &&
                    ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
                    obj[ 0 ] === undefined ) ) )
                ) {
                    type = "array";
                } else if ( obj.constructor === Error.prototype.constructor ) {
                    type = "error";
                } else {
                    type = typeof obj;
                }
                return type;
            },
            separator: function() {
                return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
            },
            // extra can be a number, shortcut for increasing-calling-decreasing
            indent: function( extra ) {
                if ( !this.multiline ) {
                    return "";
                }
                var chr = this.indentChar;
                if ( this.HTML ) {
                    chr = chr.replace( /\t/g, "   " ).replace( / /g, "&#160;" );
                }
                return new Array( this.depth + ( extra || 0 ) ).join( chr );
            },
            up: function( a ) {
                this.depth += a || 1;
            },
            down: function( a ) {
                this.depth -= a || 1;
            },
            setParser: function( name, parser ) {
                this.parsers[ name ] = parser;
            },
            // The next 3 are exposed so you can use them
            quote: quote,
            literal: literal,
            join: join,
            //
            depth: 1,
            maxDepth: 5,

            // This is the list of parsers, to modify them, use dump.setParser
            parsers: {
                window: "[Window]",
                document: "[Document]",
                error: function( error ) {
                    return "Error(\"" + error.message + "\")";
                },
                unknown: "[Unknown]",
                "null": "null",
                "undefined": "undefined",
                "function": function( fn ) {
                    var ret = "function",

                        // functions never have name in IE
                        name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];

                    if ( name ) {
                        ret += " " + name;
                    }
                    ret += "( ";

                    ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
                    return join( ret, dump.parse( fn, "functionCode" ), "}" );
                },
                array: array,
                nodelist: array,
                "arguments": array,
                object: function( map, stack ) {
                    var keys, key, val, i, nonEnumerableProperties,
                        ret = [];

                    if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
                        return "[object Object]";
                    }

                    dump.up();
                    keys = [];
                    for ( key in map ) {
                        keys.push( key );
                    }

                    // Some properties are not always enumerable on Error objects.
                    nonEnumerableProperties = [ "message", "name" ];
                    for ( i in nonEnumerableProperties ) {
                        key = nonEnumerableProperties[ i ];
                        if ( key in map && !( key in keys ) ) {
                            keys.push( key );
                        }
                    }
                    keys.sort();
                    for ( i = 0; i < keys.length; i++ ) {
                        key = keys[ i ];
                        val = map[ key ];
                        ret.push( dump.parse( key, "key" ) + ": " +
                            dump.parse( val, undefined, stack ) );
                    }
                    dump.down();
                    return join( "{", ret, "}" );
                },
                node: function( node ) {
                    var len, i, val,
                        open = dump.HTML ? "&lt;" : "<",
                        close = dump.HTML ? "&gt;" : ">",
                        tag = node.nodeName.toLowerCase(),
                        ret = open + tag,
                        attrs = node.attributes;

                    if ( attrs ) {
                        for ( i = 0, len = attrs.length; i < len; i++ ) {
                            val = attrs[ i ].nodeValue;

                            // IE6 includes all attributes in .attributes, even ones not explicitly
                            // set. Those have values like undefined, null, 0, false, "" or
                            // "inherit".
                            if ( val && val !== "inherit" ) {
                                ret += " " + attrs[ i ].nodeName + "=" +
                                    dump.parse( val, "attribute" );
                            }
                        }
                    }
                    ret += close;

                    // Show content of TextNode or CDATASection
                    if ( node.nodeType === 3 || node.nodeType === 4 ) {
                        ret += node.nodeValue;
                    }

                    return ret + open + "/" + tag + close;
                },

                // function calls it internally, it's the arguments part of the function
                functionArgs: function( fn ) {
                    var args,
                        l = fn.length;

                    if ( !l ) {
                        return "";
                    }

                    args = new Array( l );
                    while ( l-- ) {

                        // 97 is 'a'
                        args[ l ] = String.fromCharCode( 97 + l );
                    }
                    return " " + args.join( ", " ) + " ";
                },
                // object calls it internally, the key part of an item in a map
                key: quote,
                // function calls it internally, it's the content of the function
                functionCode: "[code]",
                // node calls it internally, it's an html attribute value
                attribute: quote,
                string: quote,
                date: quote,
                regexp: literal,
                number: literal,
                "boolean": literal
            },
            // if true, entities are escaped ( <, >, \t, space and \n )
            HTML: false,
            // indentation unit
            indentChar: "  ",
            // if true, items in a collection, are separated by a \n, else just a space.
            multiline: true
        };

    return dump;
}());

// back compat
QUnit.jsDump = QUnit.dump;

// For browser, export only select globals
if ( typeof window !== "undefined" ) {

    // Deprecated
    // Extend assert methods to QUnit and Global scope through Backwards compatibility
    (function() {
        var i,
            assertions = Assert.prototype;

        function applyCurrent( current ) {
            return function() {
                var assert = new Assert( QUnit.config.current );
                current.apply( assert, arguments );
            };
        }

        for ( i in assertions ) {
            QUnit[ i ] = applyCurrent( assertions[ i ] );
        }
    })();

    (function() {
        var i, l,
            keys = [
                "test",
                "module",
                "expect",
                "asyncTest",
                "start",
                "stop",
                "ok",
                "equal",
                "notEqual",
                "propEqual",
                "notPropEqual",
                "deepEqual",
                "notDeepEqual",
                "strictEqual",
                "notStrictEqual",
                "throws"
            ];

        for ( i = 0, l = keys.length; i < l; i++ ) {
            window[ keys[ i ] ] = QUnit[ keys[ i ] ];
        }
    })();

    window.QUnit = QUnit;
}

// For nodejs
if ( typeof module !== "undefined" && module.exports ) {
    module.exports = QUnit;
}

// For CommonJS with exports, but without module.exports, like Rhino
if ( typeof exports !== "undefined" ) {
    exports.QUnit = QUnit;
}

// Get a reference to the global object, like window in browsers
}( (function() {
    return this;
})() ));

/*istanbul ignore next */
// jscs:disable maximumLineLength
/*
 * Javascript Diff Algorithm
 *  By John Resig (http://ejohn.org/)
 *  Modified by Chu Alan "sprite"
 *
 * Released under the MIT license.
 *
 * More Info:
 *  http://ejohn.org/projects/javascript-diff-algorithm/
 *
 * Usage: QUnit.diff(expected, actual)
 *
 * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
 */
QUnit.diff = (function() {
    var hasOwn = Object.prototype.hasOwnProperty;

    /*jshint eqeqeq:false, eqnull:true */
    function diff( o, n ) {
        var i,
            ns = {},
            os = {};

        for ( i = 0; i < n.length; i++ ) {
            if ( !hasOwn.call( ns, n[ i ] ) ) {
                ns[ n[ i ] ] = {
                    rows: [],
                    o: null
                };
            }
            ns[ n[ i ] ].rows.push( i );
        }

        for ( i = 0; i < o.length; i++ ) {
            if ( !hasOwn.call( os, o[ i ] ) ) {
                os[ o[ i ] ] = {
                    rows: [],
                    n: null
                };
            }
            os[ o[ i ] ].rows.push( i );
        }

        for ( i in ns ) {
            if ( hasOwn.call( ns, i ) ) {
                if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
                    n[ ns[ i ].rows[ 0 ] ] = {
                        text: n[ ns[ i ].rows[ 0 ] ],
                        row: os[ i ].rows[ 0 ]
                    };
                    o[ os[ i ].rows[ 0 ] ] = {
                        text: o[ os[ i ].rows[ 0 ] ],
                        row: ns[ i ].rows[ 0 ]
                    };
                }
            }
        }

        for ( i = 0; i < n.length - 1; i++ ) {
            if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
                n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {

                n[ i + 1 ] = {
                    text: n[ i + 1 ],
                    row: n[ i ].row + 1
                };
                o[ n[ i ].row + 1 ] = {
                    text: o[ n[ i ].row + 1 ],
                    row: i + 1
                };
            }
        }

        for ( i = n.length - 1; i > 0; i-- ) {
            if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
                n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {

                n[ i - 1 ] = {
                    text: n[ i - 1 ],
                    row: n[ i ].row - 1
                };
                o[ n[ i ].row - 1 ] = {
                    text: o[ n[ i ].row - 1 ],
                    row: i - 1
                };
            }
        }

        return {
            o: o,
            n: n
        };
    }

    return function( o, n ) {
        o = o.replace( /\s+$/, "" );
        n = n.replace( /\s+$/, "" );

        var i, pre,
            str = "",
            out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
            oSpace = o.match( /\s+/g ),
            nSpace = n.match( /\s+/g );

        if ( oSpace == null ) {
            oSpace = [ " " ];
        } else {
            oSpace.push( " " );
        }

        if ( nSpace == null ) {
            nSpace = [ " " ];
        } else {
            nSpace.push( " " );
        }

        if ( out.n.length === 0 ) {
            for ( i = 0; i < out.o.length; i++ ) {
                str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
            }
        } else {
            if ( out.n[ 0 ].text == null ) {
                for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
                    str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
                }
            }

            for ( i = 0; i < out.n.length; i++ ) {
                if ( out.n[ i ].text == null ) {
                    str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
                } else {

                    // `pre` initialized at top of scope
                    pre = "";

                    for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
                        pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
                    }
                    str += " " + out.n[ i ].text + nSpace[ i ] + pre;
                }
            }
        }

        return str;
    };
}());
// jscs:enable

(function() {

// Deprecated QUnit.init - Ref #530
// Re-initialize the configuration options
QUnit.init = function() {
    var tests, banner, result, qunit,
        config = QUnit.config;

    config.stats = { all: 0, bad: 0 };
    config.moduleStats = { all: 0, bad: 0 };
    config.started = 0;
    config.updateRate = 1000;
    config.blocking = false;
    config.autostart = true;
    config.autorun = false;
    config.filter = "";
    config.queue = [];

    // Return on non-browser environments
    // This is necessary to not break on node tests
    if ( typeof window === "undefined" ) {
        return;
    }

    qunit = id( "qunit" );
    if ( qunit ) {
        qunit.innerHTML =
            "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
            "<h2 id='qunit-banner'></h2>" +
            "<div id='qunit-testrunner-toolbar'></div>" +
            "<h2 id='qunit-userAgent'></h2>" +
            "<ol id='qunit-tests'></ol>";
    }

    tests = id( "qunit-tests" );
    banner = id( "qunit-banner" );
    result = id( "qunit-testresult" );

    if ( tests ) {
        tests.innerHTML = "";
    }

    if ( banner ) {
        banner.className = "";
    }

    if ( result ) {
        result.parentNode.removeChild( result );
    }

    if ( tests ) {
        result = document.createElement( "p" );
        result.id = "qunit-testresult";
        result.className = "result";
        tests.parentNode.insertBefore( result, tests );
        result.innerHTML = "Running...<br />&#160;";
    }
};

// Don't load the HTML Reporter on non-Browser environments
if ( typeof window === "undefined" ) {
    return;
}

var config = QUnit.config,
    hasOwn = Object.prototype.hasOwnProperty,
    defined = {
        document: window.document !== undefined,
        sessionStorage: (function() {
            var x = "qunit-test-string";
            try {
                sessionStorage.setItem( x, x );
                sessionStorage.removeItem( x );
                return true;
            } catch ( e ) {
                return false;
            }
        }())
    },
    modulesList = [];

/**
* Escape text for attribute or text content.
*/
function escapeText( s ) {
    if ( !s ) {
        return "";
    }
    s = s + "";

    // Both single quotes and double quotes (for attributes)
    return s.replace( /['"<>&]/g, function( s ) {
        switch ( s ) {
        case "'":
            return "&#039;";
        case "\"":
            return "&quot;";
        case "<":
            return "&lt;";
        case ">":
            return "&gt;";
        case "&":
            return "&amp;";
        }
    });
}

/**
 * @param {HTMLElement} elem
 * @param {string} type
 * @param {Function} fn
 */
function addEvent( elem, type, fn ) {
    if ( elem.addEventListener ) {

        // Standards-based browsers
        elem.addEventListener( type, fn, false );
    } else if ( elem.attachEvent ) {

        // support: IE <9
        elem.attachEvent( "on" + type, fn );
    }
}

/**
 * @param {Array|NodeList} elems
 * @param {string} type
 * @param {Function} fn
 */
function addEvents( elems, type, fn ) {
    var i = elems.length;
    while ( i-- ) {
        addEvent( elems[ i ], type, fn );
    }
}

function hasClass( elem, name ) {
    return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
}

function addClass( elem, name ) {
    if ( !hasClass( elem, name ) ) {
        elem.className += ( elem.className ? " " : "" ) + name;
    }
}

function toggleClass( elem, name ) {
    if ( hasClass( elem, name ) ) {
        removeClass( elem, name );
    } else {
        addClass( elem, name );
    }
}

function removeClass( elem, name ) {
    var set = " " + elem.className + " ";

    // Class name may appear multiple times
    while ( set.indexOf( " " + name + " " ) >= 0 ) {
        set = set.replace( " " + name + " ", " " );
    }

    // trim for prettiness
    elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
}

function id( name ) {
    return defined.document && document.getElementById && document.getElementById( name );
}

function getUrlConfigHtml() {
    var i, j, val,
        escaped, escapedTooltip,
        selection = false,
        len = config.urlConfig.length,
        urlConfigHtml = "";

    for ( i = 0; i < len; i++ ) {
        val = config.urlConfig[ i ];
        if ( typeof val === "string" ) {
            val = {
                id: val,
                label: val
            };
        }

        escaped = escapeText( val.id );
        escapedTooltip = escapeText( val.tooltip );

        config[ val.id ] = QUnit.urlParams[ val.id ];
        if ( !val.value || typeof val.value === "string" ) {
            urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
                "' name='" + escaped + "' type='checkbox'" +
                ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
                ( config[ val.id ] ? " checked='checked'" : "" ) +
                " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
                "' title='" + escapedTooltip + "'>" + val.label + "</label>";
        } else {
            urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
                "' title='" + escapedTooltip + "'>" + val.label +
                ": </label><select id='qunit-urlconfig-" + escaped +
                "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";

            if ( QUnit.is( "array", val.value ) ) {
                for ( j = 0; j < val.value.length; j++ ) {
                    escaped = escapeText( val.value[ j ] );
                    urlConfigHtml += "<option value='" + escaped + "'" +
                        ( config[ val.id ] === val.value[ j ] ?
                            ( selection = true ) && " selected='selected'" : "" ) +
                        ">" + escaped + "</option>";
                }
            } else {
                for ( j in val.value ) {
                    if ( hasOwn.call( val.value, j ) ) {
                        urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
                            ( config[ val.id ] === j ?
                                ( selection = true ) && " selected='selected'" : "" ) +
                            ">" + escapeText( val.value[ j ] ) + "</option>";
                    }
                }
            }
            if ( config[ val.id ] && !selection ) {
                escaped = escapeText( config[ val.id ] );
                urlConfigHtml += "<option value='" + escaped +
                    "' selected='selected' disabled='disabled'>" + escaped + "</option>";
            }
            urlConfigHtml += "</select>";
        }
    }

    return urlConfigHtml;
}

// Handle "click" events on toolbar checkboxes and "change" for select menus.
// Updates the URL with the new state of `config.urlConfig` values.
function toolbarChanged() {
    var updatedUrl, value,
        field = this,
        params = {};

    // Detect if field is a select menu or a checkbox
    if ( "selectedIndex" in field ) {
        value = field.options[ field.selectedIndex ].value || undefined;
    } else {
        value = field.checked ? ( field.defaultValue || true ) : undefined;
    }

    params[ field.name ] = value;
    updatedUrl = QUnit.url( params );

    if ( "hidepassed" === field.name && "replaceState" in window.history ) {
        config[ field.name ] = value || false;
        if ( value ) {
            addClass( id( "qunit-tests" ), "hidepass" );
        } else {
            removeClass( id( "qunit-tests" ), "hidepass" );
        }

        // It is not necessary to refresh the whole page
        window.history.replaceState( null, "", updatedUrl );
    } else {
        window.location = updatedUrl;
    }
}

function toolbarUrlConfigContainer() {
    var urlConfigContainer = document.createElement( "span" );

    urlConfigContainer.innerHTML = getUrlConfigHtml();

    // For oldIE support:
    // * Add handlers to the individual elements instead of the container
    // * Use "click" instead of "change" for checkboxes
    addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
    addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );

    return urlConfigContainer;
}

function toolbarModuleFilterHtml() {
    var i,
        moduleFilterHtml = "";

    if ( !modulesList.length ) {
        return false;
    }

    modulesList.sort(function( a, b ) {
        return a.localeCompare( b );
    });

    moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
        "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
        ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
        ">< All Modules ></option>";

    for ( i = 0; i < modulesList.length; i++ ) {
        moduleFilterHtml += "<option value='" +
            escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
            ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
            ">" + escapeText( modulesList[ i ] ) + "</option>";
    }
    moduleFilterHtml += "</select>";

    return moduleFilterHtml;
}

function toolbarModuleFilter() {
    var toolbar = id( "qunit-testrunner-toolbar" ),
        moduleFilter = document.createElement( "span" ),
        moduleFilterHtml = toolbarModuleFilterHtml();

    if ( !moduleFilterHtml ) {
        return false;
    }

    moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
    moduleFilter.innerHTML = moduleFilterHtml;

    addEvent( moduleFilter.lastChild, "change", function() {
        var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
            selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );

        window.location = QUnit.url({
            module: ( selection === "" ) ? undefined : selection,

            // Remove any existing filters
            filter: undefined,
            testId: undefined
        });
    });

    toolbar.appendChild( moduleFilter );
}

function appendToolbar() {
    var toolbar = id( "qunit-testrunner-toolbar" );

    if ( toolbar ) {
        toolbar.appendChild( toolbarUrlConfigContainer() );
    }
}

function appendBanner() {
    var banner = id( "qunit-banner" );

    if ( banner ) {
        banner.className = "";
        banner.innerHTML = "<a href='" +
            QUnit.url({ filter: undefined, module: undefined, testId: undefined }) +
            "'>" + banner.innerHTML + "</a> ";
    }
}

function appendTestResults() {
    var tests = id( "qunit-tests" ),
        result = id( "qunit-testresult" );

    if ( result ) {
        result.parentNode.removeChild( result );
    }

    if ( tests ) {
        tests.innerHTML = "";
        result = document.createElement( "p" );
        result.id = "qunit-testresult";
        result.className = "result";
        tests.parentNode.insertBefore( result, tests );
        result.innerHTML = "Running...<br />&#160;";
    }
}

function storeFixture() {
    var fixture = id( "qunit-fixture" );
    if ( fixture ) {
        config.fixture = fixture.innerHTML;
    }
}

function appendUserAgent() {
    var userAgent = id( "qunit-userAgent" );
    if ( userAgent ) {
        userAgent.innerHTML = navigator.userAgent;
    }
}

function appendTestsList( modules ) {
    var i, l, x, z, test, moduleObj;

    for ( i = 0, l = modules.length; i < l; i++ ) {
        moduleObj = modules[ i ];

        if ( moduleObj.name ) {
            modulesList.push( moduleObj.name );
        }

        for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
            test = moduleObj.tests[ x ];

            appendTest( test.name, test.testId, moduleObj.name );
        }
    }
}

function appendTest( name, testId, moduleName ) {
    var title, rerunTrigger, testBlock, assertList,
        tests = id( "qunit-tests" );

    if ( !tests ) {
        return;
    }

    title = document.createElement( "strong" );
    title.innerHTML = getNameHtml( name, moduleName );

    rerunTrigger = document.createElement( "a" );
    rerunTrigger.innerHTML = "Rerun";
    rerunTrigger.href = QUnit.url({ testId: testId });

    testBlock = document.createElement( "li" );
    testBlock.appendChild( title );
    testBlock.appendChild( rerunTrigger );
    testBlock.id = "qunit-test-output-" + testId;

    assertList = document.createElement( "ol" );
    assertList.className = "qunit-assert-list";

    testBlock.appendChild( assertList );

    tests.appendChild( testBlock );
}

// HTML Reporter initialization and load
QUnit.begin(function( details ) {
    var qunit = id( "qunit" );

    // Fixture is the only one necessary to run without the #qunit element
    storeFixture();

    if ( !qunit ) {
        return;
    }

    qunit.innerHTML =
        "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
        "<h2 id='qunit-banner'></h2>" +
        "<div id='qunit-testrunner-toolbar'></div>" +
        "<h2 id='qunit-userAgent'></h2>" +
        "<ol id='qunit-tests'></ol>";

    appendBanner();
    appendTestResults();
    appendUserAgent();
    appendToolbar();
    appendTestsList( details.modules );
    toolbarModuleFilter();

    if ( config.hidepassed ) {
        addClass( qunit.lastChild, "hidepass" );
    }
});

QUnit.done(function( details ) {
    var i, key,
        banner = id( "qunit-banner" ),
        tests = id( "qunit-tests" ),
        html = [
            "Tests completed in ",
            details.runtime,
            " milliseconds.<br />",
            "<span class='passed'>",
            details.passed,
            "</span> assertions of <span class='total'>",
            details.total,
            "</span> passed, <span class='failed'>",
            details.failed,
            "</span> failed."
        ].join( "" );

    if ( banner ) {
        banner.className = details.failed ? "qunit-fail" : "qunit-pass";
    }

    if ( tests ) {
        id( "qunit-testresult" ).innerHTML = html;
    }

    if ( config.altertitle && defined.document && document.title ) {

        // show ✖ for good, ✔ for bad suite result in title
        // use escape sequences in case file gets loaded with non-utf-8-charset
        document.title = [
            ( details.failed ? "\u2716" : "\u2714" ),
            document.title.replace( /^[\u2714\u2716] /i, "" )
        ].join( " " );
    }

    // clear own sessionStorage items if all tests passed
    if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
        for ( i = 0; i < sessionStorage.length; i++ ) {
            key = sessionStorage.key( i++ );
            if ( key.indexOf( "qunit-test-" ) === 0 ) {
                sessionStorage.removeItem( key );
            }
        }
    }

    // scroll back to top to show results
    if ( config.scrolltop && window.scrollTo ) {
        window.scrollTo( 0, 0 );
    }
});

function getNameHtml( name, module ) {
    var nameHtml = "";

    if ( module ) {
        nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
    }

    nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";

    return nameHtml;
}

QUnit.testStart(function( details ) {
    var running, testBlock;

    testBlock = id( "qunit-test-output-" + details.testId );
    if ( testBlock ) {
        testBlock.className = "running";
    } else {

        // Report later registered tests
        appendTest( details.name, details.testId, details.module );
    }

    running = id( "qunit-testresult" );
    if ( running ) {
        running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module );
    }

});

QUnit.log(function( details ) {
    var assertList, assertLi,
        message, expected, actual,
        testItem = id( "qunit-test-output-" + details.testId );

    if ( !testItem ) {
        return;
    }

    message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
    message = "<span class='test-message'>" + message + "</span>";
    message += "<span class='runtime'>@ " + details.runtime + " ms</span>";

    // pushFailure doesn't provide details.expected
    // when it calls, it's implicit to also not show expected and diff stuff
    // Also, we need to check details.expected existence, as it can exist and be undefined
    if ( !details.result && hasOwn.call( details, "expected" ) ) {
        expected = escapeText( QUnit.dump.parse( details.expected ) );
        actual = escapeText( QUnit.dump.parse( details.actual ) );
        message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
            expected +
            "</pre></td></tr>";

        if ( actual !== expected ) {
            message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
                actual + "</pre></td></tr>" +
                "<tr class='test-diff'><th>Diff: </th><td><pre>" +
                QUnit.diff( expected, actual ) + "</pre></td></tr>";
        }

        if ( details.source ) {
            message += "<tr class='test-source'><th>Source: </th><td><pre>" +
                escapeText( details.source ) + "</pre></td></tr>";
        }

        message += "</table>";

    // this occours when pushFailure is set and we have an extracted stack trace
    } else if ( !details.result && details.source ) {
        message += "<table>" +
            "<tr class='test-source'><th>Source: </th><td><pre>" +
            escapeText( details.source ) + "</pre></td></tr>" +
            "</table>";
    }

    assertList = testItem.getElementsByTagName( "ol" )[ 0 ];

    assertLi = document.createElement( "li" );
    assertLi.className = details.result ? "pass" : "fail";
    assertLi.innerHTML = message;
    assertList.appendChild( assertLi );
});

QUnit.testDone(function( details ) {
    var testTitle, time, testItem, assertList,
        good, bad, testCounts, skipped,
        tests = id( "qunit-tests" );

    if ( !tests ) {
        return;
    }

    testItem = id( "qunit-test-output-" + details.testId );

    assertList = testItem.getElementsByTagName( "ol" )[ 0 ];

    good = details.passed;
    bad = details.failed;

    // store result when possible
    if ( config.reorder && defined.sessionStorage ) {
        if ( bad ) {
            sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
        } else {
            sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
        }
    }

    if ( bad === 0 ) {
        addClass( assertList, "qunit-collapsed" );
    }

    // testItem.firstChild is the test name
    testTitle = testItem.firstChild;

    testCounts = bad ?
        "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
        "";

    testTitle.innerHTML += " <b class='counts'>(" + testCounts +
        details.assertions.length + ")</b>";

    if ( details.skipped ) {
        addClass( testItem, "skipped" );
        skipped = document.createElement( "em" );
        skipped.className = "qunit-skipped-label";
        skipped.innerHTML = "skipped";
        testItem.insertBefore( skipped, testTitle );
    } else {
        addEvent( testTitle, "click", function() {
            toggleClass( assertList, "qunit-collapsed" );
        });

        testItem.className = bad ? "fail" : "pass";

        time = document.createElement( "span" );
        time.className = "runtime";
        time.innerHTML = details.runtime + " ms";
        testItem.insertBefore( time, assertList );
    }
});

if ( !defined.document || document.readyState === "complete" ) {
    config.pageLoaded = true;
    config.autorun = true;
}

if ( defined.document ) {
    addEvent( window, "load", QUnit.load );
}

})();


================================================
FILE: test/readme_example.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>SparkMD5 readme example</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="../spark-md5.js"></script>
    </head>
    <body onload="init()">
        <input type="file" id="file" />
        <script>
            function init() {
                document.getElementById('file').addEventListener('change', function () {
                    var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                        file = this.files[0],
                        chunkSize = 2097152,                             // Read in chunks of 2MB
                        chunks = Math.ceil(file.size / chunkSize),
                        currentChunk = 0,
                        spark = new SparkMD5.ArrayBuffer(),
                        fileReader = new FileReader();

                    fileReader.onload = function (e) {
                        console.log('read chunk nr', currentChunk + 1, 'of', chunks);
                        spark.append(e.target.result);                   // Append array buffer
                        currentChunk++;

                        if (currentChunk < chunks) {
                            loadNext();
                        } else {
                            console.log('finished loading');
                            console.info('computed hash', spark.end());  // Compute hash
                        }
                    };

                    fileReader.onerror = function () {
                        console.warn('oops, something went wrong.');
                    };

                    function loadNext() {
                        var start = currentChunk * chunkSize,
                            end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;

                        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                    }

                    loadNext();
                });
            }
        </script>
    </body>
</html>


================================================
FILE: test/specs.js
================================================
/*global test, equal*/

var hasher = new SparkMD5(),
    buffHasher = new SparkMD5.ArrayBuffer();

function unicodeStringToArrayBuffer(str) {
    if (/[\u0080-\uFFFF]/.test(str)) {
        str = unescape(encodeURIComponent(str));
    }

    return stringToArrayBuffer(str);
}

function stringToArrayBuffer(str) {
    var length = str.length,
       buff = new ArrayBuffer(length),
       arr = new Uint8Array(buff),
       i;

    for (i = 0; i < length; i += 1) {
        arr[i] = str.charCodeAt(i);
    }

    return buff;
}

function binaryStringToHex(str) {
    var hex = '',
        ch,
        i,
        length = str.length;

    for (i = 0; i < length; i += 1) {
        ch = str.charCodeAt(i);
        hex += (ch >> 4).toString(16);
        hex += (ch & 0xF).toString(16);
    }

    return hex;
}

test('Hash of "hello"', function () {
    var str = 'hello',
        hash = '5d41402abc4b2a76b9719d911017c592';

    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');
    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), hash, 'Incremental (normal)');
    hasher.appendBinary(str);
    equal(hasher.end(), hash, 'Incremental (binary)');
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), hash, 'Incremental (array buffer)');
});

test('Hash of "hello" (raw)', function () {
    var str = 'hello',
        hash = '5d41402abc4b2a76b9719d911017c592';

    equal(binaryStringToHex(SparkMD5.hash(str, true)), hash, 'SparkMD5.hash()');
    equal(binaryStringToHex(SparkMD5.hashBinary(str, true)), hash, 'SparkMD5.hashBinary()');
    equal(binaryStringToHex(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str), true)), hash, 'SparkMD5.ArrayBuffer.hash()');

    hasher.reset();
    hasher.append(str);
    equal(binaryStringToHex(hasher.end(true)), hash, 'Incremental (normal)');
    hasher.appendBinary(str);
    equal(binaryStringToHex(hasher.end(true)), hash, 'Incremental (binary)');
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(binaryStringToHex(buffHasher.end(true)), hash, 'Incremental (array buffer)');
});

test('Hash of 64 bytes', function () {
    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c592',
        hash = 'e0b153045b08d59d4e18a98ab823ac42';

    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');
    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), hash, 'Incremental (normal)');
    hasher.appendBinary(str);
    equal(hasher.end(), hash, 'Incremental (binary)');
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), hash, 'Incremental (array buffer)');
});

test('Hash of 128 bytes', function () {
    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c592',
        hash = 'b12bc24f5507eba4ee27092f70148415';

    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');
    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), hash, 'Incremental (normal)');
    hasher.appendBinary(str);
    equal(hasher.end(), hash, 'Incremental (binary)');
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), hash, 'Incremental (array buffer)');
});

test('Hash of 160 bytes', function () {
    var str = '5d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a765d41402abc4b2a76b9719d911017c5925d41402abc4b2a76b9719d911017c5925d41402abc4b2a76',
        hash = '66a1e6b119bf30ade63378f770e52549';

    equal(SparkMD5.hash(str), hash, 'SparkMD5.hash()');
    equal(SparkMD5.hashBinary(str), hash, 'SparkMD5.hashBinary()');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), hash, 'SparkMD5.ArrayBuffer.hash()');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), hash, 'Incremental (normal)');
    hasher.appendBinary(str);
    equal(hasher.end(), hash, 'Incremental (binary)');
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), hash, 'Incremental (array buffer)');
});

test('Incremental usage', function () {
    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456a234');

    equal(hasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (normal) of 20+20+24');

    hasher.reset();
    hasher.appendBinary('5d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a421456a234');

    equal(hasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (binary) of 20+20+24');

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));

    equal(buffHasher.end(), '014d4bbb02c66c98249114dc674a7187', 'Incremental (array buffer) of 20+20+24');

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');

    equal(hasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (normal) of 20+20+20');

    hasher.reset();
    hasher.appendBinary('5d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a421456');

    equal(hasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (binary) of 20+20+20');

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));

    equal(buffHasher.end(), 'f15937a66ae98c76c0dfbc6df2b75703', 'Incremental (array buffer) of 20+20+20');

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');

    equal(hasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (normal) of 20+60+20');

    hasher.reset();
    hasher.appendBinary('5d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456');
    hasher.appendBinary('5d41402abc4b2a421456');

    equal(hasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (binary) of 20+60+20');

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a4214565d41402abc4b2a4214565d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));

    equal(buffHasher.end(), '45762198a57a35c8523915898fb8c68c', 'Incremental (array buffer) of 20+60+20');

});

test('Incremental usage (resume)', function () {
    var md5,
        state;

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456a234');
    md5 = hasher.end();

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    state = hasher.getState();
    hasher.reset();
    hasher.setState(state);
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456a234');

    equal(hasher.end(), md5, 'MD5 should be the same');
    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');

    // Same tests but for buffers
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));
    md5 = buffHasher.end();

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    state = buffHasher.getState();

    buffHasher.reset();
    buffHasher.setState(state);
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));

    equal(buffHasher.end(), md5, 'MD5 should be the same');
    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');
});

test('Incremental usage (rolling)', function () {
    var md5,
        state;

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456a234');
    md5 = hasher.end();

    hasher.reset();
    hasher.append('5d41402abc4b2a421456');
    state = hasher.getState();
    hasher.end();
    hasher.setState(state);
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456');
    hasher.append('5d41402abc4b2a421456a234');

    equal(hasher.end(), md5, 'MD5 should be the same');
    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');

    // Same tests but for buffers
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));
    md5 = buffHasher.end();

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    state = buffHasher.getState();

    buffHasher.end();
    buffHasher.setState(state);
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));

    equal(buffHasher.end(), md5, 'MD5 should be the same');
    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');
});

test('Incremental usage (resume with JSON.stringify)', function () {
    var md5,
        state;

    // Same tests but for buffers
    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));
    md5 = buffHasher.end();

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    state = JSON.stringify(buffHasher.getState());

    buffHasher.reset();
    buffHasher.setState(JSON.parse(state));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456'));
    buffHasher.append(unicodeStringToArrayBuffer('5d41402abc4b2a421456a234'));

    equal(buffHasher.end(), md5, 'MD5 should be the same');
    equal(md5, 'c9db0e4d21ebbba7014bd62353b2135e', 'Actual MD5 check');
});

test('UTF-8', function () {
    var str = 'räksmörgås';

    equal(SparkMD5.hash(str), 'e462805dcf84413d5eddca45a4b88a5e', 'SparkMD5.hash() of "' + str + '"');
    equal(SparkMD5.hashBinary(str), '09d9d71ec8a8e3bc74e51ebd587154f3', 'SparkMD5.hashBinary() of "' + str + '"');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), 'e462805dcf84413d5eddca45a4b88a5e', 'SparkMD5.ArrayBuffer.hash() of "' + str + '"');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), 'e462805dcf84413d5eddca45a4b88a5e', 'Incremental (normal) of "' + str + '"');

    hasher.reset();
    hasher.appendBinary(str);
    equal(hasher.end(), '09d9d71ec8a8e3bc74e51ebd587154f3', 'Incremental (binary) of "' + str + '"');

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), 'e462805dcf84413d5eddca45a4b88a5e', 'Incremental (array buffer) of "' + str + '"');

    str = '\u30b9\u3092\u98df';

    equal(SparkMD5.hash(str), '453931ab48a4a5af69f3da3c21064fc9', 'SparkMD5.hash() of "' + str + '"');
    equal(SparkMD5.hashBinary(str), '24e3399be06b7cf59dbd848e18d9246c', 'SparkMD5.hashBinary() of "' + str + '"');
    equal(SparkMD5.ArrayBuffer.hash(unicodeStringToArrayBuffer(str)), '453931ab48a4a5af69f3da3c21064fc9', 'SparkMD5.ArrayBuffer.hash() of "' + str + '"');

    hasher.reset();
    hasher.append(str);
    equal(hasher.end(), '453931ab48a4a5af69f3da3c21064fc9', 'Incremental (normal) of "' + str + '"');

    hasher.reset();
    hasher.appendBinary(str);
    equal(hasher.end(), '24e3399be06b7cf59dbd848e18d9246c', 'Incremental (binary) of "' + str + '"');

    buffHasher.reset();
    buffHasher.append(unicodeStringToArrayBuffer(str));
    equal(buffHasher.end(), '453931ab48a4a5af69f3da3c21064fc9', 'Incremental (array buffer) of "' + str + '"');
});

test('Hashing a PNG - ArrayBuffer vs binary string', function () {
    var binString,
        buffer;

    // 1x1 transparent PNG
    binString = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=');
    buffer = stringToArrayBuffer(binString);

    buffHasher.reset();
    buffHasher.append(buffer);

    hasher.reset();
    hasher.appendBinary(binString);

    equal(buffHasher.end(), hasher.end(), 'md5 sum should be the same for both binary strings and ArrayBuffers');
});
Download .txt
gitextract_24fyzl0z/

├── .editorconfig
├── .gitignore
├── .jscsrc
├── .jshintrc
├── LICENSE
├── LICENSE2
├── README.md
├── bower.json
├── component.json
├── package.json
├── spark-md5.js
└── test/
    ├── css/
    │   └── qunit-1.16.0.css
    ├── file_reader.html
    ├── file_reader_binary.html
    ├── index.html
    ├── index.min.html
    ├── js/
    │   └── qunit-1.16.0.js
    ├── readme_example.html
    └── specs.js
Download .txt
SYMBOL INDEX (67 symbols across 3 files)

FILE: spark-md5.js
  function cmn (line 43) | function cmn(q, a, b, x, s, t) {
  function md5cycle (line 48) | function md5cycle(x, k) {
  function md5blk (line 192) | function md5blk(s) {
  function md5blk_array (line 202) | function md5blk_array(a) {
  function md51 (line 212) | function md51(s) {
  function md51_array (line 252) | function md51_array(a) {
  function rhex (line 300) | function rhex(n) {
  function hex (line 309) | function hex(x) {
  function clamp (line 336) | function clamp(val, length) {
  function toUtf8 (line 381) | function toUtf8(str) {
  function utf8Str2ArrayBuffer (line 389) | function utf8Str2ArrayBuffer(str, returnUInt8Array) {
  function arrayBuffer2Utf8Str (line 402) | function arrayBuffer2Utf8Str(buff) {
  function concatenateArrayBuffers (line 406) | function concatenateArrayBuffers(first, second, returnUInt8Array) {
  function hexToBinaryString (line 415) | function hexToBinaryString(hex) {
  function SparkMD5 (line 436) | function SparkMD5() {

FILE: test/js/qunit-1.16.0.js
  function registerLoggingCallback (line 413) | function registerLoggingCallback( key ) {
  function done (line 476) | function done() {
  function extractStacktrace (line 507) | function extractStacktrace( e, offset ) {
  function sourceFromStacktrace (line 549) | function sourceFromStacktrace( offset ) {
  function synchronize (line 562) | function synchronize( callback, last ) {
  function process (line 576) | function process( last ) {
  function begin (line 603) | function begin() {
  function resumeProcessing (line 639) | function resumeProcessing() {
  function pauseProcessing (line 659) | function pauseProcessing() {
  function saveGlobal (line 676) | function saveGlobal() {
  function checkPollution (line 692) | function checkPollution() {
  function diff (line 711) | function diff( a, b ) {
  function extend (line 727) | function extend( a, b, undefOnly ) {
  function runLoggingCallbacks (line 745) | function runLoggingCallbacks( key, args ) {
  function verifyLoggingCallbacks (line 757) | function verifyLoggingCallbacks() {
  function inArray (line 783) | function inArray( elem, array ) {
  function Test (line 797) | function Test( settings ) {
  function run (line 1021) | function run() {
  function generateHash (line 1217) | function generateHash( module, testName ) {
  function Assert (line 1239) | function Assert( testContext ) {
  function bindCallbacks (line 1460) | function bindCallbacks( o, callbacks, args ) {
  function useStrictEquality (line 1488) | function useStrictEquality( b, a ) {
  function quote (line 1678) | function quote( str ) {
  function literal (line 1681) | function literal( o ) {
  function join (line 1684) | function join( pre, arr, post ) {
  function array (line 1696) | function array( arr, stack ) {
  function applyCurrent (line 1948) | function applyCurrent( current ) {
  function diff (line 2024) | function diff( o, n ) {
  function escapeText (line 2238) | function escapeText( s ) {
  function addEvent (line 2266) | function addEvent( elem, type, fn ) {
  function addEvents (line 2283) | function addEvents( elems, type, fn ) {
  function hasClass (line 2290) | function hasClass( elem, name ) {
  function addClass (line 2294) | function addClass( elem, name ) {
  function toggleClass (line 2300) | function toggleClass( elem, name ) {
  function removeClass (line 2308) | function removeClass( elem, name ) {
  function id (line 2320) | function id( name ) {
  function getUrlConfigHtml (line 2324) | function getUrlConfigHtml() {
  function toolbarChanged (line 2389) | function toolbarChanged() {
  function toolbarUrlConfigContainer (line 2419) | function toolbarUrlConfigContainer() {
  function toolbarModuleFilterHtml (line 2433) | function toolbarModuleFilterHtml() {
  function toolbarModuleFilter (line 2461) | function toolbarModuleFilter() {
  function appendToolbar (line 2489) | function appendToolbar() {
  function appendBanner (line 2497) | function appendBanner() {
  function appendTestResults (line 2508) | function appendTestResults() {
  function storeFixture (line 2526) | function storeFixture() {
  function appendUserAgent (line 2533) | function appendUserAgent() {
  function appendTestsList (line 2540) | function appendTestsList( modules ) {
  function appendTest (line 2558) | function appendTest( name, testId, moduleName ) {
  function getNameHtml (line 2667) | function getNameHtml( name, module ) {

FILE: test/specs.js
  function unicodeStringToArrayBuffer (line 6) | function unicodeStringToArrayBuffer(str) {
  function stringToArrayBuffer (line 14) | function stringToArrayBuffer(str) {
  function binaryStringToHex (line 27) | function binaryStringToHex(str) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (171K chars).
[
  {
    "path": ".editorconfig",
    "chars": 220,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".gitignore",
    "chars": 24,
    "preview": "node_modules\nnpm-debug.*"
  },
  {
    "path": ".jscsrc",
    "chars": 1958,
    "preview": "{\n    \"disallowKeywordsOnNewLine\": [\n        \"else\",\n        \"catch\"\n    ],\n    \"disallowMixedSpacesAndTabs\": true,\n    "
  },
  {
    "path": ".jshintrc",
    "chars": 1129,
    "preview": "{\n    \"predef\": [\n        \"console\",\n        \"define\",\n        \"self\",\n        \"unescape\",\n        \"module\",\n        \"Fi"
  },
  {
    "path": "LICENSE",
    "chars": 485,
    "preview": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 201"
  },
  {
    "path": "LICENSE2",
    "chars": 1075,
    "preview": "Copyright (c) 2015 André Cruz <amdfcruz@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "README.md",
    "chars": 6018,
    "preview": "# SparkMD5\n\nSparkMD5 is a fast md5 implementation of the MD5 algorithm.\nThis script is based in the JKM md5 library whic"
  },
  {
    "path": "bower.json",
    "chars": 483,
    "preview": "{\n  \"name\": \"SparkMD5\",\n  \"version\": \"3.0.0\",\n  \"homepage\": \"https://github.com/satazor/js-spark-md5\",\n  \"authors\": [\n  "
  },
  {
    "path": "component.json",
    "chars": 358,
    "preview": "{\n  \"name\": \"md5\",\n  \"repo\": \"satazor/js-spark-md5\",\n  \"description\": \"Lightning fast normal and incremental md5 for jav"
  },
  {
    "path": "package.json",
    "chars": 749,
    "preview": "{\n  \"name\": \"spark-md5\",\n  \"version\": \"3.0.2\",\n  \"description\": \"Lightning fast normal and incremental md5 for javascrip"
  },
  {
    "path": "spark-md5.js",
    "chars": 22968,
    "preview": "(function (factory) {\n    if (typeof exports === 'object') {\n        // Node/CommonJS\n        module.exports = factory()"
  },
  {
    "path": "test/css/qunit-1.16.0.css",
    "chars": 5097,
    "preview": "/*!\n * QUnit 1.16.0\n * http://qunitjs.com/\n *\n * Copyright 2006, 2014 jQuery Foundation and other contributors\n * Releas"
  },
  {
    "path": "test/file_reader.html",
    "chars": 8293,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 file reader test</title>\n        <meta http-equiv=\"Content-Typ"
  },
  {
    "path": "test/file_reader_binary.html",
    "chars": 8279,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 file reader test</title>\n        <meta http-equiv=\"Content-Typ"
  },
  {
    "path": "test/index.html",
    "chars": 785,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 tests</title>\n        <meta http-equiv=\"Content-Type\" content="
  },
  {
    "path": "test/index.min.html",
    "chars": 804,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 tests (minified version)</title>\n        <meta http-equiv=\"Con"
  },
  {
    "path": "test/js/qunit-1.16.0.js",
    "chars": 87821,
    "preview": "/*!\n * QUnit 1.16.0\n * http://qunitjs.com/\n *\n * Copyright 2006, 2014 jQuery Foundation and other contributors\n * Releas"
  },
  {
    "path": "test/readme_example.html",
    "chars": 2078,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>SparkMD5 readme example</title>\n        <meta http-equiv=\"Content-Type\""
  },
  {
    "path": "test/specs.js",
    "chars": 14846,
    "preview": "/*global test, equal*/\n\nvar hasher = new SparkMD5(),\n    buffHasher = new SparkMD5.ArrayBuffer();\n\nfunction unicodeStrin"
  }
]

About this extraction

This page contains the full source code of the satazor/SparkMD5 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (159.6 KB), approximately 41.3k tokens, and a symbol index with 67 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!