From 7456e84c7e4f88e4bed84237b22bdc2ad4ee8a18 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 20 Jan 2016 10:44:51 +0100 Subject: [PATCH] refresh attachments when the user drags in a wysiwyg --- app/coffee/modules/common/components.coffee | 20 +- .../attachment/attachment.controller.coffee | 9 +- .../attachment.controller.spec.coffee | 19 +- .../attachments-full.controller.coffee | 106 ++------ .../attachments-full.controller.spec.coffee | 227 +++++------------- .../attachments-full/attachments-full.jade | 11 +- .../attachments-full.service.coffee | 131 ++++++++++ .../attachments-full.service.spec.coffee | 215 +++++++++++++++++ 8 files changed, 459 insertions(+), 279 deletions(-) create mode 100644 app/modules/components/attachments-full/attachments-full.service.coffee create mode 100644 app/modules/components/attachments-full/attachments-full.service.spec.coffee diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index 8faf23ab..c5e61b53 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -627,7 +627,7 @@ module.directive("tgEditableDescription", [ -EditableWysiwyg = (attachmentsService) -> +EditableWysiwyg = (attachmentsService, attachmentsFullService) -> link = ($scope, $el, $attrs, $model) -> isInEditMode = -> @@ -637,19 +637,11 @@ EditableWysiwyg = (attachmentsService) -> uploadFile = (file, type) -> return if !attachmentsService.validate(file) - attachmentsService.upload( - file, - $model.$modelValue.id, - $model.$modelValue.project, - type - ).then (result) -> - if taiga.isImage(result.get('name')) - url = '![' + result.get('name') + '](' + result.get('url') + ')' + return attachmentsFullService.addAttachment($model.$modelValue.project, $model.$modelValue.id, type, file).then (result) -> + if taiga.isImage(result.getIn(['file', 'name'])) + return '![' + result.getIn(['file', 'name']) + '](' + result.getIn(['file', 'url']) + ')' else - url = '[' + result.get('name') + '](' + result.get('url') + ')' - - - return url + return '[' + result.getIn(['file', 'name']) + '](' + result.getIn(['file', 'url']) + ')' $el.on 'dragover', (e) -> textarea = $el.find('textarea').focus() @@ -694,7 +686,7 @@ EditableWysiwyg = (attachmentsService) -> require: "ngModel" } -module.directive("tgEditableWysiwyg", ["tgAttachmentsService", EditableWysiwyg]) +module.directive("tgEditableWysiwyg", ["tgAttachmentsService", "tgAttachmentsFullService", EditableWysiwyg]) ############################################################################# diff --git a/app/modules/components/attachment/attachment.controller.coffee b/app/modules/components/attachment/attachment.controller.coffee index ebbbac03..487f107e 100644 --- a/app/modules/components/attachment/attachment.controller.coffee +++ b/app/modules/components/attachment/attachment.controller.coffee @@ -28,21 +28,22 @@ class AttachmentController @.form.description = @.attachment.getIn(['file', 'description']) @.form.is_deprecated = @.attachment.get(['file', 'is_deprecated']) - @.loading = false - @.title = @translate.instant("ATTACHMENT.TITLE", { fileName: @.attachment.get('name'), date: moment(@.attachment.get('created_date')).format(@translate.instant("ATTACHMENT.DATE")) }) editMode: (mode) -> - @.attachment = @.attachment.set('editable', mode) + attachment = @.attachment.set('editable', mode) + @.onUpdate({attachment: attachment}) delete: () -> @.onDelete({attachment: @.attachment}) save: () -> - @.attachment = @.attachment.set('loading', true) + attachment = @.attachment.set('loading', true) + + @.onUpdate({attachment: attachment}) attachment = @.attachment.merge({ editable: false, diff --git a/app/modules/components/attachment/attachment.controller.spec.coffee b/app/modules/components/attachment/attachment.controller.spec.coffee index e678de1d..2b7b70c8 100644 --- a/app/modules/components/attachment/attachment.controller.spec.coffee +++ b/app/modules/components/attachment/attachment.controller.spec.coffee @@ -72,10 +72,18 @@ describe "AttachmentController", -> attachment : attachment }) - ctrl.editable = false + ctrl.onUpdate = sinon.spy() + + onUpdate = sinon.match (value) -> + value = value.attachment.toJS() + + return value.editable + + , "onUpdate" + ctrl.editMode(true) - expect(ctrl.attachment.get('editable')).to.be.true + expect(ctrl.onUpdate).to.be.calledWith(onUpdate) it "delete", () -> attachment = Immutable.fromJS({ @@ -119,6 +127,12 @@ describe "AttachmentController", -> ctrl.onUpdate = sinon.spy() + onUpdateLoading = sinon.match (value) -> + value = value.attachment.toJS() + + return value.loading + , "onUpdateLoading" + onUpdate = sinon.match (value) -> value = value.attachment.toJS() @@ -137,4 +151,5 @@ describe "AttachmentController", -> attachment = ctrl.attachment.toJS() + expect(ctrl.onUpdate).to.be.calledWith(onUpdateLoading) expect(ctrl.onUpdate).to.be.calledWith(onUpdate) diff --git a/app/modules/components/attachments-full/attachments-full.controller.coffee b/app/modules/components/attachments-full/attachments-full.controller.coffee index 5aa190d4..30055c87 100644 --- a/app/modules/components/attachments-full/attachments-full.controller.coffee +++ b/app/modules/components/attachments-full/attachments-full.controller.coffee @@ -21,82 +21,46 @@ sizeFormat = @.taiga.sizeFormat class AttachmentsFullController @.$inject = [ - "tgAttachmentsService", - "$rootScope", "$translate", "$tgConfirm", "$tgConfig", - "$tgStorage" + "$tgStorage", + "tgAttachmentsFullService" ] - constructor: (@attachmentsService, @rootScope, @translate, @confirm, @config, @storage) -> - @.deprecatedsVisible = false - @.uploadingAttachments = [] - + constructor: (@translate, @confirm, @config, @storage, @attachmentsFullService) -> @.mode = @storage.get('attachment-mode', 'list') @.maxFileSize = @config.get("maxUploadFileSize", null) @.maxFileSize = sizeFormat(@.maxFileSize) if @.maxFileSize @.maxFileSizeMsg = if @.maxFileSize then @translate.instant("ATTACHMENT.MAX_UPLOAD_SIZE", {maxFileSize: @.maxFileSize}) else "" - loadAttachments: -> - @attachmentsService.list(@.type, @.objId, @.projectId).then (files) => - @.attachments = files.map (file) -> - attachment = Immutable.Map() + taiga.defineImmutableProperty @, 'attachments', () => return @attachmentsFullService.attachments + taiga.defineImmutableProperty @, 'deprecatedsCount', () => return @attachmentsFullService.deprecatedsCount + taiga.defineImmutableProperty @, 'attachmentsVisible', () => return @attachmentsFullService.attachmentsVisible + taiga.defineImmutableProperty @, 'deprecatedsVisible', () => return @attachmentsFullService.deprecatedsVisible - return attachment.merge({ - loading: false, - editable: false, - file: file - }) + uploadingAttachments: () -> + return @attachmentsFullService.uploadingAttachments - @.generate() + addAttachment: (file) -> + editable = (@.mode == 'list') + + @attachmentsFullService.addAttachment(@.projectId, @.objId, @.type, file, editable) setMode: (mode) -> @.mode = mode @storage.set('attachment-mode', mode) - generate: () -> - @.deprecatedsCount = @.attachments.count (it) -> it.getIn(['file', 'is_deprecated']) - - if @.deprecatedsVisible - @.attachmentsVisible = @.attachments - else - @.attachmentsVisible = @.attachments.filter (it) -> !it.getIn(['file', 'is_deprecated']) - toggleDeprecatedsVisible: () -> - @.deprecatedsVisible = !@.deprecatedsVisible - @.generate() + @attachmentsFullService.toggleDeprecatedsVisible() - addAttachment: (file, editable = true) -> - return new Promise (resolve, reject) => - if @attachmentsService.validate(file) - @.uploadingAttachments.push(file) + addAttachments: (files) -> + _.forEach files, (file) => @.addAttachment(file) - - promise = @attachmentsService.upload(file, @.objId, @.projectId, @.type) - promise.then (file) => - @.uploadingAttachments = @.uploadingAttachments.filter (uploading) -> - return uploading.name != file.get('name') - - attachment = Immutable.Map() - - attachment = attachment.merge({ - file: file, - editable: editable, - loading: false - }) - - @.attachments = @.attachments.push(attachment) - @.generate() - @rootScope.$broadcast("attachment:create") - resolve(@.attachments) - else - reject(file) - - addAttachments: (files, editable) -> - _.forEach files, (file) => @.addAttachment(file, editable) + loadAttachments: -> + @attachmentsFullService.loadAttachments(@.type, @.objId, @.projectId) deleteAttachment: (toDeleteAttachment) -> title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT") @@ -111,40 +75,14 @@ class AttachmentsFullController @confirm.notify("error", null, message) askResponse.finish(false) - onSuccess = () => - @.attachments = @.attachments.filter (attachment) -> attachment != toDeleteAttachment - @.generate() + onSuccess = () => askResponse.finish() - askResponse.finish() - - return @attachmentsService.delete(@.type, toDeleteAttachment.getIn(['file', 'id'])).then(onSuccess, onError) + @attachmentsFullService.deleteAttachment(toDeleteAttachment, @.type).then(onSuccess, onError) reorderAttachment: (attachment, newIndex) -> - oldIndex = @.attachments.findIndex (it) -> it == attachment - return if oldIndex == newIndex - - attachments = @.attachments.remove(oldIndex) - attachments = attachments.splice(newIndex, 0, attachment) - attachments = attachments.map (x, i) -> x.setIn(['file', 'order'], i + 1) - - promises = attachments.map (attachment) => - patch = {order: attachment.getIn(['file', 'order'])} - - return @attachmentsService.patch(attachment.getIn(['file', 'id']), @.type, patch) - - return Promise.all(promises.toJS()).then () => - @.attachments = attachments - @.generate() + @attachmentsFullService.reorderAttachment(@.type, attachment, newIndex) updateAttachment: (toUpdateAttachment) -> - index = @.attachments.findIndex (attachment) -> - return attachment.getIn(['file', 'id']) == toUpdateAttachment.getIn(['file', 'id']) - oldAttachment = @.attachments.get(index) - - patch = taiga.patch(oldAttachment.get('file'), toUpdateAttachment.get('file')) - - return @attachmentsService.patch(toUpdateAttachment.getIn(['file', 'id']), @.type, patch).then () => - @.attachments = @.attachments.set(index, toUpdateAttachment) - @.generate() + @attachmentsFullService.updateAttachment(toUpdateAttachment, @.type) angular.module("taigaComponents").controller("AttachmentsFull", AttachmentsFullController) diff --git a/app/modules/components/attachments-full/attachments-full.controller.spec.coffee b/app/modules/components/attachments-full/attachments-full.controller.spec.coffee index f3f528de..e45321af 100644 --- a/app/modules/components/attachments-full/attachments-full.controller.spec.coffee +++ b/app/modules/components/attachments-full/attachments-full.controller.spec.coffee @@ -22,13 +22,6 @@ describe "AttachmentsController", -> $controller = null mocks = {} - _mockAttachmentsService = -> - mocks.attachmentsService = { - upload: sinon.stub() - } - - $provide.value("tgAttachmentsService", mocks.attachmentsService) - _mockConfirm = -> mocks.confirm = {} @@ -55,21 +48,20 @@ describe "AttachmentsController", -> $provide.value("$tgStorage", mocks.storage) - _mockRootScope = -> - mocks.rootScope = {} + _mockAttachmetsFullService = -> + mocks.attachmentsFullService = {} - $provide.value("$rootScope", mocks.rootScope) + $provide.value("tgAttachmentsFullService", mocks.attachmentsFullService) _mocks = -> module (_$provide_) -> $provide = _$provide_ - _mockAttachmentsService() _mockConfirm() _mockTranslate() _mockConfig() _mockStorage() - _mockRootScope() + _mockAttachmetsFullService() return null @@ -86,100 +78,31 @@ describe "AttachmentsController", -> _setup() - it "generate, refresh deprecated counter", () -> - attachments = Immutable.fromJS([ - { - file: { - is_deprecated: false - } - }, - { - file: { - is_deprecated: true - } - }, - { - file: { - is_deprecated: true - } - }, - { - file: { - is_deprecated: false - } - }, - { - file: { - is_deprecated: true - } - } - ]) - - ctrl = $controller("AttachmentsFull") - - ctrl.attachments = attachments - - ctrl.generate() - - expect(ctrl.deprecatedsCount).to.be.equal(3) - it "toggle deprecated visibility", () -> + + mocks.attachmentsFullService.toggleDeprecatedsVisible = sinon.spy() + ctrl = $controller("AttachmentsFull") - ctrl.deprecatedsVisible = false - - ctrl.generate = sinon.spy() - ctrl.toggleDeprecatedsVisible() - expect(ctrl.deprecatedsVisible).to.be.true - expect(ctrl.generate).to.be.calledOnce + expect(mocks.attachmentsFullService.toggleDeprecatedsVisible).to.be.calledOnce - describe "add attachments", () -> - it "valid attachment", (done) -> - file = Immutable.fromJS({ - file: {}, - name: 'test', - size: 3000 - }) + it "add attachment", () -> + mocks.attachmentsFullService.addAttachment = sinon.spy() - mocks.attachmentsService.validate = sinon.stub() - mocks.attachmentsService.validate.withArgs(file).returns(true) + ctrl = $controller("AttachmentsFull") - mocks.attachmentsService.upload = sinon.stub() - mocks.attachmentsService.upload.promise().resolve(file) + file = Immutable.Map() - mocks.rootScope.$broadcast = sinon.spy() + ctrl.projectId = 3 + ctrl.objId = 30 + ctrl.type = 'us' + ctrl.mode = 'list' - ctrl = $controller("AttachmentsFull") - ctrl.attachments = Immutable.List() + ctrl.addAttachment(file) - ctrl.addAttachment(file).then () -> - expect(mocks.rootScope.$broadcast).have.been.calledWith('attachment:create') - expect(ctrl.attachments.count()).to.be.equal(1) - done() - - it "invalid attachment", () -> - file = Immutable.fromJS({ - file: {}, - name: 'test', - size: 3000 - }) - - mocks.attachmentsService.validate = sinon.stub() - mocks.attachmentsService.validate.withArgs(file).returns(false) - - mocks.attachmentsService.upload = sinon.stub() - mocks.attachmentsService.upload.promise().resolve(file) - - mocks.rootScope.$broadcast = sinon.spy() - - ctrl = $controller("AttachmentsFull") - - ctrl.attachments = Immutable.List() - - ctrl.addAttachment(file).then null, () -> - expect(ctrl.attachments.count()).to.be.equal(0) + expect(mocks.attachmentsFullService.addAttachment).to.have.been.calledWith(3, 30, 'us', file, true) it "add attachments", () -> ctrl = $controller("AttachmentsFull") @@ -199,6 +122,11 @@ describe "AttachmentsController", -> describe "deleteattachments", () -> it "success attachment", (done) -> + deleteFile = Immutable.Map() + + mocks.attachmentsFullService.deleteAttachment = sinon.stub() + mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().resolve() + askResponse = { finish: sinon.spy() } @@ -209,37 +137,20 @@ describe "AttachmentsController", -> mocks.confirm.askOnDelete = sinon.stub() mocks.confirm.askOnDelete.withArgs('title', 'message').promise().resolve(askResponse) - mocks.attachmentsService.delete = sinon.stub() - mocks.attachmentsService.delete.withArgs('us', 2).promise().resolve() - ctrl = $controller("AttachmentsFull") ctrl.type = 'us' - ctrl.generate = sinon.spy() - ctrl.attachments = Immutable.fromJS([ - { - file: {id: 1} - }, - { - file: {id: 2} - }, - { - file: {id: 3} - }, - { - file: {id: 4} - } - ]) - - deleteFile = ctrl.attachments.get(1) ctrl.deleteAttachment(deleteFile).then () -> - expect(ctrl.generate).have.been.calledOnce - expect(ctrl.attachments.size).to.be.equal(3) expect(askResponse.finish).have.been.calledOnce done() it "error attachment", (done) -> + deleteFile = Immutable.Map() + + mocks.attachmentsFullService.deleteAttachment = sinon.stub() + mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().reject() + askResponse = { finish: sinon.spy() } @@ -253,76 +164,52 @@ describe "AttachmentsController", -> mocks.confirm.notify = sinon.spy() - mocks.attachmentsService.delete = sinon.stub() - mocks.attachmentsService.delete.promise().reject() - ctrl = $controller("AttachmentsFull") ctrl.type = 'us' - ctrl.generate = sinon.spy() - ctrl.attachments = Immutable.fromJS([ - { - file: {id: 1} - }, - { - file: {id: 2} - }, - { - file: {id: 3} - }, - { - file: {id: 4} - } - ]) - - deleteFile = ctrl.attachments.get(1) ctrl.deleteAttachment(deleteFile).then () -> - expect(ctrl.attachments.size).to.be.equal(4) expect(askResponse.finish.withArgs(false)).have.been.calledOnce expect(mocks.confirm.notify.withArgs('error', null, 'error')) done() - it "reorder attachments", (done) -> - attachments = Immutable.fromJS([ - {file: {id: 0, is_deprecated: false, order: 0}}, - {file: {id: 1, is_deprecated: true, order: 1}}, - {file: {id: 2, is_deprecated: true, order: 2}}, - {file: {id: 3, is_deprecated: false, order: 3}}, - {file: {id: 4, is_deprecated: true, order: 4}} - ]) - - mocks.attachmentsService.patch = sinon.stub() - mocks.attachmentsService.patch.promise().resolve() + it "loadAttachments", () -> + mocks.attachmentsFullService.loadAttachments = sinon.spy() ctrl = $controller("AttachmentsFull") + ctrl.projectId = 3 + ctrl.objId = 30 ctrl.type = 'us' - ctrl.attachments = attachments - ctrl.reorderAttachment(attachments.get(1), 0).then () -> - expect(ctrl.attachments.get(0)).to.be.equal(attachments.get(1)) - done() + ctrl.loadAttachments() + + expect(mocks.attachmentsFullService.loadAttachments).to.have.been.calledWith('us', 30, 3) + + it "reorder attachments", () -> + mocks.attachmentsFullService.reorderAttachment = sinon.spy() + + ctrl = $controller("AttachmentsFull") + + file = Immutable.Map() + + ctrl.projectId = 3 + ctrl.objId = 30 + ctrl.type = 'us' + + ctrl.reorderAttachment(file, 5) + + expect(mocks.attachmentsFullService.reorderAttachment).to.have.been.calledWith('us', file, 5) it "update attachment", () -> - attachments = Immutable.fromJS([ - {file: {id: 0, is_deprecated: false, order: 0}}, - {file: {id: 1, is_deprecated: true, order: 1}}, - {file: {id: 2, is_deprecated: true, order: 2}}, - {file: {id: 3, is_deprecated: false, order: 3}}, - {file: {id: 4, is_deprecated: true, order: 4}} - ]) - - attachment = attachments.get(1) - attachment = attachment.setIn(['file', 'is_deprecated'], false) - - mocks.attachmentsService.patch = sinon.stub() - mocks.attachmentsService.patch.withArgs(1, 'us', {is_deprecated: false}).promise().resolve() + mocks.attachmentsFullService.updateAttachment = sinon.spy() ctrl = $controller("AttachmentsFull") - ctrl.type = 'us' - ctrl.attachments = attachments + file = Immutable.Map() - ctrl.updateAttachment(attachment).then () -> - expect(ctrl.attachments.get(1).toJS()).to.be.eql(attachment.toJS()) + ctrl.type = 'us' + + ctrl.updateAttachment(file, 5) + + expect(mocks.attachmentsFullService.updateAttachment).to.have.been.calledWith(file, 'us') diff --git a/app/modules/components/attachments-full/attachments-full.jade b/app/modules/components/attachments-full/attachments-full.jade index 13a81dd3..eab3c84f 100644 --- a/app/modules/components/attachments-full/attachments-full.jade +++ b/app/modules/components/attachments-full/attachments-full.jade @@ -34,7 +34,8 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)") multiple="multiple", tg-file-change="vm.addAttachments(files, true)" ) - .attachments-empty(ng-if="!vm.attachments.size") + + .attachments-empty(ng-if="!vm.attachments.size && !vm.uploadingAttachments().length") div {{'ATTACHMENT.DROP' | translate}} .attachment-list.sortable(ng-if="vm.mode == 'list'") div(tg-attachments-sortable="vm.reorderAttachment(attachment, index)") @@ -49,13 +50,13 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)") type="vm.type" ) - .single-attachment(ng-repeat="file in vm.uploadingAttachments") + .single-attachment(ng-repeat="file in vm.uploadingAttachments()") .attachment-name span.icon include ../../../svg/attachment.svg - span {{attachment.get('name')}} + span {{file.name}} .attachment-size - span {{attachment.get('size') | sizeFormat}} + span {{file.size | sizeFormat}} .attachment-comments span {{file.progressMessage}} @@ -88,7 +89,7 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)") on-update="vm.updateAttachment(attachment)", type="vm.type" ) - .single-attachment(ng-repeat="file in vm.uploadingAttachments") + .single-attachment(ng-repeat="file in vm.uploadingAttachments()") .loading-container img.loading-spinner( src="/#{v}/svg/spinner-circle.svg", diff --git a/app/modules/components/attachments-full/attachments-full.service.coffee b/app/modules/components/attachments-full/attachments-full.service.coffee new file mode 100644 index 00000000..625b8672 --- /dev/null +++ b/app/modules/components/attachments-full/attachments-full.service.coffee @@ -0,0 +1,131 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: attachments-full.service.coffee +### + +class AttachmentsFullService extends taiga.Service + @.$inject = [ + "tgAttachmentsService", + "$rootScope" + ] + + constructor: (@attachmentsService, @rootScope) -> + @._attachments = Immutable.List() + @._deprecatedsCount = 0 + @._attachmentsVisible = Immutable.List() + @._deprecatedsVisible = false + @.uploadingAttachments = [] + + taiga.defineImmutableProperty @, 'attachments', () => return @._attachments + taiga.defineImmutableProperty @, 'deprecatedsCount', () => return @._deprecatedsCount + taiga.defineImmutableProperty @, 'attachmentsVisible', () => return @._attachmentsVisible + taiga.defineImmutableProperty @, 'deprecatedsVisible', () => return @._deprecatedsVisible + + toggleDeprecatedsVisible: () -> + @._deprecatedsVisible = !@._deprecatedsVisible + @.regenerate() + + regenerate: () -> + @._deprecatedsCount = @._attachments.count (it) -> it.getIn(['file', 'is_deprecated']) + + if @._deprecatedsVisible + @._attachmentsVisible = @._attachments + else + @._attachmentsVisible = @._attachments.filter (it) -> !it.getIn(['file', 'is_deprecated']) + + addAttachment: (projectId, objId, type, file, editable = true) -> + return new Promise (resolve, reject) => + if @attachmentsService.validate(file) + @.uploadingAttachments.push(file) + + promise = @attachmentsService.upload(file, objId, projectId, type) + promise.then (file) => + @.uploadingAttachments = @.uploadingAttachments.filter (uploading) -> + return uploading.name != file.get('name') + + attachment = Immutable.Map() + + attachment = attachment.merge({ + file: file, + editable: editable, + loading: false + }) + + @._attachments = @._attachments.push(attachment) + + @.regenerate() + + @rootScope.$broadcast("attachment:create") + + resolve(attachment) + else + reject(file) + + loadAttachments: (type, objId, projectId)-> + @attachmentsService.list(type, objId, projectId).then (files) => + @._attachments = files.map (file) -> + attachment = Immutable.Map() + + return attachment.merge({ + loading: false, + editable: false, + file: file + }) + + @.regenerate() + + deleteAttachment: (toDeleteAttachment, type) -> + onSuccess = () => + @._attachments = @._attachments.filter (attachment) -> attachment != toDeleteAttachment + + @.regenerate() + + return @attachmentsService.delete(type, toDeleteAttachment.getIn(['file', 'id'])).then(onSuccess) + + reorderAttachment: (type, attachment, newIndex) -> + oldIndex = @.attachments.findIndex (it) -> it == attachment + return if oldIndex == newIndex + + attachments = @.attachments.remove(oldIndex) + attachments = attachments.splice(newIndex, 0, attachment) + attachments = attachments.map (x, i) -> x.setIn(['file', 'order'], i + 1) + + promises = [] + attachments.forEach (attachment) => + patch = {order: attachment.getIn(['file', 'order'])} + + promises.push @attachmentsService.patch(attachment.getIn(['file', 'id']), type, patch) + + return Promise.all(promises).then () => + @._attachments = attachments + + @.regenerate() + + updateAttachment: (toUpdateAttachment, type) -> + index = @._attachments.findIndex (attachment) -> + return attachment.getIn(['file', 'id']) == toUpdateAttachment.getIn(['file', 'id']) + + oldAttachment = @._attachments.get(index) + + patch = taiga.patch(oldAttachment.get('file'), toUpdateAttachment.get('file')) + + return @attachmentsService.patch(toUpdateAttachment.getIn(['file', 'id']), type, patch).then () => + @._attachments = @._attachments.set(index, toUpdateAttachment) + + @.regenerate() + +angular.module("taigaComponents").service("tgAttachmentsFullService", AttachmentsFullService) diff --git a/app/modules/components/attachments-full/attachments-full.service.spec.coffee b/app/modules/components/attachments-full/attachments-full.service.spec.coffee new file mode 100644 index 00000000..b987bb72 --- /dev/null +++ b/app/modules/components/attachments-full/attachments-full.service.spec.coffee @@ -0,0 +1,215 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# File: attchments-full.service.spec.coffee +### + +describe "tgAttachmentsFullService", -> + $provide = null + attachmentsFullService = null + mocks = {} + + _mockAttachmentsService = -> + mocks.attachmentsService = { + upload: sinon.stub() + } + + $provide.value("tgAttachmentsService", mocks.attachmentsService) + + _mockRootScope = -> + mocks.rootScope = {} + + $provide.value("$rootScope", mocks.rootScope) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockAttachmentsService() + _mockRootScope() + + return null + + _inject = -> + inject (_tgAttachmentsFullService_) -> + attachmentsFullService = _tgAttachmentsFullService_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaComponents" + + _setup() + + it "generate, refresh deprecated counter", () -> + attachments = Immutable.fromJS([ + { + file: { + is_deprecated: false + } + }, + { + file: { + is_deprecated: true + } + }, + { + file: { + is_deprecated: true + } + }, + { + file: { + is_deprecated: false + } + }, + { + file: { + is_deprecated: true + } + } + ]) + + attachmentsFullService._attachments = attachments + + attachmentsFullService.regenerate() + + expect(attachmentsFullService._deprecatedsCount).to.be.equal(3) + + it "toggle deprecated visibility", () -> + attachmentsFullService._deprecatedsVisible = false + + attachmentsFullService.regenerate = sinon.spy() + + attachmentsFullService.toggleDeprecatedsVisible() + + expect(attachmentsFullService.deprecatedsVisible).to.be.true + expect(attachmentsFullService.regenerate).to.be.calledOnce + + describe "add attachments", () -> + it "valid attachment", (done) -> + projectId = 1 + objId = 2 + type = "issue" + + file = Immutable.fromJS({ + file: {}, + name: 'test', + size: 3000 + }) + + mocks.attachmentsService.validate = sinon.stub() + mocks.attachmentsService.validate.withArgs(file).returns(true) + + mocks.attachmentsService.upload = sinon.stub() + mocks.attachmentsService.upload.promise().resolve(file) + + mocks.rootScope.$broadcast = sinon.spy() + + attachmentsFullService._attachments = Immutable.List() + + attachmentsFullService.addAttachment(projectId, objId, type, file).then () -> + expect(mocks.rootScope.$broadcast).have.been.calledWith('attachment:create') + expect(attachmentsFullService.attachments.count()).to.be.equal(1) + done() + + it "invalid attachment", () -> + file = Immutable.fromJS({ + file: {}, + name: 'test', + size: 3000 + }) + + mocks.attachmentsService.validate = sinon.stub() + mocks.attachmentsService.validate.withArgs(file).returns(false) + + mocks.attachmentsService.upload = sinon.stub() + mocks.attachmentsService.upload.promise().resolve(file) + + mocks.rootScope.$broadcast = sinon.spy() + + attachmentsFullService._attachments = Immutable.List() + + attachmentsFullService.addAttachment(file).then null, () -> + expect(attachmentsFullService._attachments.count()).to.be.equal(0) + + describe "deleteattachments", () -> + it "success attachment", (done) -> + mocks.attachmentsService.delete = sinon.stub() + mocks.attachmentsService.delete.withArgs('us', 2).promise().resolve() + + attachmentsFullService.regenerate = sinon.spy() + attachmentsFullService._attachments = Immutable.fromJS([ + { + file: {id: 1} + }, + { + file: {id: 2} + }, + { + file: {id: 3} + }, + { + file: {id: 4} + } + ]) + + deleteFile = attachmentsFullService._attachments.get(1) + + attachmentsFullService.deleteAttachment(deleteFile, 'us').then () -> + expect(attachmentsFullService.regenerate).have.been.calledOnce + expect(attachmentsFullService.attachments.size).to.be.equal(3) + done() + + it "reorder attachments", (done) -> + attachments = Immutable.fromJS([ + {file: {id: 0, is_deprecated: false, order: 0}}, + {file: {id: 1, is_deprecated: true, order: 1}}, + {file: {id: 2, is_deprecated: true, order: 2}}, + {file: {id: 3, is_deprecated: false, order: 3}}, + {file: {id: 4, is_deprecated: true, order: 4}} + ]) + + mocks.attachmentsService.patch = sinon.stub() + mocks.attachmentsService.patch.promise().resolve() + + attachmentsFullService._attachments = attachments + + attachmentsFullService.reorderAttachment('us', attachments.get(1), 0).then () -> + expect(attachmentsFullService.attachments.get(0)).to.be.equal(attachments.get(1)) + done() + + it "update attachment", () -> + attachments = Immutable.fromJS([ + {file: {id: 0, is_deprecated: false, order: 0}}, + {file: {id: 1, is_deprecated: true, order: 1}}, + {file: {id: 2, is_deprecated: true, order: 2}}, + {file: {id: 3, is_deprecated: false, order: 3}}, + {file: {id: 4, is_deprecated: true, order: 4}} + ]) + + attachment = attachments.get(1) + attachment = attachment.setIn(['file', 'is_deprecated'], false) + + mocks.attachmentsService.patch = sinon.stub() + mocks.attachmentsService.patch.withArgs(1, 'us', {is_deprecated: false}).promise().resolve() + + attachmentsFullService._attachments = attachments + + attachmentsFullService.updateAttachment(attachment, 'us').then () -> + expect(attachmentsFullService.attachments.get(1).toJS()).to.be.eql(attachment.toJS())