diff --git a/app/src/common/services/image/imageService.spec.ts b/app/src/common/services/image/imageService.spec.ts index d0230fc3..8ecfc30c 100644 --- a/app/src/common/services/image/imageService.spec.ts +++ b/app/src/common/services/image/imageService.spec.ts @@ -3,8 +3,13 @@ namespace common.services.image { let seededChance = new Chance(1), imageService:common.services.image.ImageService, $httpBackend:ng.IHttpBackendService, + $http:ng.IHttpService, $q:ng.IQService, - ngRestAdapter:NgRestAdapter.INgRestAdapterService + ngRestAdapter:NgRestAdapter.INgRestAdapterService, + Upload:ng.angularFileUpload.IUploadService, + paginationService:common.services.pagination.PaginationService, + $rootScope:ng.IRootScopeService, + $timeout: ng.ITimeoutService ; @@ -43,13 +48,18 @@ namespace common.services.image { module('app'); - inject((_$httpBackend_, _imageService_, _$q_, _ngRestAdapter_) => { + inject((_$httpBackend_, _imageService_, _$q_, _ngRestAdapter_, _Upload_, _paginationService_, _$http_, _$rootScope_, _$timeout_) => { if (!imageService) { // Don't rebind, so each test gets the singleton $httpBackend = _$httpBackend_; imageService = _imageService_; $q = _$q_; ngRestAdapter = _ngRestAdapter_; + Upload = _Upload_; + paginationService = _paginationService_; + $http = _$http_; + $rootScope = _$rootScope_; + $timeout = _$timeout_ } }); @@ -82,10 +92,7 @@ namespace common.services.image { apiKey: 'abc-123' }); - $httpBackend.expectPOST('https://api.cloudinary.com/v1_1/spira/image/upload', (req) => { - //console.log('req', req); - return true; //@todo set expectation - }).respond({ + $httpBackend.expectPOST('https://api.cloudinary.com/v1_1/spira/image/upload').respond({ //as the request is a FormObject, for some reason httpbackend won't passthrough the request body so assertions can't be added bytes: 517112, created_at: "2015-09-07T06:24:16Z", etag: "67131e8d70ecb06a8400554f5eef8c77", @@ -117,6 +124,101 @@ namespace common.services.image { expect(uploadPromise).eventually.to.be.fulfilled; expect(uploadPromise).eventually.to.be.instanceOf(common.models.Image); + (imageService).ngRestAdapter.uuid.restore(); + + }); + + + it.only('should notify progress on upload', () => { + + let fixedImageId = chance.guid(), + ts = moment().unix(), + image = fixtures.imageFile; + + $httpBackend.expectGET('/api/cloudinary/signature?public_id='+fixedImageId+'×tamp='+ts+'&type=upload').respond({ + signature: 'this-is-the-signature', + apiKey: 'abc-123' + }); + + + sinon.stub((imageService).ngRestAdapter, 'uuid').returns(fixedImageId); //fix the value of the ngRestAdapter.uuid() method + let mockUploadDeferred = $q.defer(); + (mockUploadDeferred.promise).progress = (callback) => { + return mockUploadDeferred.promise.then(null, null, callback); + }; + sinon.stub((Upload), 'upload').returns(mockUploadDeferred.promise); + + let mockedImageService = new ImageService(Upload, $q, ngRestAdapter, $http, paginationService, $timeout); + + let progressSpy = sinon.spy(); + let uploadPromise = mockedImageService.uploadImage({ + file: image, + alt: 'image test', + }).then(null, null, progressSpy); + + + $timeout.flush(); + + $httpBackend.flush(1); + + + mockUploadDeferred.notify({ + loaded: 5, + total: 10, + }); + + mockUploadDeferred.notify({ + loaded: 8, + total: 10, + }); + + $httpBackend.expectPUT('/api/images/'+fixedImageId, { + imageId: fixedImageId, + version: ts, + alt: "image test", + title: "image test", + format:'jpg', + }).respond(204); + + mockUploadDeferred.resolve({ + data: { + bytes: 517112, + created_at: "2015-09-07T06:24:16Z", + etag: "67131e8d70ecb06a8400554f5eef8c77", + format: "jpg", + height: 1632, + original_filename: image.name, + public_id: fixedImageId, + resource_type: "image", + secure_url: `https://res.cloudinary.com/spira/image/upload/v${ts}/${fixedImageId}.jpg`, + signature: "3c2255855ac023ff1bde0faa454667e9494152a6", + tags: [], + type: "upload", + url: `http://res.cloudinary.com/spira/image/upload/v${ts}/${fixedImageId}.jpg`, + version: ts, + width: 2448, + } + }); //call the progress callback function + + + $httpBackend.flush(1); + + $rootScope.$apply(); + + uploadPromise.then(() => { + progressSpy.should.have.been.calledWith(sinon.match({ event: 'cloudinary_signature' })); + progressSpy.should.have.been.calledWith(sinon.match({ event: 'cloudinary_upload', progressValue: 50 })); + progressSpy.should.have.been.calledWith(sinon.match({ event: 'cloudinary_upload', progressValue: 80 })); + progressSpy.should.have.been.calledWith(sinon.match({ event: 'api_link' })); + progressSpy.should.have.callCount(4); + }); + + expect(uploadPromise).eventually.to.be.fulfilled; + expect(uploadPromise).eventually.to.be.instanceOf(common.models.Image); + + + (imageService).ngRestAdapter.uuid.restore(); + (Upload).upload.restore(); }); diff --git a/app/src/common/services/image/imageService.ts b/app/src/common/services/image/imageService.ts index 3410eebe..615ac18e 100644 --- a/app/src/common/services/image/imageService.ts +++ b/app/src/common/services/image/imageService.ts @@ -67,7 +67,8 @@ namespace common.services.image { private $q:ng.IQService, private ngRestAdapter:NgRestAdapter.INgRestAdapterService, private $http:ng.IHttpService, - private paginationService:common.services.pagination.PaginationService) { + private paginationService:common.services.pagination.PaginationService, + private $timeout:ng.ITimeoutService) { } @@ -136,6 +137,7 @@ namespace common.services.image { progressName: 'Upload progress', progressValue: progressPercentage, }); + }); uploadPromise.finally(() => { @@ -228,9 +230,13 @@ namespace common.services.image { .join('&') ; - deferredUploadProgress.notify({ - event: 'cloudinary_signature', - message: "Signing request for cloudinary", + //we wrap the notification in a timeout so the calling controller has a chance to register a notification listener before the first event is emitted + this.$timeout(() => { + deferredUploadProgress.notify({ + event: 'cloudinary_signature', + message: "Signing request for cloudinary", + }); + }); return this.ngRestAdapter.get('/cloudinary/signature?' + signableString)