const expect = chai.expect; import EventTargetMixin from '../core/util/eventtarget.js'; import GestureHandler from '../core/input/gesturehandler.js'; class DummyTarget extends EventTargetMixin { } describe('Gesture handler', function () { let target, handler; let gestures; let clock; let touches; before(function () { clock = sinon.useFakeTimers(); }); after(function () { clock.restore(); }); beforeEach(function () { target = new DummyTarget(); gestures = sinon.spy(); target.addEventListener('gesturestart', gestures); target.addEventListener('gesturemove', gestures); target.addEventListener('gestureend', gestures); touches = []; handler = new GestureHandler(); handler.attach(target); }); afterEach(function () { if (handler) { handler.detach(); } target = null; gestures = null; }); function touchStart(id, x, y) { let touch = { identifier: id, clientX: x, clientY: y }; touches.push(touch); let ev = { type: 'touchstart', touches: touches, targetTouches: touches, changedTouches: [ touch ], stopPropagation: sinon.spy(), preventDefault: sinon.spy() }; target.dispatchEvent(ev); } function touchMove(id, x, y) { let touch = touches.find(t => t.identifier === id); touch.clientX = x; touch.clientY = y; let ev = { type: 'touchmove', touches: touches, targetTouches: touches, changedTouches: [ touch ], stopPropagation: sinon.spy(), preventDefault: sinon.spy() }; target.dispatchEvent(ev); } function touchEnd(id) { let idx = touches.findIndex(t => t.identifier === id); let touch = touches.splice(idx, 1)[0]; let ev = { type: 'touchend', touches: touches, targetTouches: touches, changedTouches: [ touch ], stopPropagation: sinon.spy(), preventDefault: sinon.spy() }; target.dispatchEvent(ev); } describe('Single finger tap', function () { it('should handle single finger tap', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; touchEnd(1); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'onetap', clientX: 20.0, clientY: 30.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'onetap', clientX: 20.0, clientY: 30.0 } })); }); }); describe('Two finger tap', function () { it('should handle two finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); expect(gestures).to.not.have.been.called; touchEnd(1); expect(gestures).to.not.have.been.called; touchEnd(2); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twotap', clientX: 25.0, clientY: 40.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'twotap', clientX: 25.0, clientY: 40.0 } })); }); it('should ignore slow starting two finger tap', function () { touchStart(1, 20.0, 30.0); clock.tick(500); touchStart(2, 30.0, 50.0); touchEnd(1); touchEnd(2); expect(gestures).to.not.have.been.called; }); it('should ignore slow ending two finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); touchEnd(1); clock.tick(500); touchEnd(2); expect(gestures).to.not.have.been.called; }); it('should ignore slow two finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); clock.tick(1500); touchEnd(1); touchEnd(2); expect(gestures).to.not.have.been.called; }); }); describe('Three finger tap', function () { it('should handle three finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); touchStart(3, 40.0, 40.0); expect(gestures).to.not.have.been.called; touchEnd(1); expect(gestures).to.not.have.been.called; touchEnd(2); expect(gestures).to.not.have.been.called; touchEnd(3); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'threetap', clientX: 30.0, clientY: 40.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'threetap', clientX: 30.0, clientY: 40.0 } })); }); it('should ignore slow starting three finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); clock.tick(500); touchStart(3, 40.0, 40.0); touchEnd(1); touchEnd(2); touchEnd(3); expect(gestures).to.not.have.been.called; }); it('should ignore slow ending three finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); touchStart(3, 40.0, 40.0); touchEnd(1); touchEnd(2); clock.tick(500); touchEnd(3); expect(gestures).to.not.have.been.called; }); it('should ignore three finger drag', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); touchStart(3, 40.0, 40.0); touchMove(1, 120.0, 130.0); touchMove(2, 130.0, 150.0); touchMove(3, 140.0, 140.0); touchEnd(1); touchEnd(2); touchEnd(3); expect(gestures).to.not.have.been.called; }); it('should ignore slow three finger tap', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 50.0); touchStart(3, 40.0, 40.0); clock.tick(1500); touchEnd(1); touchEnd(2); touchEnd(3); expect(gestures).to.not.have.been.called; }); }); describe('Single finger drag', function () { it('should handle horizontal single finger drag', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(1, 40.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(1, 80.0, 30.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'drag', clientX: 20.0, clientY: 30.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag', clientX: 80.0, clientY: 30.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'drag', clientX: 80.0, clientY: 30.0 } })); }); it('should handle vertical single finger drag', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(1, 20.0, 50.0); expect(gestures).to.not.have.been.called; touchMove(1, 20.0, 90.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'drag', clientX: 20.0, clientY: 30.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag', clientX: 20.0, clientY: 90.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'drag', clientX: 20.0, clientY: 90.0 } })); }); it('should handle diagonal single finger drag', function () { touchStart(1, 120.0, 130.0); expect(gestures).to.not.have.been.called; touchMove(1, 90.0, 100.0); expect(gestures).to.not.have.been.called; touchMove(1, 60.0, 70.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'drag', clientX: 120.0, clientY: 130.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag', clientX: 60.0, clientY: 70.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'drag', clientX: 60.0, clientY: 70.0 } })); }); }); describe('Long press', function () { it('should handle long press', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; clock.tick(1500); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gesturestart', detail: { type: 'longpress', clientX: 20.0, clientY: 30.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'longpress', clientX: 20.0, clientY: 30.0 } })); }); it('should handle long press drag', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; clock.tick(1500); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gesturestart', detail: { type: 'longpress', clientX: 20.0, clientY: 30.0 } })); gestures.resetHistory(); touchMove(1, 120.0, 50.0); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gesturemove', detail: { type: 'longpress', clientX: 120.0, clientY: 50.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'longpress', clientX: 120.0, clientY: 50.0 } })); }); }); describe('Two finger drag', function () { it('should handle fast and distinct horizontal two finger drag', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(1, 40.0, 30.0); touchMove(2, 50.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(2, 90.0, 30.0); touchMove(1, 80.0, 30.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 60.0, magnitudeY: 0.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 60.0, magnitudeY: 0.0 } })); }); it('should handle fast and distinct vertical two finger drag', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 30.0); expect(gestures).to.not.have.been.called; touchMove(1, 20.0, 100.0); touchMove(2, 30.0, 40.0); expect(gestures).to.not.have.been.called; touchMove(2, 30.0, 90.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 0.0, magnitudeY: 65.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'twodrag', clientX: 25.0, clientY: 30.0, magnitudeX: 0.0, magnitudeY: 65.0 } })); }); it('should handle fast and distinct diagonal two finger drag', function () { touchStart(1, 120.0, 130.0); touchStart(2, 130.0, 130.0); expect(gestures).to.not.have.been.called; touchMove(1, 80.0, 90.0); touchMove(2, 100.0, 130.0); expect(gestures).to.not.have.been.called; touchMove(2, 60.0, 70.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 125.0, clientY: 130.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 125.0, clientY: 130.0, magnitudeX: -55.0, magnitudeY: -50.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'twodrag', clientX: 125.0, clientY: 130.0, magnitudeX: -55.0, magnitudeY: -50.0 } })); }); it('should ignore fast almost two finger dragging', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 30.0); touchMove(1, 80.0, 30.0); touchMove(2, 70.0, 30.0); touchEnd(1); touchEnd(2); expect(gestures).to.not.have.been.called; clock.tick(1500); expect(gestures).to.not.have.been.called; }); it('should handle slow horizontal two finger drag', function () { touchStart(1, 50.0, 40.0); touchStart(2, 60.0, 40.0); touchMove(1, 80.0, 40.0); touchMove(2, 110.0, 40.0); expect(gestures).to.not.have.been.called; clock.tick(60); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 55.0, clientY: 40.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 55.0, clientY: 40.0, magnitudeX: 40.0, magnitudeY: 0.0 } })); }); it('should handle slow vertical two finger drag', function () { touchStart(1, 40.0, 40.0); touchStart(2, 40.0, 60.0); touchMove(2, 40.0, 80.0); touchMove(1, 40.0, 100.0); expect(gestures).to.not.have.been.called; clock.tick(60); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 40.0, clientY: 50.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 40.0, clientY: 50.0, magnitudeX: 0.0, magnitudeY: 40.0 } })); }); it('should handle slow diagonal two finger drag', function () { touchStart(1, 50.0, 40.0); touchStart(2, 40.0, 60.0); touchMove(1, 70.0, 60.0); touchMove(2, 90.0, 110.0); expect(gestures).to.not.have.been.called; clock.tick(60); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag', clientX: 45.0, clientY: 50.0, magnitudeX: 0.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag', clientX: 45.0, clientY: 50.0, magnitudeX: 35.0, magnitudeY: 35.0 } })); }); it('should ignore too slow two finger drag', function () { touchStart(1, 20.0, 30.0); clock.tick(500); touchStart(2, 30.0, 30.0); touchMove(1, 40.0, 30.0); touchMove(2, 50.0, 30.0); touchMove(1, 80.0, 30.0); expect(gestures).to.not.have.been.called; }); }); describe('Pinch', function () { it('should handle pinching distinctly and fast inwards', function () { touchStart(1, 0.0, 0.0); touchStart(2, 130.0, 130.0); expect(gestures).to.not.have.been.called; touchMove(1, 50.0, 40.0); touchMove(2, 100.0, 130.0); expect(gestures).to.not.have.been.called; touchMove(2, 60.0, 70.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'pinch', clientX: 65.0, clientY: 65.0, magnitudeX: 130.0, magnitudeY: 130.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'pinch', clientX: 65.0, clientY: 65.0, magnitudeX: 10.0, magnitudeY: 30.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'pinch', clientX: 65.0, clientY: 65.0, magnitudeX: 10.0, magnitudeY: 30.0 } })); }); it('should handle pinching fast and distinctly outwards', function () { touchStart(1, 100.0, 100.0); touchStart(2, 110.0, 100.0); expect(gestures).to.not.have.been.called; touchMove(1, 130.0, 70.0); touchMove(2, 0.0, 200.0); expect(gestures).to.not.have.been.called; touchMove(1, 180.0, 20.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'pinch', clientX: 105.0, clientY: 100.0, magnitudeX: 10.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'pinch', clientX: 105.0, clientY: 100.0, magnitudeX: 180.0, magnitudeY: 180.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'pinch', clientX: 105.0, clientY: 100.0, magnitudeX: 180.0, magnitudeY: 180.0 } })); }); it('should ignore fast almost pinching', function () { touchStart(1, 20.0, 30.0); touchStart(2, 130.0, 130.0); touchMove(1, 80.0, 70.0); touchEnd(1); touchEnd(2); expect(gestures).to.not.have.been.called; clock.tick(1500); expect(gestures).to.not.have.been.called; }); it('should handle pinching inwards slowly', function () { touchStart(1, 0.0, 0.0); touchStart(2, 130.0, 130.0); touchMove(1, 50.0, 40.0); touchMove(2, 100.0, 130.0); expect(gestures).to.not.have.been.called; clock.tick(60); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'pinch', clientX: 65.0, clientY: 65.0, magnitudeX: 130.0, magnitudeY: 130.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'pinch', clientX: 65.0, clientY: 65.0, magnitudeX: 50.0, magnitudeY: 90.0 } })); }); it('should handle pinching outwards slowly', function () { touchStart(1, 100.0, 130.0); touchStart(2, 110.0, 130.0); touchMove(2, 200.0, 130.0); expect(gestures).to.not.have.been.called; clock.tick(60); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'pinch', clientX: 105.0, clientY: 130.0, magnitudeX: 10.0, magnitudeY: 0.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'pinch', clientX: 105.0, clientY: 130.0, magnitudeX: 100.0, magnitudeY: 0.0 } })); }); it('should ignore pinching too slowly', function () { touchStart(1, 0.0, 0.0); clock.tick(500); touchStart(2, 130.0, 130.0); touchMove(2, 100.0, 130.0); touchMove(1, 50.0, 40.0); expect(gestures).to.not.have.been.called; }); }); describe('Ignoring', function () { it('should ignore extra touches during gesture', function () { touchStart(1, 20.0, 30.0); touchMove(1, 40.0, 30.0); touchMove(1, 80.0, 30.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'drag' } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag' } })); gestures.resetHistory(); touchStart(2, 10.0, 10.0); expect(gestures).to.not.have.been.called; touchMove(1, 100.0, 50.0); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag', clientX: 100.0, clientY: 50.0 } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'drag', clientX: 100.0, clientY: 50.0 } })); }); it('should ignore extra touches when waiting for gesture to end', function () { touchStart(1, 20.0, 30.0); touchStart(2, 30.0, 30.0); touchMove(1, 40.0, 30.0); touchMove(2, 90.0, 30.0); touchMove(1, 80.0, 30.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'twodrag' } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'twodrag' } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'twodrag' } })); gestures.resetHistory(); touchStart(3, 10.0, 10.0); touchEnd(3); expect(gestures).to.not.have.been.called; }); it('should ignore extra touches after gesture', function () { touchStart(1, 20.0, 30.0); touchMove(1, 40.0, 30.0); touchMove(1, 80.0, 30.0); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'drag' } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag' } })); gestures.resetHistory(); touchStart(2, 10.0, 10.0); expect(gestures).to.not.have.been.called; touchMove(1, 100.0, 50.0); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gesturemove', detail: { type: 'drag' } })); gestures.resetHistory(); touchEnd(1); expect(gestures).to.have.been.calledOnceWith( sinon.match({ type: 'gestureend', detail: { type: 'drag' } })); gestures.resetHistory(); touchEnd(2); expect(gestures).to.not.have.been.called; // Check that everything is reseted after trailing ignores are released touchStart(3, 20.0, 30.0); touchEnd(3); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'onetap' } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'onetap' } })); }); it('should properly reset after a gesture', function () { touchStart(1, 20.0, 30.0); expect(gestures).to.not.have.been.called; touchEnd(1); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'onetap', clientX: 20.0, clientY: 30.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'onetap', clientX: 20.0, clientY: 30.0 } })); gestures.resetHistory(); touchStart(2, 70.0, 80.0); expect(gestures).to.not.have.been.called; touchEnd(2); expect(gestures).to.have.been.calledTwice; expect(gestures.firstCall).to.have.been.calledWith( sinon.match({ type: 'gesturestart', detail: { type: 'onetap', clientX: 70.0, clientY: 80.0 } })); expect(gestures.secondCall).to.have.been.calledWith( sinon.match({ type: 'gestureend', detail: { type: 'onetap', clientX: 70.0, clientY: 80.0 } })); }); }); });