diff --git a/README.md b/README.md index 9bc2fdb3..9e256c82 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ parameter must be adjusted together with `progressCallbacksInterval` parameter. * `successStatuses` Response is success if response status is in this list (Default: `[200,201, 202]`) * `permanentErrors` Response fails if response status is in this list (Default: `[404, 415, 500, 501]`) - +* `getFolderPathsInfo` function to get all files paths info. It will be passed a paths(string) array, a FlowFolder object and a callback function.And the callback function should be passed an Object(`{"a/": entryId or some other things, "a/c/": entryId or some other things}`) as parameter. #### Properties @@ -139,6 +139,7 @@ parameter must be adjusted together with `progressCallbacksInterval` parameter. * `.supportDirectory` A boolean value, which indicates if browser supports directory uploads. * `.opts` A hash object of the configuration of the Flow.js instance. * `.files` An array of `FlowFile` file objects added by the user (see full docs for this object type below). +* `.parsedFiles` An array of `FlowFile` or `FlowFolder` file objects added by the user (see full docs for this object type below). #### Methods @@ -181,8 +182,8 @@ This event is also called before file is added to upload queue, this means that calling `flow.upload()` function will not start current file upload. Optionally, you can use the browser `event` object from when the file was added. -* `.filesAdded(array, event)` Same as fileAdded, but used for multiple file validation. -* `.filesSubmitted(array, event)` Can be used to start upload of currently added files. +* `.filesAdded(array/*FlowFile*/, array/*FlowFolder*/, event)` Same as fileAdded, but used for multiple file validation. +* `.filesSubmitted(array/*FlowFile*/, array/*FlowFolder*/, event)` Can be used to start upload of currently added files. * `.fileRetry(file, chunk)` Something went wrong during upload of a specific file, uploading is being retried. * `.fileError(file, message, chunk)` An error occurred during upload of a specific file. @@ -197,6 +198,7 @@ FlowFile constructor can be accessed in `Flow.FlowFile`. #### Properties * `.flowObj` A back-reference to the parent `Flow` object. +* `.folderObj` A back-reference to the parent `FlowFolder` object if the file is in a file. * `.file` The correlating HTML5 `File` object. * `.name` The name of the file. * `.relativePath` The relative path to the file (defaults to file name if relative path doesn't exist) @@ -210,7 +212,7 @@ FlowFile constructor can be accessed in `Flow.FlowFile`. #### Methods -* `.progress(relative)` Returns a float between 0 and 1 indicating the current upload progress of the file. If `relative` is `true`, the value is returned relative to all files in the Flow.js instance. +* `.progress()` Returns a float between 0 and 1 indicating the current upload progress of the file. * `.pause()` Pause uploading the file. * `.resume()` Resume uploading the file. * `.cancel()` Abort uploading the file and delete it from the list of files to upload. @@ -218,10 +220,39 @@ FlowFile constructor can be accessed in `Flow.FlowFile`. * `.bootstrap()` Rebuild the state of a `FlowFile` object, including reassigning chunks and XMLHttpRequest instances. * `.isUploading()` Returns a boolean indicating whether file chunks is uploading. * `.isComplete()` Returns a boolean indicating whether the file has completed uploading and received a server response. +* `.isPaused()` Returns a boolean indicating whether file is paused. * `.sizeUploaded()` Returns size uploaded in bytes. * `.timeRemaining()` Returns remaining time to finish upload file in seconds. Accuracy is based on average speed. If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY` * `.getExtension()` Returns file extension in lowercase. * `.getType()` Returns file type. +* `.getSize()` Returns file size. +* `.hasError()` Returns a boolean indicating whether file has an error. + +### FlowFolder +FlowFolder constructor can be accessed in `Flow.FlowFolder`. +#### Properties + +* `.flowObj` A back-reference to the parent `Flow` object. +* `.files` An array of FlowFile file objects in the folder. +* `.name` The name of the folder. +* `.pathsInfo` The paths info of the folder. +* `.averageSpeed` Average upload speed, bytes per second. +* `.currentSpeed` Current upload speed, bytes per second. + +#### Methods + +* `.progress(relative)` Returns a float between 0 and 1 indicating the current upload progress of the folder files. +* `.pause()` Pause uploading the folder files. +* `.resume()` Resume uploading the folder files. +* `.cancel()` Abort uploading the folder files and delete them from the list of files to upload. +* `.retry()` Retry uploading the folder files. +* `.isUploading()` Returns a boolean indicating whether folder files is uploading. +* `.isComplete()` Returns a boolean indicating whether the folder files has completed uploading and received a server response. +* `.isPaused()` Returns a boolean indicating whether file is paused. +* `.sizeUploaded()` Returns size uploaded in bytes. +* `.timeRemaining()` Returns remaining time to finish upload folder files file in seconds. Accuracy is based on average speed. If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY` +* `.getSize()` Returns folder files size. +* `.hasError()` Returns a boolean indicating whether file has an error. ## Contribution diff --git a/samples/Node.js/public/folder.html b/samples/Node.js/public/folder.html new file mode 100644 index 00000000..ee835e10 --- /dev/null +++ b/samples/Node.js/public/folder.html @@ -0,0 +1,253 @@ + + + + Flow.js - Multiple simultaneous, stable and resumable uploads via the HTML5 File API + + + + +
+ +

Flow.js

+

It's a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.

+ +

The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause and resume uploads without loosing state.

+ +

Flow.js relies on the HTML5 File API and the ability to chunks files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.

+ +
+ +

Demo

+ + + +
+ Your browser, unfortunately, is not supported by Flow.js. The library requires support for the HTML5 File API along with file slicing. +
+ +
+ Drop files here to upload or select folder or select from your computer or select images +
+ +
+ + + + + + +
+ + + +
+
+ + + + + +
+ + + + + diff --git a/src/flow.js b/src/flow.js index 6f3ddee6..d0dc93de 100644 --- a/src/flow.js +++ b/src/flow.js @@ -62,6 +62,12 @@ */ this.files = []; + /** + * List of FlowFile|FlowFolder objects + * @type {Array.} + */ + this.parsedFiles = []; + /** * Default options for flow.js * @type {Object} @@ -89,7 +95,18 @@ chunkRetryInterval: null, permanentErrors: [404, 415, 500, 501], successStatuses: [200, 201, 202], - onDropStopPropagation: false + onDropStopPropagation: false, + getFolderPathsInfo: function(paths, folderObj, callback) { + /** + * paths: ['a/', 'a/b/'] + * the callback's parameter will be like this: + { + 'a/': entryId or some other things, + 'a/b/': entryId or some other things + } + */ + callback({}) + } }; /** @@ -192,8 +209,25 @@ * returned false. Otherwise it returns true. */ fire: function (event, args) { + var args = Array.prototype.slice.call(arguments); + args.unshift(true); + return this._fire.apply(this, args); + }, + + /** + * Fire an event + * @function + * @param {Boolean} catchall or not + * @param {string} event event name + * @param {...} args arguments of a callback + * @return {bool} value is false if at least one of the event handlers which handled this event + * returned false. Otherwise it returns true. + * @private + */ + _fire: function (catchall, event, args) { // `arguments` is an object, not array, in FF, so: args = Array.prototype.slice.call(arguments); + args.shift(); event = event.toLowerCase(); var preventDefault = false; if (this.events.hasOwnProperty(event)) { @@ -201,7 +235,7 @@ preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault; }, this); } - if (event != 'catchall') { + if (catchall && event != 'catchall') { args.unshift('catchAll'); preventDefault = this.fire.apply(this, args) === false || preventDefault; } @@ -502,7 +536,7 @@ * @function */ resume: function () { - each(this.files, function (file) { + each(this.parsedFiles, function (file) { file.resume(); }); }, @@ -512,18 +546,18 @@ * @function */ pause: function () { - each(this.files, function (file) { + each(this.parsedFiles, function (file) { file.pause(); }); }, /** - * Cancel upload of all FlowFile objects and remove them from the list. + * Cancel upload of all FlowFile|FlowFolder objects and remove them from the list. * @function */ cancel: function () { - for (var i = this.files.length - 1; i >= 0; i--) { - this.files[i].cancel(); + for (var i = this.parsedFiles.length - 1; i >= 0; i--) { + this.parsedFiles[i].cancel(); } }, @@ -575,28 +609,80 @@ } } }, this); - if (this.fire('filesAdded', files, event)) { + var hashMap = new HashMap(); + parseFlowFiles(hashMap, files); + var newParsedFiles = this.getParsedFiles(hashMap); + + if (this.fire('filesAdded', files, newParsedFiles, event)) { each(files, function (file) { if (this.opts.singleFile && this.files.length > 0) { this.removeFile(this.files[0]); } this.files.push(file); }, this); + newParsedFiles.push.apply(this.parsedFiles, newParsedFiles); } - this.fire('filesSubmitted', files, event); + this.fire('filesSubmitted', files, newParsedFiles, event); }, + /** + * get parsedFiles list. + * @function + * @param {FlowFile} file + * @return {Array} parsedFiles + */ + getParsedFiles: function(hashMap) { + var parsedFiles = []; + var f; + each(hashMap.items, function(k) { + f = hashMap.getItem(k); + if (f.constructor == HashMap) { + // is a folder + f = new FlowFolder(this, f, k); + } + parsedFiles.push(f); + }, this); + return parsedFiles; + }, /** * Cancel upload of a specific FlowFile object from the list. * @function * @param {FlowFile} file */ - removeFile: function (file) { + removeFile: function(file) { for (var i = this.files.length - 1; i >= 0; i--) { if (this.files[i] === file) { this.files.splice(i, 1); - file.abort(); + if (file.folderObj) { + // remove file from folderObj.files + file.folderObj.removeFile(file); + } else { + file.abort(); + this.removeParsedFile(file, true); + } + } + } + }, + + /** + * Cancel upload of a specific FlowFolder|FlowFole object from the parsedFiles list. + * @function + * @param {FlowFolder|FlowFile} file + * @param {Boolean} fromRemoveFile + */ + removeParsedFile: function(file, fromRemoveFile) { + for (var i = this.parsedFiles.length - 1, k; i >= 0; i--) { + k = this.parsedFiles[i]; + if (k === file) { + this.parsedFiles.splice(i, 1); + if (file.files) { + each(file.files, function(_file) { + this.removeFile(_file); + }, this); + } else { + !fromRemoveFile && this.removeFile(file); + } } } }, @@ -669,7 +755,485 @@ }; + /** + * parse relativePath to paths + * @function + * @return {Array} parsed paths + * @example + * 'a/b/c/dd.jpg' => ['a/', 'a/b/', 'a/b/c/'] + */ + function parsePaths(path) { + var ret = []; + if (!path) return ret; + + var paths = path.split('/'); + var len = paths.length; + var i = 1; + paths.splice(len - 1, 1); + len--; + if (paths.length) { + while (i <= len) { + ret.push(paths.slice(0, i++).join('/') + '/'); + } + } + return ret; + } + + /** + * update the result hashMap by checking the flowFiles + * @function + * @param {HashMap} ret + * @param {Array} flowFiles + */ + function parseFlowFiles(ret, flowFiles) { + if (!flowFiles.length) return; + each(flowFiles, function(flowFile) { + var ppaths = parsePaths(flowFile.relativePath); + if (ppaths.length) { + // in a folder + each(ppaths, function(path, i) { + var item = ret.getItem(path); + if (!item) { + item = new HashMap(); + ret.setItem(path, item); + } + if (ppaths[i + 1]) { + // a folder + // do nothing + } else { + // a file + item.setItem(flowFile.relativePath, flowFile); + } + }); + flowFile.path = ppaths[ppaths.length - 1]; + } else { + // a file + ret.setItem(flowFile.relativePath, flowFile); + } + }); + for (var i = 0, len = ret.items.length, k, v, ppaths; i < len; i++) { + k = ret.items[i]; + v = ret.getItem(k); + if (v.constructor == HashMap) { + // folder + ppaths = parsePaths(k); + if (ppaths[0] && ppaths[0] !== k) { + // add sub folder to root folder + // so the ret hashMap will be a chain + ret.getItem(ppaths[0]).setItem(k, v); + if (ret.delItem(k)) { + i--; + len--; + } + } + } + } + } + + /** + * HashMap class + * @name HashMap + * @constructor + */ + function HashMap() { + this.items = []; + this.itemsObj = {}; + } + + HashMap.prototype = { + + constructor: HashMap, + + setItem: function(item, obj) { + if (!this.itemsObj[item] && this.itemsObj[item] !== obj) { + this.itemsObj[item] = obj; + this.items.push(item); + } + return this; + }, + + getItem: function(k) { + return this.itemsObj[k]; + }, + + removeItem: function(v) { + var ret = false; + each(this.items, function(k1, i) { + if (this.itemsObj[k1] === v) { + delete this.itemsObj[k1]; + this.items.splice(i , 1); + ret = true; + } + }, this); + return ret; + }, + + delItem: function(k) { + var ret = false; + each(this.items, function(k1, i) { + if (k1 === k) { + delete this.itemsObj[k]; + this.items.splice(i , 1); + ret = true; + } + }, this); + return ret; + } + + }; + + // get all flowFiles in hashmap + function getFlowFilesByHM(hashmap, folderObj, ret) { + each(hashmap.items, function(k) { + var v = hashmap.getItem(k); + if (v.constructor == HashMap) { + getFlowFilesByHM(v, folderObj, ret); + } else { + // in a folder + folderObj && (v.folderObj = folderObj); + ret.push(v); + } + }); + } + + // get all paths in items(hash.items) without no repeat + function getFolderPaths(items, rObj) { + var ret = []; + if (!rObj) rObj = {}; + each(items, function(k) { + if (!rObj[k]) { + var paths = parsePaths(k); + each(paths, function(path) { + if (!rObj[path]) { + rObj[path] = 1; + ret.push(path); + } + }); + rObj[k] = 1; + } + }); + return ret; + } + + + + /** + * FlowFolder class + * @name FlowFolder + * @param {Flow} flowObj + * @param {hashMap} hashMap + * @param {pathname} pathname + * @constructor + */ + function FlowFolder(flowObj, hashMap, pathname) { + + /** + * Reference to parent Flow instance + * @type {Flow} + */ + this.flowObj = flowObj; + + this.name = pathname.substr(0, pathname.length - 1); + + this.isFolder = true; + + /** + * Average upload speed + * @type {number} + */ + this.averageSpeed = 0; + + /** + * Current upload speed + * @type {number} + */ + this.currentSpeed = 0; + + this.pathsInfo = {}; + + var allPaths = getFolderPaths(hashMap.items); + + allPaths.sort(); + + var $ = this; + var inited = false; + + this.waiting = true; + + /** + * all files in the folder + * @type {Array} + */ + this.files = []; + + // getFolderPathsInfo + this.flowObj.opts.getFolderPathsInfo(allPaths, this, function(data) { + if (!inited) { + inited = true; + init(); + } + + $.setPathsInfo(data); + + $.waiting = false; + + // upload now + $.resume(); + + $.flowObj._fire(false, '_gotPathsInfo', $); + + }); + + if (!inited) { + inited = true; + init(); + } + + function init() { + getFlowFilesByHM(hashMap, $, $.files); + // pause for now + // because getFolderPathsInfo may be async + $.pause(true); + } + + } + + FlowFolder.prototype = { + + /** + * Returns a boolean indicating whether or not the instance is currently + * uploading anything. + * @function + * @returns {boolean} + */ + isUploading: function () { + if (!this.isPaused() && !this.isComplete()) { + return true; + } + return false; + }, + + /** + * Indicates if the files has finished + * @function + * @returns {boolean} + */ + isComplete: function () { + var isComplete = true; + each(this.files, function (file) { + if (!file.isComplete()) { + isComplete = false; + return false; + } + }); + return isComplete; + }, + + /** + * Whether one of the files has an error + * @function + * @returns {boolean} + */ + hasError: function() { + var nerr = false; + each(this.files, function (file) { + if (!file.error) { + nerr = true; + return false; + } + }); + return !nerr; + }, + + /** + * Cancel upload of a specific FlowFile object from the files list. + * @function + * @param {FlowFile} file + */ + removeFile: function(file) { + for (var i = this.files.length - 1; i >= 0; i--) { + if (this.files[i] === file) { + this.files.splice(i, 1); + file.folderObj = null; + } + } + this.flowObj.removeFile(file); + if (this.files.length <= 0) { + // now remove the current FlowFolder Object + this.flowObj.removeParsedFile(this); + } + }, + + /** + * Indicates if one of the files is paused + * @function + * @returns {boolean} + */ + isPaused: function() { + var paused = false; + each(this.files, function (file) { + if (file.paused) { + paused = true; + return false; + } + }); + return paused; + }, + + /** + * Retry aborted file upload + * @function + */ + retry: function (single) { + if (single) { + single.bootstrap() + } else { + each(this.files, function (file) { + file.bootstrap() + }) + } + this.flowObj.upload(); + }, + + /** + * Set pathsInfo + * @function + * @param {Object} data + */ + setPathsInfo: function(data) { + var pathsInfo = this.pathsInfo; + each(data, function(v, key) { + pathsInfo[key] = v; + }); + }, + + /** + * Resume uploading. + * @function + */ + resume: function () { + each(this.files, function (file) { + file.resume(); + }); + }, + + /** + * Pause uploading. + * @function + * @param {Boolean|Undefined} isAbort + */ + pause: function (isAbort) { + var funcName = isAbort ? 'abort' : 'pause'; + each(this.files, function (file) { + file[funcName](); + }); + }, + /** + * Cancel upload of the files and remove them from the files list. + * @function + */ + cancel: function () { + for (var i = this.files.length - 1; i >= 0; i--) { + this.files[i].cancel(true); + } + this.flowObj.removeParsedFile(this); + }, + + /** + * Returns a number between 0 and 1 indicating the current upload progress + * of all files. + * @function + * @returns {number} + */ + progress: function () { + var totalDone = 0; + var totalSize = 0; + // Resume all chunks currently being uploaded + each(this.files, function (file) { + totalDone += file.progress() * file.size; + totalSize += file.size; + }); + return totalSize > 0 ? totalDone / totalSize : + this.isComplete() ? 1 : 0;; + }, + + /** + * Returns the total size of all files in bytes. + * @function + * @returns {number} + */ + getSize: function () { + var totalSize = 0; + each(this.files, function (file) { + totalSize += file.size; + }); + return totalSize; + }, + + /** + * Returns the total size uploaded of all files in bytes. + * @function + * @returns {number} + */ + sizeUploaded: function () { + var size = 0; + each(this.files, function (file) { + size += file.sizeUploaded(); + }); + return size; + }, + + /** + * Update speed parameters + * @function + * @private + */ + measureSpeed: function() { + var averageSpeeds = 0; + var currentSpeeds = 0; + var num = 0; + each(this.files, function (file) { + if (!file.paused && !file.error) { + num += 1; + averageSpeeds += file.averageSpeed || 0; + currentSpeeds += file.currentSpeed || 0; + } + }); + if (num) { + this.averageSpeed = averageSpeeds / num; + this.currentSpeed = currentSpeeds / num; + } else { + this.averageSpeed = 0; + this.currentSpeed = 0; + } + }, + + /** + * Returns remaining time to upload all files in seconds. Accuracy is based on average speed. + * If speed is zero, time remaining will be equal to positive infinity `Number.POSITIVE_INFINITY` + * @function + * @returns {number} + */ + timeRemaining: function () { + var sizeDelta = 0; + var averageSpeed = 0; + each(this.files, function (file) { + if (!file.paused && !file.error) { + sizeDelta += file.size - file.sizeUploaded(); + averageSpeed += file.averageSpeed; + } + }); + if (sizeDelta && !averageSpeed) { + return Number.POSITIVE_INFINITY; + } + if (!sizeDelta && !averageSpeed) { + return 0; + } + return Math.floor(sizeDelta / averageSpeed); + } + + }; @@ -688,6 +1252,12 @@ */ this.flowObj = flowObj; + /** + * Reference to parent FlowFolder instance + * @type {FlowFolder} + */ + this.folderObj = null; + /** * Reference to file * @type {File} @@ -712,6 +1282,12 @@ */ this.relativePath = file.relativePath || file.webkitRelativePath || this.name; + /** + * File path, it will be rewrited if the file is in a folder. + * @type {string} + */ + this.path = ''; + /** * File unique identifier * @type {string} @@ -773,6 +1349,16 @@ } FlowFile.prototype = { + + /** + * Whether the file has an error + * @function + * @returns {boolean} + */ + hasError: function() { + return this.error; + }, + /** * Update speed parameters * @link http://stackoverflow.com/questions/2779600/how-to-estimate-download-time-remaining-accurately @@ -789,6 +1375,9 @@ this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0); this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed; this._prevUploadedSize = uploaded; + if (this.folderObj) { + this.folderObj.measureSpeed(); + } }, /** @@ -837,6 +1426,15 @@ } }, + /** + * Indicates if the flowFile is paused + * @function + * @returns {boolean} + */ + isPaused: function() { + return this.paused; + }, + /** * Pause file upload * @function @@ -852,6 +1450,9 @@ */ resume: function() { this.paused = false; + if (this.folderObj && this.folderObj.waiting) { + return; + } this.flowObj.upload(); }, @@ -878,8 +1479,11 @@ * Cancel current upload and remove from a list * @function */ - cancel: function () { + cancel: function (iFolder) { this.flowObj.removeFile(this); + if (!iFolder) { + this.flowObj.removeParsedFile(this); + } }, /** @@ -1002,6 +1606,15 @@ return Math.floor(delta / this.averageSpeed); }, + /** + * Returns the file's size in bytes. + * @function + * @returns {number} + */ + getSize: function () { + return this.size; + }, + /** * Get file type * @function @@ -1241,7 +1854,7 @@ */ test: function () { // Set up request and listen for event - this.xhr = new XMLHttpRequest(); + if (!this.xhr) this.xhr = new XMLHttpRequest(); this.xhr.addEventListener("load", this.testHandler, false); this.xhr.addEventListener("error", this.testHandler, false); var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this); @@ -1263,6 +1876,22 @@ * @function */ send: function () { + var $ = this; + var folderObj = this.fileObj.folderObj; + if (folderObj && folderObj.waiting) { + this.xhr = new XMLHttpRequest(); + this.flowObj.on('_gotPathsInfo', function(_folderObj) { + if (_folderObj === folderObj) { + $._doSend(); + } + }); + return; + } + + this._doSend(); + }, + + _doSend: function () { var preprocess = this.flowObj.opts.preprocess; if (typeof preprocess === 'function') { switch (this.preprocessState) { @@ -1290,7 +1919,7 @@ var bytes = this.fileObj.file[func](this.startByte, this.endByte, this.fileObj.file.type); // Set up request and listen for event - this.xhr = new XMLHttpRequest(); + if (!this.xhr) this.xhr = new XMLHttpRequest(); this.xhr.upload.addEventListener('progress', this.progressHandler, false); this.xhr.addEventListener("load", this.doneHandler, false); this.xhr.addEventListener("error", this.doneHandler, false); @@ -1332,7 +1961,7 @@ } else { if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) { // HTTP 200, perfect - // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed. + // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed. return 'success'; } else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 || !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) { @@ -1525,6 +2154,17 @@ */ Flow.FlowFile = FlowFile; + /** + * HashMap constructor + * @type {HashMap} + */ + Flow.HashMap = HashMap; + /** + * FlowFolder constructor + * @type {FlowFolder} + */ + Flow.FlowFolder = FlowFolder; + /** * FlowFile constructor * @type {FlowChunk} diff --git a/test/fileAddSpec.js b/test/fileAddSpec.js index 222fce1a..ceec9e95 100644 --- a/test/fileAddSpec.js +++ b/test/fileAddSpec.js @@ -24,15 +24,19 @@ describe('fileAdd event', function() { it('should call filesAdded event', function() { var count = 0; - flow.on('filesAdded', function (files) { + var count2 = 0; + flow.on('filesAdded', function (files, parsedFiles) { count = files.length; + count2 = parsedFiles.length; }); flow.addFiles([ - new Blob(['file part']), - new Blob(['file 2 part']) + new File(['file part'], 'file1'), + new File(['file 2 part'], 'file2') ]); expect(count).toBe(2); + expect(count2).toBe(2); expect(flow.files.length).toBe(2); + expect(flow.parsedFiles.length).toBe(2); }); it('should validate fileAdded', function() { @@ -41,6 +45,7 @@ describe('fileAdd event', function() { }); flow.addFile(new Blob(['file part'])); expect(flow.files.length).toBe(0); + expect(flow.parsedFiles.length).toBe(0); }); it('should validate filesAdded', function() { @@ -49,6 +54,7 @@ describe('fileAdd event', function() { }); flow.addFile(new Blob(['file part'])); expect(flow.files.length).toBe(0); + expect(flow.parsedFiles.length).toBe(0); }); it('should validate fileAdded and filesAdded', function() { @@ -56,8 +62,8 @@ describe('fileAdd event', function() { return false; }); var valid = false; - flow.on('filesAdded', function (files) { - valid = files.length === 0; + flow.on('filesAdded', function (files, parsedFiles) { + valid = files.length === 0 && parsedFiles.length === 0; }); flow.addFile(new Blob(['file part'])); expect(valid).toBeTruthy(); diff --git a/test/setupSpec.js b/test/setupSpec.js index 91413e30..6f7943c2 100644 --- a/test/setupSpec.js +++ b/test/setupSpec.js @@ -21,6 +21,11 @@ describe('setup', function() { expect(flow.files.length).toBe(0); }); + it('parsedFiles should be empty', function() { + expect(flow.parsedFiles).toBeDefined(); + expect(flow.parsedFiles.length).toBe(0); + }); + it('events should be empty', function() { expect(flow.events).toBeDefined(); expect(Object.keys(flow.events).length).toBe(0);