From f5ce0e844f2f9564ee07f78cbe608d9525a870f7 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Sat, 22 Nov 2014 10:11:06 -0800 Subject: [PATCH] [Representation] Spec ContextMenuGesture Add spec for ContextMenuGesture, which exposes a menu of applicable actions for objects when it is performed. One of the built-in gestures supported by the representation component, WTD-521. --- .../src/gestures/ContextMenuGesture.js | 22 +-- .../src/gestures/GestureConstants.js | 3 +- .../test/gestures/ContextMenuGestureSpec.js | 137 ++++++++++++++++-- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/platform/representation/src/gestures/ContextMenuGesture.js b/platform/representation/src/gestures/ContextMenuGesture.js index 2a553cca53..0c24cf84bc 100644 --- a/platform/representation/src/gestures/ContextMenuGesture.js +++ b/platform/representation/src/gestures/ContextMenuGesture.js @@ -4,15 +4,16 @@ * Module defining ContextMenuGesture. Created by vwoeltje on 11/17/14. */ define( - [], - function () { + ["./GestureConstants"], + function (GestureConstants) { "use strict"; var MENU_TEMPLATE = "" + - ""; + "", + dismissExistingMenu; /** * Add listeners to a view such that it launches a context menu for the @@ -24,7 +25,7 @@ define( function showMenu(event) { var winDim = [$window.innerWidth, $window.innerHeight], eventCoors = [event.pageX, event.pageY], - menuDim = [170, 200], + menuDim = GestureConstants.MCT_MENU_DIMENSIONS, body = $document.find('body'), scope = $rootScope.$new(), goLeft = eventCoors[0] + menuDim[0] > winDim[0], @@ -35,16 +36,16 @@ define( function dismiss() { menu.remove(); body.off("click", dismiss); - ContextMenuGesture.dismissExistingMenu = undefined; + dismissExistingMenu = undefined; } // Dismiss any menu which was already showing - if (ContextMenuGesture.dismissExistingMenu) { - ContextMenuGesture.dismissExistingMenu(); + if (dismissExistingMenu) { + dismissExistingMenu(); } // ...and record the presence of this menu. - ContextMenuGesture.dismissExistingMenu = dismiss; + dismissExistingMenu = dismiss; // Set up the scope, including menu positioning scope.domainObject = domainObject; @@ -73,8 +74,9 @@ define( return { destroy: function () { - if (ContextMenuGesture.dismissExistingMenu) { - ContextMenuGesture.dismissExistingMenu(); + // Scope has been destroyed, so remove all listeners. + if (dismissExistingMenu) { + dismissExistingMenu(); } element.off('contextmenu', showMenu); } diff --git a/platform/representation/src/gestures/GestureConstants.js b/platform/representation/src/gestures/GestureConstants.js index addbf50ae0..05ebafc2d2 100644 --- a/platform/representation/src/gestures/GestureConstants.js +++ b/platform/representation/src/gestures/GestureConstants.js @@ -4,5 +4,6 @@ * Module defining GestureConstants. Created by vwoeltje on 11/17/14. */ define({ - MCT_DRAG_TYPE: 'mct-domain-object-id' + MCT_DRAG_TYPE: 'mct-domain-object-id', + MCT_MENU_DIMENSIONS: [ 170, 200 ] }); \ No newline at end of file diff --git a/platform/representation/test/gestures/ContextMenuGestureSpec.js b/platform/representation/test/gestures/ContextMenuGestureSpec.js index a96800a27b..1c5b0b3c83 100644 --- a/platform/representation/test/gestures/ContextMenuGestureSpec.js +++ b/platform/representation/test/gestures/ContextMenuGestureSpec.js @@ -1,21 +1,136 @@ -/*global define,Promise*/ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + /** * Module defining ContextMenuGestureSpec. Created by vwoeltje on 11/22/14. */ define( - ["../../src/gestures/ContextMenuGesture"], - function (ContextMenuGesture) { + ["../../src/gestures/ContextMenuGesture", "../../src/gestures/GestureConstants"], + function (ContextMenuGesture, GestureConstants) { "use strict"; - /** - * - * @constructor - */ - function ContextMenuGestureSpec() { - return {}; - } + var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove" ], + DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ]; - return ContextMenuGestureSpec; + // ContextMenuGesture($compile, $document, $window, $rootScope, element, domainObject) + + describe("The 'context menu' gesture", 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: GestureConstants[0] * 4, innerHeight: GestureConstants[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("attaches a callback for context menu events", function () { + expect(mockElement.on).toHaveBeenCalledWith( + "contextmenu", + jasmine.any(Function) + ); + }); + + it("detaches a callback for context menu events when destroyed", function () { + expect(mockElement.off).not.toHaveBeenCalled(); + + gesture.destroy(); + + expect(mockElement.off).toHaveBeenCalledWith( + "contextmenu", + 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("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