diff --git a/platform/entanglement/src/actions/CopyAction.js b/platform/entanglement/src/actions/CopyAction.js index c7a1c14ba7..6df372b45c 100644 --- a/platform/entanglement/src/actions/CopyAction.js +++ b/platform/entanglement/src/actions/CopyAction.js @@ -26,20 +26,6 @@ define( function (AbstractComposeAction) { "use strict"; - /* - function CopyAction(locationService, copyService, context) { - return new AbstractComposeAction ( - locationService, - copyService, - context, - "Duplicate", - "to a location" - ); - } - - return CopyAction; - */ - /** * The CopyAction is available from context menus and allows a user to * deep copy an object to another location of their choosing. @@ -50,6 +36,8 @@ define( */ function CopyAction($log, locationService, copyService, dialogService, notificationService, context) { + this.dialog = undefined; + this.notification = undefined; this.dialogService = dialogService; this.notificationService = notificationService; this.$log = $log; @@ -58,58 +46,61 @@ define( context, "Duplicate", "to a location"); } + /** + * Updates user about progress of copy. Should not be invoked by + * client code under any circumstances. + * + * @private + * @param phase + * @param totalObjects + * @param processed + */ + CopyAction.prototype.progress = function(phase, totalObjects, processed){ + /* + Copy has two distinct phases. In the first phase a copy plan is + made in memory. During this phase of execution, the user is + shown a blocking 'modal' dialog. + + In the second phase, the copying is taking place, and the user + is shown non-invasive banner notifications at the bottom of the screen. + */ + if (phase.toLowerCase() === 'preparing' && !this.dialog){ + this.dialog = self.dialogService.showBlockingMessage({ + title: "Preparing to copy objects", + unknownProgress: true, + severity: "info", + }); + } else if (phase.toLowerCase() === "copying") { + self.dialogService.dismiss(); + if (!notification) { + this.notification = self.notificationService + .notify({ + title: "Copying objects", + unknownProgress: false, + severity: "info" + }); + } + this.notification.model.progress = (processed / totalObjects) * 100; + this.notification.model.title = ["Copied ", processed, "of ", + totalObjects, "objects"].join(" "); + } + } + /** * Executes the CopyAction. The CopyAction uses the default behaviour of * the AbstractComposeAction, but extends it to support notification * updates of progress on copy. */ CopyAction.prototype.perform = function() { - var self = this, - notification, - dialog, - notificationModel = { - title: "Copying objects", - unknownProgress: false, - severity: "info", - }; - - /* - Show banner notification of copy progress. - */ - function progress(phase, totalObjects, processed){ - /* - Copy has two distinct phases. In the first phase a copy plan is - made in memory. During this phase of execution, the user is - shown a blocking 'modal' dialog. - - In the second phase, the copying is taking place, and the user - is shown non-invasive banner notifications at the bottom of the screen. - */ - if (phase.toLowerCase() === 'preparing' && !dialog){ - dialog = self.dialogService.showBlockingMessage({ - title: "Preparing to copy objects", - unknownProgress: true, - severity: "info", - }); - } else if (phase.toLowerCase() === "copying") { - self.dialogService.dismiss(); - if (!notification) { - notification = self.notificationService - .notify(notificationModel); - } - notificationModel.progress = (processed / totalObjects) * 100; - notificationModel.title = ["Copied ", processed, "of ", - totalObjects, "objects"].join(" "); - } - } + var self = this; - AbstractComposeAction.prototype.perform.call(this) + return AbstractComposeAction.prototype.perform.call(this) .then( - function(){ - notification.dismiss(); + function success(){ + self.notification.dismiss(); self.notificationService.info("Copying complete."); }, - function(error){ + function error(error){ self.$log.error("Error copying objects. ", error); //Show more general error message self.notificationService.notify({ @@ -119,10 +110,11 @@ define( }); }, - function(notification){ - progress(notification.phase, notification.totalObjects, notification.processed); - }) - }; + function notification(notification){ + this.progress(notification.phase, notification.totalObjects, notification.processed); + } + ); + }; return CopyAction; } ); diff --git a/platform/entanglement/test/actions/CopyActionSpec.js b/platform/entanglement/test/actions/CopyActionSpec.js index 4284a22e59..3fa4055f4c 100644 --- a/platform/entanglement/test/actions/CopyActionSpec.js +++ b/platform/entanglement/test/actions/CopyActionSpec.js @@ -41,7 +41,12 @@ define( selectedObject, selectedObjectContextCapability, currentParent, - newParent; + newParent, + notificationService, + notification, + dialogService, + mockLog, + abstractComposePromise; beforeEach(function () { selectedObjectContextCapability = jasmine.createSpyObj( @@ -87,10 +92,47 @@ define( ] ); + abstractComposePromise = jasmine.createSpyObj( + 'abstractComposePromise', + [ + 'then' + ] + ); + + abstractComposePromise.then.andCallFake(function(success, error, notify){ + notify({phase: "copying", totalObjects: 10, processed: 10}); + success(); + } + ) + + locationServicePromise.then.andCallFake(function(callback){ + callback(newParent); + return abstractComposePromise; + }); + locationService .getLocationFromUser .andReturn(locationServicePromise); + dialogService = jasmine.createSpyObj('dialogService', + ['showBlockingMessage'] + ); + dialogService.showBlockingMessage.andReturn(); + + notification = jasmine.createSpyObj('notification', + ['dismiss', 'model'] + ); + notification.dismiss.andReturn(); + + notificationService = jasmine.createSpyObj('notificationService', + ['notify'] + ); + + notificationService.notify.andReturn(notification); + + mockLog = jasmine.createSpyObj('log', ['error']); + mockLog.error.andReturn(); + copyService = new MockCopyService(); }); @@ -102,8 +144,11 @@ define( }; copyAction = new CopyAction( + mockLog, locationService, copyService, + dialogService, + notificationService, context ); }); @@ -114,6 +159,7 @@ define( describe("when performed it", function () { beforeEach(function () { + spyOn(copyAction, 'progress').andCallThrough(); copyAction.perform(); }); @@ -132,7 +178,7 @@ define( .toHaveBeenCalledWith(jasmine.any(Function)); }); - it("copys object to selected location", function () { + it("copies object to selected location", function () { locationServicePromise .then .mostRecentCall @@ -141,6 +187,11 @@ define( expect(copyService.perform) .toHaveBeenCalledWith(selectedObject, newParent); }); + + it("notifies the user of progress", function(){ + expect(copyAction.progress.calls.length).toBeGreaterThan(0) + }); + }); }); @@ -152,8 +203,11 @@ define( }; copyAction = new CopyAction( + mockLog, locationService, copyService, + dialogService, + notificationService, context ); }); diff --git a/platform/entanglement/test/actions/LinkActionSpec.js b/platform/entanglement/test/actions/LinkActionSpec.js deleted file mode 100644 index 03967a6672..0000000000 --- a/platform/entanglement/test/actions/LinkActionSpec.js +++ /dev/null @@ -1,174 +0,0 @@ -/***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT Web is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT Web includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/*global define,describe,beforeEach,it,jasmine,expect */ - -define( - [ - '../../src/actions/LinkAction', - '../services/MockLinkService', - '../DomainObjectFactory' - ], - function (LinkAction, MockLinkService, domainObjectFactory) { - "use strict"; - - describe("Link Action", function () { - - var linkAction, - locationService, - locationServicePromise, - linkService, - context, - selectedObject, - selectedObjectContextCapability, - currentParent, - newParent; - - beforeEach(function () { - selectedObjectContextCapability = jasmine.createSpyObj( - 'selectedObjectContextCapability', - [ - 'getParent' - ] - ); - - selectedObject = domainObjectFactory({ - name: 'selectedObject', - model: { - name: 'selectedObject' - }, - capabilities: { - context: selectedObjectContextCapability - } - }); - - currentParent = domainObjectFactory({ - name: 'currentParent' - }); - - selectedObjectContextCapability - .getParent - .andReturn(currentParent); - - newParent = domainObjectFactory({ - name: 'newParent' - }); - - locationService = jasmine.createSpyObj( - 'locationService', - [ - 'getLocationFromUser' - ] - ); - - locationServicePromise = jasmine.createSpyObj( - 'locationServicePromise', - [ - 'then' - ] - ); - - locationService - .getLocationFromUser - .andReturn(locationServicePromise); - - linkService = new MockLinkService(); - }); - - - describe("with context from context-action", function () { - beforeEach(function () { - context = { - domainObject: selectedObject - }; - - linkAction = new LinkAction( - locationService, - linkService, - context - ); - }); - - it("initializes happily", function () { - expect(linkAction).toBeDefined(); - }); - - describe("when performed it", function () { - beforeEach(function () { - linkAction.perform(); - }); - - it("prompts for location", function () { - expect(locationService.getLocationFromUser) - .toHaveBeenCalledWith( - "Link selectedObject to a new location", - "Link To", - jasmine.any(Function), - currentParent - ); - }); - - it("waits for location from user", function () { - expect(locationServicePromise.then) - .toHaveBeenCalledWith(jasmine.any(Function)); - }); - - it("links object to selected location", function () { - locationServicePromise - .then - .mostRecentCall - .args[0](newParent); - - expect(linkService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - }); - }); - - describe("with context from drag-drop", function () { - beforeEach(function () { - context = { - selectedObject: selectedObject, - domainObject: newParent - }; - - linkAction = new LinkAction( - locationService, - linkService, - context - ); - }); - - it("initializes happily", function () { - expect(linkAction).toBeDefined(); - }); - - - it("performs link immediately", function () { - linkAction.perform(); - expect(linkService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - }); - }); - } -); diff --git a/platform/entanglement/test/actions/MoveActionSpec.js b/platform/entanglement/test/actions/MoveActionSpec.js deleted file mode 100644 index 52a7c6e301..0000000000 --- a/platform/entanglement/test/actions/MoveActionSpec.js +++ /dev/null @@ -1,174 +0,0 @@ -/***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT Web is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT Web includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/*global define,describe,beforeEach,it,jasmine,expect */ - -define( - [ - '../../src/actions/MoveAction', - '../services/MockMoveService', - '../DomainObjectFactory' - ], - function (MoveAction, MockMoveService, domainObjectFactory) { - "use strict"; - - describe("Move Action", function () { - - var moveAction, - locationService, - locationServicePromise, - moveService, - context, - selectedObject, - selectedObjectContextCapability, - currentParent, - newParent; - - beforeEach(function () { - selectedObjectContextCapability = jasmine.createSpyObj( - 'selectedObjectContextCapability', - [ - 'getParent' - ] - ); - - selectedObject = domainObjectFactory({ - name: 'selectedObject', - model: { - name: 'selectedObject' - }, - capabilities: { - context: selectedObjectContextCapability - } - }); - - currentParent = domainObjectFactory({ - name: 'currentParent' - }); - - selectedObjectContextCapability - .getParent - .andReturn(currentParent); - - newParent = domainObjectFactory({ - name: 'newParent' - }); - - locationService = jasmine.createSpyObj( - 'locationService', - [ - 'getLocationFromUser' - ] - ); - - locationServicePromise = jasmine.createSpyObj( - 'locationServicePromise', - [ - 'then' - ] - ); - - locationService - .getLocationFromUser - .andReturn(locationServicePromise); - - moveService = new MockMoveService(); - }); - - - describe("with context from context-action", function () { - beforeEach(function () { - context = { - domainObject: selectedObject - }; - - moveAction = new MoveAction( - locationService, - moveService, - context - ); - }); - - it("initializes happily", function () { - expect(moveAction).toBeDefined(); - }); - - describe("when performed it", function () { - beforeEach(function () { - moveAction.perform(); - }); - - it("prompts for location", function () { - expect(locationService.getLocationFromUser) - .toHaveBeenCalledWith( - "Move selectedObject to a new location", - "Move To", - jasmine.any(Function), - currentParent - ); - }); - - it("waits for location from user", function () { - expect(locationServicePromise.then) - .toHaveBeenCalledWith(jasmine.any(Function)); - }); - - it("moves object to selected location", function () { - locationServicePromise - .then - .mostRecentCall - .args[0](newParent); - - expect(moveService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - }); - }); - - describe("with context from drag-drop", function () { - beforeEach(function () { - context = { - selectedObject: selectedObject, - domainObject: newParent - }; - - moveAction = new MoveAction( - locationService, - moveService, - context - ); - }); - - it("initializes happily", function () { - expect(moveAction).toBeDefined(); - }); - - - it("performs move immediately", function () { - moveAction.perform(); - expect(moveService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - }); - }); - } -); diff --git a/platform/entanglement/test/suite.json b/platform/entanglement/test/suite.json index 12831b407a..a4db45a909 100644 --- a/platform/entanglement/test/suite.json +++ b/platform/entanglement/test/suite.json @@ -1,5 +1,6 @@ [ "actions/AbstractComposeAction", + "actions/CopyAction", "services/CopyService", "services/LinkService", "services/MoveService",