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 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 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 " ], "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 ", "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 ================================================ SparkMD5 file reader test

SparkMD5 file reader test, incremental and normal md5

Please note that the advantage of doing an incremental md5 is to keep memory usage low.

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).
With normal md5, you should observe slightly faster times but high memory usage (because the entire file need to be read into an array).
With incremental md5, you should observe stable memory usage but slightly higher times.
Be aware that while using normal md5, the browser can crash due to high memory usage.

================================================ FILE: test/file_reader_binary.html ================================================ SparkMD5 file reader test

SparkMD5 file reader test, incremental and normal md5

Please note that the advantage of doing an incremental md5 is to keep memory usage low.

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).
With normal md5, you should observe slightly faster times but high memory usage (because the entire file need to be read into an array).
With incremental md5, you should observe stable memory usage but slightly higher times.
Be aware that while using normal md5, the browser can crash due to high memory usage.

================================================ FILE: test/index.html ================================================ SparkMD5 tests

SparkMD5 test

    ================================================ FILE: test/index.min.html ================================================ SparkMD5 tests (minified version)

    SparkMD5 test

      ================================================ 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é 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 ? "
      " : "\n" : this.HTML ? " " : " "; }, // 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, " " ); } 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 ? "<" : "<", close = dump.HTML ? ">" : ">", 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 brown fox jumped jumps 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 += "" + out.o[ i ] + oSpace[ i ] + ""; } } else { if ( out.n[ 0 ].text == null ) { for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) { str += "" + out.o[ n ] + oSpace[ n ] + ""; } } for ( i = 0; i < out.n.length; i++ ) { if ( out.n[ i ].text == null ) { str += "" + out.n[ i ] + nSpace[ i ] + ""; } 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 += "" + out.o[ n ] + oSpace[ n ] + ""; } 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 = "

      " + escapeText( document.title ) + "

      " + "

      " + "
      " + "

      " + "
        "; } 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...
         "; } }; // 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 "'"; case "\"": return """; case "<": return "<"; case ">": return ">"; case "&": return "&"; } }); } /** * @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 += ""; } else { urlConfigHtml += ""; } } 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 += "" + ""; 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 = "" + banner.innerHTML + " "; } } 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...
         "; } } 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 = "

        " + escapeText( document.title ) + "

        " + "

        " + "
        " + "

        " + "
          "; 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.
          ", "", details.passed, " assertions of ", details.total, " passed, ", details.failed, " 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 = "" + escapeText( module ) + ": "; } nameHtml += "" + escapeText( name ) + ""; 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:
          " + 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 = "" + message + ""; message += "@ " + details.runtime + " ms"; // 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 += ""; if ( actual !== expected ) { message += "" + ""; } if ( details.source ) { message += ""; } message += "
          Expected:
          " +
                      expected +
                      "
          Result:
          " +
                          actual + "
          Diff:
          " +
                          QUnit.diff( expected, actual ) + "
          Source:
          " +
                          escapeText( details.source ) + "
          "; // this occours when pushFailure is set and we have an extracted stack trace } else if ( !details.result && details.source ) { message += "" + "" + "
          Source:
          " +
                      escapeText( details.source ) + "
          "; } 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 ? "" + bad + ", " + "" + good + ", " : ""; testTitle.innerHTML += " (" + testCounts + details.assertions.length + ")"; 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 ================================================ SparkMD5 readme example ================================================ 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'); });