Skip to content

Instantly share code, notes, and snippets.

@destos
Created February 18, 2014 15:48
Show Gist options
  • Select an option

  • Save destos/9073522 to your computer and use it in GitHub Desktop.

Select an option

Save destos/9073522 to your computer and use it in GitHub Desktop.
Angular Angular File upload lib multiple endpoint/file handler.
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)
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