diff --git a/platform/commonUI/browse/test/MenuArrowControllerSpec.js b/platform/commonUI/browse/test/MenuArrowControllerSpec.js new file mode 100644 index 0000000000..7a78407d36 --- /dev/null +++ b/platform/commonUI/browse/test/MenuArrowControllerSpec.js @@ -0,0 +1,243 @@ +/***************************************************************************** + * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +/** + * MenuArrowControllerSpec. Created by shale on 07/02/2015. + */ +define( + ["../src/BrowseController"], + function (BrowseController) { + "use strict"; + + describe("The browse controller", function () { + var mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService, + mockRootObject, + mockUrlService, + mockDomainObject, + mockNextObject, + controller; + + function mockPromise(value) { + return { + then: function (callback) { + return mockPromise(callback(value)); + } + }; + } + + beforeEach(function () { + mockScope = jasmine.createSpyObj( + "$scope", + [ "$on", "$watch" ] + ); + mockRoute = { current: { params: {} } }; + mockLocation = jasmine.createSpyObj( + "$location", + [ "path" ] + ); + mockUrlService = jasmine.createSpyObj( + "urlService", + ["urlForLocation"] + ); + mockObjectService = jasmine.createSpyObj( + "objectService", + [ "getObjects" ] + ); + mockNavigationService = jasmine.createSpyObj( + "navigationService", + [ + "getNavigation", + "setNavigation", + "addListener", + "removeListener" + ] + ); + mockRootObject = jasmine.createSpyObj( + "domainObject", + [ "getId", "getCapability", "getModel", "useCapability" ] + ); + mockDomainObject = jasmine.createSpyObj( + "domainObject", + [ "getId", "getCapability", "getModel", "useCapability" ] + ); + mockNextObject = jasmine.createSpyObj( + "nextObject", + [ "getId", "getCapability", "getModel", "useCapability" ] + ); + + mockObjectService.getObjects.andReturn(mockPromise({ + ROOT: mockRootObject + })); + mockRootObject.useCapability.andReturn(mockPromise([ + mockDomainObject + ])); + mockDomainObject.useCapability.andReturn(mockPromise([ + mockNextObject + ])); + mockNextObject.useCapability.andReturn(undefined); + mockNextObject.getId.andReturn("next"); + mockDomainObject.getId.andReturn("mine"); + + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService, + mockUrlService + ); + }); + + it("uses composition to set the navigated object, if there is none", function () { + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService, + mockUrlService + ); + expect(mockNavigationService.setNavigation) + .toHaveBeenCalledWith(mockDomainObject); + }); + + it("does not try to override navigation", function () { + mockNavigationService.getNavigation.andReturn(mockDomainObject); + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService, + mockUrlService + ); + expect(mockScope.navigatedObject).toBe(mockDomainObject); + }); + + it("updates scope when navigated object changes", function () { + // Should have registered a listener - call it + mockNavigationService.addListener.mostRecentCall.args[0]( + mockDomainObject + ); + expect(mockScope.navigatedObject).toEqual(mockDomainObject); + }); + + it("releases its navigation listener when its scope is destroyed", function () { + expect(mockScope.$on).toHaveBeenCalledWith( + "$destroy", + jasmine.any(Function) + ); + mockScope.$on.mostRecentCall.args[1](); + // Should remove the listener it added earlier + expect(mockNavigationService.removeListener).toHaveBeenCalledWith( + mockNavigationService.addListener.mostRecentCall.args[0] + ); + }); + + it("uses route parameters to choose initially-navigated object", function () { + mockRoute.current.params.ids = "mine/next"; + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService + ); + expect(mockScope.navigatedObject).toBe(mockNextObject); + expect(mockNavigationService.setNavigation) + .toHaveBeenCalledWith(mockNextObject); + }); + + it("handles invalid IDs by going as far as possible", function () { + // Idea here is that if we get a bad path of IDs, + // browse controller should traverse down it until + // it hits an invalid ID. + mockRoute.current.params.ids = "mine/junk"; + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService + ); + expect(mockScope.navigatedObject).toBe(mockDomainObject); + expect(mockNavigationService.setNavigation) + .toHaveBeenCalledWith(mockDomainObject); + }); + + it("handles compositionless objects by going as far as possible", function () { + // Idea here is that if we get a path which passes + // through an object without a composition, browse controller + // should stop at it since remaining IDs cannot be loaded. + mockRoute.current.params.ids = "mine/next/junk"; + controller = new BrowseController( + mockScope, + mockRoute, + mockLocation, + mockObjectService, + mockNavigationService + ); + expect(mockScope.navigatedObject).toBe(mockNextObject); + expect(mockNavigationService.setNavigation) + .toHaveBeenCalledWith(mockNextObject); + }); + + it("updates the displayed route to reflect current navigation", function () { + var mockContext = jasmine.createSpyObj('context', ['getPath']), + mockUnlisten = jasmine.createSpy('unlisten'), + mockMode = "browse"; + + mockContext.getPath.andReturn( + [mockRootObject, mockDomainObject, mockNextObject] + ); + mockNextObject.getCapability.andCallFake(function (c) { + return c === 'context' && mockContext; + }); + mockScope.$on.andReturn(mockUnlisten); + // Provide a navigation change + mockNavigationService.addListener.mostRecentCall.args[0]( + mockNextObject + ); + + // Allows the path index to be checked + // prior to setting $route.current + mockLocation.path.andReturn("/browse/"); + + // Exercise the Angular workaround + mockScope.$on.mostRecentCall.args[1](); + expect(mockUnlisten).toHaveBeenCalled(); + + // location.path to be called with the urlService's + // urlFor function with the next domainObject and mode + expect(mockLocation.path).toHaveBeenCalledWith( + mockUrlService.urlForLocation(mockMode, mockNextObject) + ); + }); + + }); + } +); diff --git a/platform/commonUI/browse/test/suite.json b/platform/commonUI/browse/test/suite.json index 6cc41900e1..e3a8f11c28 100644 --- a/platform/commonUI/browse/test/suite.json +++ b/platform/commonUI/browse/test/suite.json @@ -1,6 +1,7 @@ [ "BrowseController", "BrowseObjectController", + "MenuArrowController", "creation/CreateAction", "creation/CreateActionProvider", "creation/CreateMenuController", diff --git a/platform/representation/test/actions/ContextMenuActionSpec.js b/platform/representation/test/actions/ContextMenuActionSpec.js new file mode 100644 index 0000000000..2d65ff6414 --- /dev/null +++ b/platform/representation/test/actions/ContextMenuActionSpec.js @@ -0,0 +1,163 @@ +/***************************************************************************** + * 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,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + + +/** + * Module defining ContextMenuActionSpec. Created by shale on 07/02/2015. + */ +define( + ["../../src/actions/ContextMenuAction", "../../src/gestures/GestureConstants"], + function (ContextMenuAction, GestureConstants) { + "use strict"; + + var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove" ], + DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ], + MENU_DIMENSIONS = GestureConstants.MCT_MENU_DIMENSIONS; + + + describe("The 'context menu' action", function () { + var mockCompile, + mockCompiledTemplate, + mockMenu, + mockDocument, + mockBody, + mockWindow, + mockRootScope, + mockScope, + mockElement, + mockDomainObject, + mockEvent, + gesture, + fireGesture; + + beforeEach(function () { + mockCompile = jasmine.createSpy("$compile"); + mockCompiledTemplate = jasmine.createSpy("template"); + mockMenu = jasmine.createSpyObj("menu", JQLITE_FUNCTIONS); + mockDocument = jasmine.createSpyObj("$document", JQLITE_FUNCTIONS); + mockBody = jasmine.createSpyObj("body", JQLITE_FUNCTIONS); + mockWindow = { innerWidth: MENU_DIMENSIONS[0] * 4, innerHeight: MENU_DIMENSIONS[1] * 4 }; + mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]); + mockScope = {}; + mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); + mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); + mockEvent = jasmine.createSpyObj("event", ["preventDefault"]); + mockEvent.pageX = 0; + mockEvent.pageY = 0; + + mockCompile.andReturn(mockCompiledTemplate); + mockCompiledTemplate.andReturn(mockMenu); + mockDocument.find.andReturn(mockBody); + mockRootScope.$new.andReturn(mockScope); + + gesture = new ContextMenuGesture( + mockCompile, + mockDocument, + mockWindow, + mockRootScope, + mockElement, + mockDomainObject + ); + + // Capture the contextmenu callback + fireGesture = mockElement.on.mostRecentCall.args[1]; + }); + + it("compiles and adds a menu to the DOM on a contextmenu event", function () { + // Make sure that callback really is for the contextmenu event + expect(mockElement.on.mostRecentCall.args[0]).toEqual("contextmenu"); + + fireGesture(mockEvent); + + expect(mockBody.append).toHaveBeenCalledWith(mockMenu); + }); + + it("prevents the default context menu behavior", function () { + fireGesture(mockEvent); + expect(mockEvent.preventDefault).toHaveBeenCalled(); + }); + + it("positions menus where clicked", function () { + mockEvent.pageX = 10; + mockEvent.pageY = 5; + fireGesture(mockEvent); + expect(mockScope.menuStyle.left).toEqual("10px"); + expect(mockScope.menuStyle.top).toEqual("5px"); + expect(mockScope.menuStyle.right).toBeUndefined(); + expect(mockScope.menuStyle.bottom).toBeUndefined(); + expect(mockScope.menuClass['go-up']).toBeFalsy(); + expect(mockScope.menuClass['go-left']).toBeFalsy(); + }); + + it("repositions menus near the screen edge", function () { + mockEvent.pageX = mockWindow.innerWidth - 10; + mockEvent.pageY = mockWindow.innerHeight - 5; + fireGesture(mockEvent); + expect(mockScope.menuStyle.right).toEqual("10px"); + expect(mockScope.menuStyle.bottom).toEqual("5px"); + expect(mockScope.menuStyle.left).toBeUndefined(); + expect(mockScope.menuStyle.top).toBeUndefined(); + expect(mockScope.menuClass['go-up']).toBeTruthy(); + expect(mockScope.menuClass['go-left']).toBeTruthy(); + }); + + it("removes a menu when body is clicked", function () { + // Show the menu + fireGesture(mockEvent); + + // Verify precondition + expect(mockBody.off).not.toHaveBeenCalled(); + + // Find and fire body's click listener + mockBody.on.calls.forEach(function (call) { + if (call.args[0] === 'click') { + call.args[1](); + } + }); + + // Menu should have been removed + expect(mockMenu.remove).toHaveBeenCalled(); + + // Listener should have been detached from body + expect(mockBody.off).toHaveBeenCalled(); + }); + + it("removes listeners from body if destroyed while menu is showing", function () { + // Show the menu + fireGesture(mockEvent); + + // Verify preconditions + expect(mockBody.off).not.toHaveBeenCalled(); + expect(mockMenu.remove).not.toHaveBeenCalled(); + + // Destroy the menu + gesture.destroy(); + + // Verify menu was removed and listener detached + expect(mockBody.off).toHaveBeenCalled(); + expect(mockMenu.remove).toHaveBeenCalled(); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/representation/test/gestures/ContextMenuGestureSpec.js b/platform/representation/test/gestures/ContextMenuGestureSpec.js index 8b91a969d4..7a13bb98ff 100644 --- a/platform/representation/test/gestures/ContextMenuGestureSpec.js +++ b/platform/representation/test/gestures/ContextMenuGestureSpec.js @@ -26,8 +26,8 @@ * Module defining ContextMenuGestureSpec. Created by vwoeltje on 11/22/14. */ define( - ["../../src/gestures/ContextMenuGesture", "../../src/gestures/GestureConstants"], - function (ContextMenuGesture, GestureConstants) { + ["../../src/gestures/ContextMenuGesture"], + function (ContextMenuGesture) { "use strict"; var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove" ], @@ -100,66 +100,9 @@ define( mockElement.on.mostRecentCall.args[1] ); }); - - it("compiles and adds a menu to the DOM on a contextmenu event", function () { - // Make sure that callback really is for the contextmenu event - expect(mockElement.on.mostRecentCall.args[0]).toEqual("contextmenu"); - - fireGesture(mockEvent); - - expect(mockBody.append).toHaveBeenCalledWith(mockMenu); - }); - - it("prevents the default context menu behavior", function () { - fireGesture(mockEvent); - expect(mockEvent.preventDefault).toHaveBeenCalled(); - }); - - it("positions menus where clicked", function () { - mockEvent.pageX = 10; - mockEvent.pageY = 5; - fireGesture(mockEvent); - expect(mockScope.menuStyle.left).toEqual("10px"); - expect(mockScope.menuStyle.top).toEqual("5px"); - expect(mockScope.menuStyle.right).toBeUndefined(); - expect(mockScope.menuStyle.bottom).toBeUndefined(); - expect(mockScope.menuClass['go-up']).toBeFalsy(); - expect(mockScope.menuClass['go-left']).toBeFalsy(); - }); - - it("repositions menus near the screen edge", function () { - mockEvent.pageX = mockWindow.innerWidth - 10; - mockEvent.pageY = mockWindow.innerHeight - 5; - fireGesture(mockEvent); - expect(mockScope.menuStyle.right).toEqual("10px"); - expect(mockScope.menuStyle.bottom).toEqual("5px"); - expect(mockScope.menuStyle.left).toBeUndefined(); - expect(mockScope.menuStyle.top).toBeUndefined(); - expect(mockScope.menuClass['go-up']).toBeTruthy(); - expect(mockScope.menuClass['go-left']).toBeTruthy(); - }); - - it("removes a menu when body is clicked", function () { - // Show the menu - fireGesture(mockEvent); - - // Verify precondition - expect(mockBody.off).not.toHaveBeenCalled(); - - // Find and fire body's click listener - mockBody.on.calls.forEach(function (call) { - if (call.args[0] === 'click') { - call.args[1](); - } - }); - - // Menu should have been removed - expect(mockMenu.remove).toHaveBeenCalled(); - - // Listener should have been detached from body - expect(mockBody.off).toHaveBeenCalled(); - }); - + + ////// Is this one here or in action spec? (shale) + /* it("removes listeners from body if destroyed while menu is showing", function () { // Show the menu fireGesture(mockEvent); @@ -175,6 +118,7 @@ define( expect(mockBody.off).toHaveBeenCalled(); expect(mockMenu.remove).toHaveBeenCalled(); }); + */ }); } diff --git a/platform/representation/test/suite.json b/platform/representation/test/suite.json index 600b81baea..16ebb5c666 100644 --- a/platform/representation/test/suite.json +++ b/platform/representation/test/suite.json @@ -1,4 +1,5 @@ [ + "actions/ContextMenuAction", "gestures/ContextMenuGesture", "gestures/DragGesture", "gestures/DropGesture",