import { Cartesian2 } from "../../Source/Cesium.js"; import { clone } from "../../Source/Cesium.js"; import { combine } from "../../Source/Cesium.js"; import { defined } from "../../Source/Cesium.js"; import { FeatureDetection } from "../../Source/Cesium.js"; import { KeyboardEventModifier } from "../../Source/Cesium.js"; import { ScreenSpaceEventHandler } from "../../Source/Cesium.js"; import { ScreenSpaceEventType } from "../../Source/Cesium.js"; import DomEventSimulator from "../DomEventSimulator.js"; describe("Core/ScreenSpaceEventHandler", function () { var usePointerEvents; var element; var handler; function createCloningSpy(name) { var spy = jasmine.createSpy(name); var cloningSpy = function () { // deep clone arguments so they are captured correctly by the spy var args = [].slice.apply(arguments).map(function (arg) { return clone(arg, true); }); spy.apply(this, args); }; function createPropertyDescriptor(prop) { return { get: function () { return spy[prop]; }, set: function (value) { spy[prop] = value; }, }; } for (var prop in spy) { if (spy.hasOwnProperty(prop)) { Object.defineProperty(cloningSpy, prop, createPropertyDescriptor(prop)); } } return cloningSpy; } var eventsToStop = "pointerdown pointerup pointermove pointercancel mousedown mouseup mousemove touchstart touchend touchmove touchcancel dblclick wheel mousewheel DOMMouseScroll".split( " " ); function stop(event) { event.stopPropagation(); event.preventDefault(); } beforeAll(function () { usePointerEvents = FeatureDetection.supportsPointerEvents(); }); beforeEach(function () { // ignore events that bubble up to the document. // this prevents triggering the browser's "middle click to scroll" behavior eventsToStop.forEach(function (e) { document.addEventListener(e, stop, false); }); element = document.createElement("div"); element.style.position = "absolute"; element.style.top = "0"; element.style.left = "0"; document.body.appendChild(element); element.disableRootEvents = true; if (usePointerEvents) { spyOn(element, "setPointerCapture"); } handler = new ScreenSpaceEventHandler(element); }); afterEach(function () { document.body.removeChild(element); handler = !handler.isDestroyed() && handler.destroy(); eventsToStop.forEach(function (e) { document.removeEventListener(e, stop, false); }); }); describe("setInputAction", function () { it("throws if action is undefined", function () { expect(function () { handler.setInputAction(); }).toThrowDeveloperError(); }); it("throws if type is undefined", function () { expect(function () { handler.setInputAction(function () {}); }).toThrowDeveloperError(); }); }); describe("getInputAction", function () { it("throws if type is undefined", function () { expect(function () { handler.getInputAction(); }).toThrowDeveloperError(); }); }); describe("removeInputAction", function () { it("throws if type is undefined", function () { expect(function () { handler.removeInputAction(); }).toThrowDeveloperError(); }); }); var MouseButton = { LEFT: 0, MIDDLE: 1, RIGHT: 2, }; function keyForValue(obj, val) { for (var key in obj) { if (obj[key] === val) { return key; } } } function createMouseSpec(specFunction, eventType, button, modifier) { var specName = keyForValue(ScreenSpaceEventType, eventType) + " action"; if (defined(modifier)) { specName += " with " + keyForValue(KeyboardEventModifier, modifier) + " modifier"; } it(specName, function () { var eventOptions = { button: button, ctrlKey: modifier === KeyboardEventModifier.CTRL, altKey: modifier === KeyboardEventModifier.ALT, shiftKey: modifier === KeyboardEventModifier.SHIFT, }; specFunction(eventType, modifier, eventOptions); }); } function createAllMouseSpecCombinations( specFunction, possibleButtons, possibleModifiers, possibleEventTypes ) { for (var i = 0; i < possibleButtons.length; ++i) { var eventType = possibleEventTypes[i]; var button = possibleButtons[i]; for (var j = 0; j < possibleModifiers.length; ++j) { var modifier = possibleModifiers[j]; createMouseSpec(specFunction, eventType, button, modifier); } } } function simulateMouseDown(element, options) { if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine(options, { pointerType: "mouse", }) ); } else { DomEventSimulator.fireMouseDown(element, options); } } function simulateMouseUp(element, options) { if (usePointerEvents) { DomEventSimulator.firePointerUp( element, combine(options, { pointerType: "mouse", }) ); } else { DomEventSimulator.fireMouseUp(element, options); } } function simulateMouseMove(element, options) { if (usePointerEvents) { DomEventSimulator.firePointerMove( element, combine(options, { pointerType: "mouse", }) ); } else { DomEventSimulator.fireMouseMove(element, options); } } describe("handles mouse down", function () { function testMouseDownEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); function simulateInput() { simulateMouseDown( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [ MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, ]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ ScreenSpaceEventType.LEFT_DOWN, ScreenSpaceEventType.MIDDLE_DOWN, ScreenSpaceEventType.RIGHT_DOWN, ]; createAllMouseSpecCombinations( testMouseDownEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); describe("handles mouse up", function () { function testMouseUpEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); // down, then up function simulateInput() { simulateMouseDown( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); simulateMouseUp( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); action.calls.reset(); // down, move, then up function simulateInput2() { simulateMouseDown( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); simulateMouseMove( element, combine( { clientX: 10, clientY: 11, }, eventOptions ) ); simulateMouseUp( element, combine( { clientX: 10, clientY: 11, }, eventOptions ) ); } simulateInput2(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(10, 11), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); simulateInput2(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [ MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, ]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ ScreenSpaceEventType.LEFT_UP, ScreenSpaceEventType.MIDDLE_UP, ScreenSpaceEventType.RIGHT_UP, ]; createAllMouseSpecCombinations( testMouseUpEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); describe("handles mouse click", function () { function testMouseClickEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); // down, then up function simulateInput() { simulateMouseDown( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); simulateMouseUp( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // mouse clicks are not fired if the mouse moves too far away from the down position action.calls.reset(); simulateMouseDown( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); simulateMouseUp( element, combine( { clientX: 10, clientY: 11, }, eventOptions ) ); expect(action).not.toHaveBeenCalled(); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [ MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, ]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ ScreenSpaceEventType.LEFT_CLICK, ScreenSpaceEventType.MIDDLE_CLICK, ScreenSpaceEventType.RIGHT_CLICK, ]; createAllMouseSpecCombinations( testMouseClickEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); describe("handles mouse double click", function () { function testMouseDoubleClickEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); function simulateInput() { DomEventSimulator.fireDoubleClick( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [MouseButton.LEFT]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ScreenSpaceEventType.LEFT_DOUBLE_CLICK]; createAllMouseSpecCombinations( testMouseDoubleClickEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); describe("handles mouse move", function () { function testMouseMoveEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); function simulateInput() { simulateMouseMove( element, combine( { clientX: 1, clientY: 2, }, eventOptions ) ); simulateMouseMove( element, combine( { clientX: 2, clientY: 3, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(2); expect(action).toHaveBeenCalledWith({ startPosition: new Cartesian2(1, 2), endPosition: new Cartesian2(2, 3), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [undefined]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ScreenSpaceEventType.MOUSE_MOVE]; createAllMouseSpecCombinations( testMouseMoveEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); describe("handles mouse wheel", function () { if ("onwheel" in document) { describe("using standard wheel event", function () { function testWheelEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); function simulateInput() { DomEventSimulator.fireWheel( element, combine( { deltaY: 120, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); // NOTE: currently the action is passed the value of 'wheelDelta' which is inverted from the standard 'deltaY' expect(action).toHaveBeenCalledWith(-120); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [undefined]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ScreenSpaceEventType.WHEEL]; createAllMouseSpecCombinations( testWheelEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); } else if (document.onmousewheel !== undefined) { describe("using legacy mousewheel event", function () { function testMouseWheelEvent(eventType, modifier, eventOptions) { var action = createCloningSpy("action"); handler.setInputAction(action, eventType, modifier); expect(handler.getInputAction(eventType, modifier)).toEqual(action); function simulateInput() { DomEventSimulator.fireMouseWheel( element, combine( { wheelDelta: -120, }, eventOptions ) ); } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith(-120); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType, modifier); simulateInput(); expect(action).not.toHaveBeenCalled(); } var possibleButtons = [undefined]; var possibleModifiers = [ undefined, KeyboardEventModifier.SHIFT, KeyboardEventModifier.CTRL, KeyboardEventModifier.ALT, ]; var possibleEventTypes = [ScreenSpaceEventType.WHEEL]; createAllMouseSpecCombinations( testMouseWheelEvent, possibleButtons, possibleModifiers, possibleEventTypes ); }); } }); it("handles touch start", function () { var eventType = ScreenSpaceEventType.LEFT_DOWN; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("handles touch move", function () { var eventType = ScreenSpaceEventType.MOUSE_MOVE; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then move function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchMovePosition = { clientX: 10, clientY: 11, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerMove( element, combine( { pointerType: "touch", pointerId: 1, }, touchMovePosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchMove(element, { changedTouches: [ combine( { identifier: 0, }, touchMovePosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ startPosition: new Cartesian2(1, 2), endPosition: new Cartesian2(10, 11), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("handles touch end", function () { var eventType = ScreenSpaceEventType.LEFT_UP; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then end function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchEndPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchEnd(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); action.calls.reset(); // start, move, then end function simulateInput2() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchMovePosition = { clientX: 10, clientY: 11, }; var touchEndPosition = { clientX: 10, clientY: 11, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerMove( element, combine( { pointerType: "touch", pointerId: 1, }, touchMovePosition ) ); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchMove(element, { changedTouches: [ combine( { identifier: 0, }, touchMovePosition ), ], }); DomEventSimulator.fireTouchEnd(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput2(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(10, 11), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); simulateInput2(); expect(action).not.toHaveBeenCalled(); }); it("treats touch end as touch cancel", function () { var eventType = ScreenSpaceEventType.LEFT_UP; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then end function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchEndPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerCancel( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchCancel(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); action.calls.reset(); // start, move, then end function simulateInput2() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchMovePosition = { clientX: 10, clientY: 11, }; var touchEndPosition = { clientX: 10, clientY: 11, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerMove( element, combine( { pointerType: "touch", pointerId: 1, }, touchMovePosition ) ); DomEventSimulator.firePointerCancel( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchMove(element, { changedTouches: [ combine( { identifier: 0, }, touchMovePosition ), ], }); DomEventSimulator.fireTouchCancel(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput2(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(10, 11), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); simulateInput2(); expect(action).not.toHaveBeenCalled(); }); it("handles touch pinch start", function () { var eventType = ScreenSpaceEventType.PINCH_START; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // touch 1, then touch 2 function simulateInput() { var touch1StartPosition = { clientX: 1, clientY: 2, }; var touch2StartPosition = { clientX: 3, clientY: 4, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touch1StartPosition ) ); DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 2, }, touch2StartPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touch1StartPosition ), ], }); DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touch1StartPosition ), combine( { identifier: 1, }, touch2StartPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position1: new Cartesian2(1, 2), position2: new Cartesian2(3, 4), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("handles touch pinch move", function () { var eventType = ScreenSpaceEventType.PINCH_MOVE; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // touch 1, then touch 2, then move function simulateInput() { var touch1StartPosition = { clientX: 1, clientY: 2, }; var touch2StartPosition = { clientX: 4, clientY: 3, }; var touch1MovePosition = { clientX: 10, clientY: 11, }; var touch2MovePosition = { clientX: 21, clientY: 20, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touch1StartPosition ) ); DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 2, }, touch2StartPosition ) ); DomEventSimulator.firePointerMove( element, combine( { pointerType: "touch", pointerId: 1, }, touch1MovePosition ) ); DomEventSimulator.firePointerMove( element, combine( { pointerType: "touch", pointerId: 2, }, touch2MovePosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touch1StartPosition ), ], }); DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touch1StartPosition ), combine( { identifier: 1, }, touch2StartPosition ), ], }); DomEventSimulator.fireTouchMove(element, { changedTouches: [ combine( { identifier: 0, }, touch1MovePosition ), combine( { identifier: 1, }, touch2MovePosition ), ], }); } } simulateInput(); // original delta X: 3 // original delta Y: 1 // final delta X: 11 // final delta Y: 9 if (usePointerEvents) { // because every pointer event is separate, one touch moves, then the other. expect(action.calls.count()).toEqual(2); // intermediate delta X: -6 // intermediate delta Y: -8 expect(action).toHaveBeenCalledWith({ distance: { startPosition: new Cartesian2(0, Math.sqrt(3 * 3 + 1 * 1) * 0.25), endPosition: new Cartesian2(0, Math.sqrt(-6 * -6 + -8 * -8) * 0.25), }, angleAndHeight: { startPosition: new Cartesian2(Math.atan2(1, 3), (3 + 2) * 0.125), endPosition: new Cartesian2(Math.atan2(-8, -6), (3 + 11) * 0.125), }, }); expect(action).toHaveBeenCalledWith({ distance: { startPosition: new Cartesian2(0, Math.sqrt(-6 * -6 + -8 * -8) * 0.25), endPosition: new Cartesian2(0, Math.sqrt(11 * 11 + 9 * 9) * 0.25), }, angleAndHeight: { startPosition: new Cartesian2(Math.atan2(-8, -6), (3 + 11) * 0.125), endPosition: new Cartesian2(Math.atan2(9, 11), (11 + 20) * 0.125), }, }); } else { // touch events can move both touches simultaneously expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ distance: { startPosition: new Cartesian2(0, Math.sqrt(3 * 3 + 1 * 1) * 0.25), endPosition: new Cartesian2(0, Math.sqrt(11 * 11 + 9 * 9) * 0.25), }, angleAndHeight: { startPosition: new Cartesian2(Math.atan2(1, 3), (3 + 2) * 0.125), endPosition: new Cartesian2(Math.atan2(9, 11), (11 + 20) * 0.125), }, }); } // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("handles touch pinch release", function () { var leftDownEventType = ScreenSpaceEventType.LEFT_DOWN; var leftDownAction = createCloningSpy("LEFT_DOWN"); handler.setInputAction(leftDownAction, leftDownEventType); var pinchStartEventType = ScreenSpaceEventType.PINCH_START; var pinchStartAction = createCloningSpy("PINCH_START"); handler.setInputAction(pinchStartAction, pinchStartEventType); var pinchEndEventType = ScreenSpaceEventType.PINCH_END; var pinchEndAction = createCloningSpy("PINCH_END"); handler.setInputAction(pinchEndAction, pinchEndEventType); var touch1Position = { clientX: 1, clientY: 2, }; var touch2Position = { clientX: 4, clientY: 3, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touch1Position ) ); DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 2, }, touch2Position ) ); // Releasing one of two fingers should not trigger // PINCH_END or LEFT_DOWN: leftDownAction.calls.reset(); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touch1Position ) ); expect(pinchEndAction).not.toHaveBeenCalled(); expect(leftDownAction).not.toHaveBeenCalled(); pinchEndAction.calls.reset(); // Putting another finger down should not trigger // PINCH_START: pinchStartAction.calls.reset(); DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touch2Position ) ); expect(pinchStartAction).not.toHaveBeenCalled(); // Releasing both fingers should trigger PINCH_END: DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touch2Position ) ); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 2, }, touch2Position ) ); expect(pinchEndAction).toHaveBeenCalled(); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine({ identifier: 0 }, touch1Position), combine({ identifier: 1 }, touch2Position), ], }); leftDownAction.calls.reset(); // Releasing one of two fingers should not trigger // PINCH_END or LEFT_DOWN: DomEventSimulator.fireTouchEnd(element, { changedTouches: [combine({ identifier: 0 }, touch1Position)], }); expect(pinchEndAction).not.toHaveBeenCalled(); expect(leftDownAction).not.toHaveBeenCalled(); // Releasing both fingers should trigger PINCH_END: pinchEndAction.calls.reset(); DomEventSimulator.fireTouchEnd(element, { changedTouches: [combine({ identifier: 1 }, touch2Position)], }); expect(pinchEndAction).toHaveBeenCalled(); } }); it("handles touch click", function () { var eventType = ScreenSpaceEventType.LEFT_CLICK; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then end function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchEndPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchEnd(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("handles touch and hold gesture", function () { jasmine.clock().install(); var delay = ScreenSpaceEventHandler.touchHoldDelayMilliseconds; var eventType = ScreenSpaceEventType.RIGHT_CLICK; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then end function simulateInput(timeout) { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchEndPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); jasmine.clock().tick(timeout); DomEventSimulator.firePointerUp( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); jasmine.clock().tick(timeout); DomEventSimulator.fireTouchEnd(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput(delay + 1); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // Should not be fired if hold delay is less than touchHoldDelayMilliseconds. action.calls.reset(); simulateInput(delay - 1); expect(action).not.toHaveBeenCalled(); // Should not be fired after removal. action.calls.reset(); handler.removeInputAction(eventType); simulateInput(delay + 1); expect(action).not.toHaveBeenCalled(); // Should not fire click action if touch and hold is triggered. eventType = ScreenSpaceEventType.LEFT_CLICK; handler.setInputAction(action, eventType); simulateInput(delay + 1); expect(action).not.toHaveBeenCalled(); jasmine.clock().uninstall(); }); it("treats touch cancel as touch end for touch clicks", function () { var eventType = ScreenSpaceEventType.LEFT_CLICK; var action = createCloningSpy("action"); handler.setInputAction(action, eventType); expect(handler.getInputAction(eventType)).toEqual(action); // start, then end function simulateInput() { var touchStartPosition = { clientX: 1, clientY: 2, }; var touchEndPosition = { clientX: 1, clientY: 2, }; if (usePointerEvents) { DomEventSimulator.firePointerDown( element, combine( { pointerType: "touch", pointerId: 1, }, touchStartPosition ) ); DomEventSimulator.firePointerCancel( element, combine( { pointerType: "touch", pointerId: 1, }, touchEndPosition ) ); } else { DomEventSimulator.fireTouchStart(element, { changedTouches: [ combine( { identifier: 0, }, touchStartPosition ), ], }); DomEventSimulator.fireTouchCancel(element, { changedTouches: [ combine( { identifier: 0, }, touchEndPosition ), ], }); } } simulateInput(); expect(action.calls.count()).toEqual(1); expect(action).toHaveBeenCalledWith({ position: new Cartesian2(1, 2), }); // should not be fired after removal action.calls.reset(); handler.removeInputAction(eventType); simulateInput(); expect(action).not.toHaveBeenCalled(); }); it("sets isDestroyed when destroyed", function () { expect(handler.isDestroyed()).toEqual(false); handler.destroy(); expect(handler.isDestroyed()).toEqual(true); }); it("unregisters event listeners when destroyed", function () { handler = handler.destroy(); spyOn(element, "addEventListener").and.callThrough(); spyOn(element, "removeEventListener").and.callThrough(); handler = new ScreenSpaceEventHandler(element); expect(element.addEventListener.calls.count()).not.toEqual(0); expect(element.removeEventListener.calls.count()).toEqual(0); handler.destroy(); expect(element.removeEventListener.calls.count()).toEqual( element.addEventListener.calls.count() ); }); });