Created
February 18, 2014 15:48
-
-
Save destos/9073522 to your computer and use it in GitHub Desktop.
Angular Angular File upload lib multiple endpoint/file handler.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| describe 'UploadList', -> | |
| beforeEach -> | |
| angular.mock.module('ca.uploads') | |
| angular.mock.module('angularLocalStorage.mock') | |
| beforeEach inject ($rootScope, $controller, $http, $httpBackend, $upload, $timeout, mockStore) -> | |
| @scope = $rootScope.$new() | |
| @httpBackend = $httpBackend | |
| @callbacks = | |
| success: sinon.stub() | |
| error: sinon.stub() | |
| # @mockUploadService = | |
| # create: sinon.stub().returns @callbacks | |
| @endpoints = | |
| none: | |
| field: 'image_local' | |
| url: '/upload-to/' | |
| @upload = $upload | |
| @ctrl = $controller("UploadList", {$scope: @scope, $http: $http, $upload: @upload, UploadEndpoints: @endpoints, $timeout: $timeout, storage: mockStore}) | |
| # always apply timeout for storage init | |
| $timeout.flush() | |
| afterEach inject ($timeout, mockStore) -> | |
| $timeout.verifyNoPendingTasks() | |
| mockStore.clearAll() | |
| it 'adds a blank files list to the scope', -> | |
| expect(@scope.files).to.exist.and.be.an('array').and.be.empty | |
| it 'defines default options', -> | |
| expect(@scope.options).to.exist.and.eql( | |
| endpoint: 'none' | |
| upload_on_add: false | |
| upload_limit: 6 | |
| ) | |
| describe '$scope.file_limit_reached()', -> | |
| it 'returns false when the length of $scope.files.length < $scope.options.upload_limit', -> | |
| @scope.options.upload_limit = 3 | |
| @scope.files = ['file1', 'file2'] | |
| expect(@scope.file_limit_reached()).to.be.false | |
| it 'returns true when the length of $scope.files.length >= $scope.options.upload_limit', -> | |
| @scope.options.upload_limit = 2 | |
| @scope.files = ['file1', 'file2'] | |
| expect(@scope.file_limit_reached(), 'when equal').to.be.true | |
| @scope.options.upload_limit = 1 | |
| expect(@scope.file_limit_reached(), 'when less').to.be.true | |
| it 'returns true when $scope.options.upload_limit == false', -> | |
| @scope.options.upload_limit = false | |
| expect(@scope.file_limit_reached()).to.be.false | |
| describe '$scope.add_file(file)', -> | |
| beforeEach -> | |
| @file_limit_reached = sinon.stub(@scope, 'file_limit_reached') | |
| @upload_file = sinon.stub(@scope, 'upload_file') | |
| it 'adds a new file to the files list with default options', -> | |
| @file_limit_reached.returns(false) | |
| file = | |
| name: 'filename.doc' | |
| data: 'adsndoiwnd' | |
| expect(@scope.files).to.have.length(0) | |
| returned = @scope.add_file(file) | |
| expect(returned).to.be.true | |
| expect(@scope.files).to.have.length(1) | |
| expect(@scope.files[0]).to.eql | |
| local_file: file | |
| progress: 0 | |
| in_progress: false | |
| uploaded: false | |
| failed: false | |
| error: false | |
| meta: {} | |
| expect(@file_limit_reached).calledOnce | |
| expect(@upload_file).not.called | |
| it 'does not add a file if file_limit_reached() returns true', -> | |
| @file_limit_reached.returns(true) | |
| file = | |
| name: 'filename.doc' | |
| data: 'adsndoiwnd' | |
| expect(@scope.files).to.have.length(0) | |
| returned = @scope.add_file(file) | |
| expect(returned).to.be.false | |
| expect(@scope.files).to.have.length(0) | |
| expect(@file_limit_reached).calledOnce | |
| expect(@upload_file).not.called | |
| it 'calls $scope.upload_file(file) when options.upload_on_add is true', -> | |
| @scope.options.upload_on_add = true | |
| file = | |
| name: 'filename.doc' | |
| called_obj = | |
| local_file: file | |
| progress: 0 | |
| in_progress: false | |
| uploaded: false | |
| failed: false | |
| error: false | |
| meta: {} | |
| returned = @scope.add_file(file) | |
| expect(returned).to.be.true | |
| expect(@upload_file).calledOnce.calledWith(called_obj) | |
| describe '$scope.remove_file(file)', -> | |
| it 'removes a file from the files list', -> | |
| file1 = | |
| name: 'file1.doc' | |
| file2 = | |
| name: 'file2.doc' | |
| file3 = | |
| name: 'file3.doc' | |
| @scope.add_file(file1) | |
| @scope.add_file(file2) | |
| @scope.add_file(file3) | |
| expect(@scope.files).to.have.length(3) | |
| # should remove the first file | |
| @scope.remove_file(@scope.files[0]) | |
| expect(@scope.files).to.have.length(2) | |
| it 'aborts the file if an upload is in progress', -> | |
| abort = sinon.spy() | |
| file = | |
| name: 'file1.doc' | |
| upload_file = sinon.stub(@scope, 'upload_file') | |
| @scope.add_file file | |
| expect(@scope.files).to.have.length(1) | |
| added_file_meta = @scope.files[0] | |
| @scope.upload_file(added_file_meta) | |
| # abort added during upload_file | |
| added_file_meta.abort = abort | |
| @scope.remove_file(added_file_meta) | |
| expect(abort).calledOnce | |
| expect(upload_file).calledOnce | |
| describe '$scope.uploadables', -> | |
| it 'checks all files and filters out all uploaded and in progress', -> | |
| file = | |
| name: 'filename.doc' | |
| data: 'adsndoiwnd' | |
| @scope.add_file(file) | |
| @scope.add_file(file) | |
| @scope.add_file(file) | |
| expect(@scope.files).to.have.length(3) | |
| @scope.files[0].uploaded = true | |
| @scope.files[1].in_progress = true | |
| @scope.files[2].failed = true | |
| @scope.files[2].local_file.name = 'special.doc' | |
| uppers = @scope.uploadables() | |
| expect(uppers).to.be.an('array') | |
| expect(uppers).to.have.length(1) | |
| expect(uppers[0].local_file.name).to.equal('special.doc') | |
| describe '$scope.upload_all_files', -> | |
| beforeEach -> | |
| @scope.files = ['file1', 'file2', 'file3'] | |
| @uploadables = sinon.stub(@scope, 'uploadables').returns(@scope.files) | |
| it 'calls $scope.uploadables and loops through the results calling $scope.upload_files', -> | |
| upload_file = sinon.stub(@scope, 'upload_file') | |
| expect(@uploadables).not.called | |
| @scope.upload_all_files() | |
| expect(@uploadables).calledOnce | |
| expect(upload_file).calledThrice | |
| expect(upload_file).calledWith('file1') | |
| expect(upload_file).calledWith('file2') | |
| expect(upload_file).calledWith('file3') | |
| it 'doesn\'t call $scope.uploadables if there are no files', -> | |
| @scope.files = [] | |
| expect(@uploadables).not.called | |
| @scope.upload_all_files() | |
| expect(@uploadables).not.called | |
| describe '$scope.upload_file', -> | |
| beforeEach -> | |
| @endpoints.rawr = | |
| field: 'amazing_image_local' | |
| url: '/upload-to/someplace/' | |
| extra: | |
| campaign_id: 'page.campaign' | |
| another: 'page.bool' | |
| @upload_call = sinon.stub(@upload, 'upload') | |
| @upload_return = | |
| abort: sinon.stub() | |
| progress: sinon.stub() | |
| success: sinon.stub() | |
| error: sinon.stub() | |
| @upload_call.returns @upload_return | |
| it 'should throw an error if no endpoint found', -> | |
| @scope.options.endpoint = 'blah' | |
| expect(@scope.upload_file).to.throw('no endpoint found') | |
| describe 'endpoints', -> | |
| it 'uses the endpoint\'s configuration for the request', -> | |
| @scope.options.endpoint = 'rawr' | |
| @scope.add_file | |
| name: 'file1.doc' | |
| data: 'data' | |
| @scope.upload_file(@scope.files[0]) | |
| expect(@upload_call).calledWith | |
| url: '/upload-to/someplace/' | |
| data: | |
| filename: 'file1.doc' | |
| file: | |
| name: 'file1.doc' | |
| data: 'data' | |
| fileFormDataName: 'amazing_image_local' | |
| # abort method is assigned to a property on the file for later use in #scope.remove_file | |
| expect(@scope.files[0].abort).to.equal(@upload_return.abort) | |
| it 'when defining an extra property will parse the string as an expression and add it to the sent data when found', -> | |
| @scope.options.endpoint = 'rawr' | |
| @scope.add_file | |
| name: 'file1.doc' | |
| data: 'data' | |
| # scoped model that our extra property is looking for | |
| @scope.page = | |
| campaign: 12 | |
| bool: false | |
| @scope.upload_file(@scope.files[0]) | |
| expect(@upload_call).calledWith | |
| url: '/upload-to/someplace/' | |
| data: | |
| campaign_id: 12 | |
| another: false | |
| filename: 'file1.doc' | |
| file: | |
| name: 'file1.doc' | |
| data: 'data' | |
| fileFormDataName: 'amazing_image_local' | |
| describe 'file states', -> | |
| beforeEach inject ($timeout) -> | |
| @scope.add_file | |
| name: 'file1.doc' | |
| data: 'data' | |
| @file = @scope.files[0] | |
| expect(@file).to.have.property('uploaded', false) | |
| expect(@file).to.have.property('in_progress', false) | |
| expect(@file).to.have.property('failed', false) | |
| expect(@file).to.have.property('progress', 0) | |
| @scope.upload_file(@file) | |
| it 'when queued', -> | |
| expect(@file).to.have.property('uploaded', false) | |
| expect(@file).to.have.property('in_progress', true) | |
| expect(@file).to.have.property('failed', false) | |
| it 'on success, data returned is assigned to file.meta', -> | |
| @upload_return.success.firstCall.args[0]('passed data') | |
| expect(@file).to.have.property('uploaded', true) | |
| expect(@file).to.have.property('in_progress', false) | |
| expect(@file).to.have.property('failed', false) | |
| expect(@file.meta).to.equal('passed data') | |
| it 'on failure, data returned is assigned to file.meta', -> | |
| @upload_return.error.firstCall.args[0]( | |
| 'image_local': ['an error occured', 'and another'] | |
| ) | |
| expect(@file).to.have.property('uploaded', false) | |
| expect(@file).to.have.property('in_progress', false) | |
| expect(@file).to.have.property('failed', true) | |
| expect(@file.error).to.equal('an error occured and another') | |
| it 'on progress, file\'s progress is correctly calculated', -> | |
| @upload_return.progress.firstCall.args[0]( | |
| loaded: 74 | |
| total: 100 | |
| ) | |
| expect(@file).to.have.property('progress', 74) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| angular.module('ca.uploads', ['ca.directives.utilities', 'angularFileUpload', 'angularLocalStorage']) | |
| .constant('UploadEndpoints', | |
| dispute_document: | |
| field: 'local_file' | |
| url: '/brands/api/reviews/disputes_document/create' | |
| extra: | |
| dispute: '$scope.review.dispute.review' | |
| review_receipt: | |
| field: 'image_local' | |
| url: '/reviews/upload-receipt/' | |
| ) | |
| .controller 'UploadList', ($window, $scope, $log, UploadEndpoints, $parse, $upload, storage, $timeout) -> | |
| ###* | |
| * exposed options object so functionality can be toggled from the interface | |
| * @type {Object} | |
| ### | |
| $scope.options = options = { | |
| endpoint: 'none' | |
| upload_on_add: false | |
| upload_limit: 6 | |
| } | |
| ###* | |
| * scopped list of files that can be displayed in the interface, stored to | |
| * localstorage/cookies so page refreshes dont clear uplaod references | |
| * @type {Array} | |
| ### | |
| $scope.files = [] | |
| $timeout -> | |
| storage.bind($scope, 'files', defaultValue: [], storeName: "#{options.endpoint}_files") | |
| ###* | |
| * returns true/false depending on whether or not the upload_limit | |
| * has been reached. | |
| * Also returns false when upload_limit is false, infinite uploads! | |
| * @return {booleen} | |
| ### | |
| $scope.file_limit_reached = -> | |
| if angular.isNumber(options.upload_limit) then $scope.files.length >= options.upload_limit else !(options.upload_limit is false) | |
| create_file_object = (file) -> | |
| local_file: file | |
| progress: 0 | |
| in_progress: false | |
| uploaded: false | |
| failed: false | |
| error: false | |
| meta: {} | |
| ###* | |
| * Adds a file to the scopped list of files | |
| * will automatically upload the file if options.upload_on_add is true | |
| * @param {File} file - Should be a native browser file object | |
| * @return {null} | |
| ### | |
| add_file = (file) -> | |
| # debugger | |
| throw new Error('no file preset') if not file | |
| # untested | |
| if $scope.file_limit_reached() | |
| # need some sorta feedback here | |
| return false | |
| $scope.$apply -> | |
| new_file = create_file_object(file) | |
| $scope.files.push new_file | |
| if options.upload_on_add | |
| $scope.upload_file(new_file) | |
| return true | |
| ###* | |
| * Loop through an array of files and passes them onto add_file() | |
| * @param {array} files - an array of files | |
| ### | |
| add_files = (files) -> | |
| add_file(file) for file in files | |
| # attempt at tracking exceptions when adding a file | |
| if $window.zone | |
| $window.zone.fork(Zone.longStackTraceZone).run -> | |
| $scope.add_file = add_file | |
| $scope.add_files = add_files | |
| else | |
| $scope.add_file = add_file | |
| $scope.add_files = add_files | |
| ###* | |
| * removes a file from scopped files, and attempts to cancel the upload request | |
| * @param {File} file - Should be a native browser file object | |
| * @return {null} | |
| ### | |
| $scope.remove_file = (file) -> | |
| # abort upload request if running | |
| file.abort?() | |
| index = $scope.files.indexOf(file) | |
| $scope.files.splice(index, 1) | |
| return | |
| ###* | |
| * returns an array of files that meet the conditions of not being uploaded and | |
| * not in progress | |
| * @return {Array} - trimmed copy of $scope.files | |
| ### | |
| $scope.uploadables = -> | |
| _.where($scope.files, {uploaded: false, in_progress: false}) | |
| ###* | |
| * uploads the file | |
| * @param {File} file - Should be a native browser file object | |
| * @return {null} | |
| ### | |
| $scope.upload_file = (file) -> | |
| endpoint = UploadEndpoints[options.endpoint] | |
| throw Error('no endpoint found') if not endpoint | |
| file.in_progress = true | |
| file.error = false | |
| # create data to send | |
| data = | |
| filename: file.local_file.name | |
| if endpoint.extra | |
| for field, expression of endpoint.extra | |
| value = $parse(expression)($scope) | |
| if value isnt undefined then data[field] = value | |
| doc = $upload.upload | |
| url: endpoint.url | |
| data: data | |
| file: file.local_file | |
| fileFormDataName: endpoint.field | |
| # allow file to abort | |
| file.abort = doc.abort | |
| doc.progress (e) -> | |
| $scope.$apply -> | |
| file.progress = Math.floor((e.loaded/e.total)*100) | |
| doc.success((data) -> | |
| file.in_progress = false | |
| file.uploaded = true | |
| file.failed = false | |
| file.meta = data | |
| # TODO: need to impliment a hook for pushing the data to dispute document in the other use case | |
| # $scope.review.dispute.documents.push(data) | |
| ) | |
| doc.error((data) -> | |
| $log.warn file, (data.detail || data) | |
| try | |
| if (errors = data[endpoint.field]).length | |
| file.error = errors.join(' ') | |
| file.in_progress = false | |
| file.uploaded = false | |
| file.failed = true | |
| ) | |
| return | |
| ###* | |
| * find all un-uploaded files and send them on their way | |
| * @return {null} | |
| ### | |
| $scope.upload_all_files = -> | |
| return if not $scope.files.length | |
| # only try to upload anything not in progress and non-uploaded | |
| for file in $scope.uploadables() | |
| $scope.upload_file(file) | |
| return | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment