From 730916b4a4770dc16ff62305047b980047a0592e Mon Sep 17 00:00:00 2001 From: Mitchal Date: Thu, 11 Jan 2018 15:38:21 +0100 Subject: [PATCH] feat(touchEvents): complete touch event support for Chrome only, and subsequently remove usage of Ph --- src/acceptance.js | 133 ++++++++++++++++++-------- testem/testem-dev.js | 2 +- testem/testem.js | 5 +- tests/acceptance/mouse-events.test.js | 12 +-- tests/acceptance/touch-events.test.js | 63 ++++++++---- 5 files changed, 153 insertions(+), 62 deletions(-) diff --git a/src/acceptance.js b/src/acceptance.js index a9010dc..6cff677 100644 --- a/src/acceptance.js +++ b/src/acceptance.js @@ -32,7 +32,7 @@ let history; */ export function scaleWindowWidth(scale) { andThen(() => { - var $root = $(root); + let $root = $(root); const currentWidth = $root.width(); const newWidth = currentWidth * scale; $root.css('width', `${newWidth}px`); @@ -162,20 +162,71 @@ export function mouseMove(selector, options) { triggerMouseEvent(mouseMove, selector, options); } +/** + * Waits for an element to show up, and then simulates a user touch start by triggering a touch event on that element. + * @param {string|jQuery} selector The jQuery selector or jQuery object to simulate touch on. + * Note that the selector or jQuery object must represent exactly one (1) element in the app, or the call will fail. + * @param {object} [options] Any options to pass along to the simulated touch event. + * @example + * touchStart('.element-to-touch', {touches: [{ clientX: 1337, clientY: 1338 }], changedTouches: [{ clientX: 1337, clientY: 1338}]}); + */ +export function touchStart(selector, options) { + triggerTouchEvent(touchStart, selector, options); +} + +/** + * Waits for an element to show up, and then simulates a user touch move by triggering a touch event on that element. + * @param {string|jQuery} selector The jQuery selector or jQuery object to simulate touch on. + * Note that the selector or jQuery object must represent exactly one (1) element in the app, or the call will fail. + * @param {object} [options] Any options to pass along to the simulated touch event. + * @example + * touchMove('.element-to-touch', {touches: [{ clientX: 1337, clientY: 1338 }], changedTouches: [{ clientX: 1337, clientY: 1338}]}); + */ +export function touchMove(selector, options) { + triggerTouchEvent(touchMove, selector, options); +} + +/** + * Waits for an element to show up, and then simulates a user touch cancel by triggering a touch event on that element. + * @param {string|jQuery} selector The jQuery selector or jQuery object to simulate touch on. + * Note that the selector or jQuery object must represent exactly one (1) element in the app, or the call will fail. + * @param {object} [options] Any options to pass along to the simulated touch event. + * @example + * touchCancel('.element-to-touch', {touches: [{ clientX: 1337, clientY: 1338 }], changedTouches: [{ clientX: 1337, clientY: 1338}]}); + */ +export function touchCancel(selector, options) { + triggerTouchEvent(touchCancel, selector, options); +} + +/** + * Waits for an element to show up, and then simulates a user touch end by triggering a touch event on that element. + * @param {string|jQuery} selector The jQuery selector or jQuery object to simulate touch on. + * Note that the selector or jQuery object must represent exactly one (1) element in the app, or the call will fail. + * @param {object} [options] Any options to pass along to the simulated touch event. + * @example + * touchEnd('.element-to-touch', {touches: [{ clientX: 1337, clientY: 1338 }], changedTouches: [{ clientX: 1337, clientY: 1338}]}); + */ +export function touchEnd(selector, options) { + triggerTouchEvent(touchEnd, selector, options); +} + function triggerMouseEvent( exportedFunction, selector, options, - eventsToTriggerFirst = []) { - triggerEvent(exportedFunction, selector, options, createMouseEvent, eventsToTriggerFirst) + eventsToTriggerFirst = [] +) { + triggerEvent( + exportedFunction, + selector, + options, + createMouseEvent, + eventsToTriggerFirst + ); } -function triggerTouchEvent( - exportedFunction, - selector, - options, - eventsToTriggerFirst = []) { - triggerEvent(exportedFunction, selector, options, createTouchEvent, eventsToTriggerFirst) +function triggerTouchEvent(exportedFunction, selector, options) { + triggerEvent(exportedFunction, selector, options, createTouchEvent); } function triggerEvent( @@ -202,8 +253,9 @@ function triggerEvent( : options; function triggerEvent(eventName) { - const event = createEvent(eventName, evaluatedOptions); - jqueryElement[0].dispatchEvent(event); + const target = jqueryElement[0]; + const event = createEvent(eventName, evaluatedOptions, target); + target.dispatchEvent(event); } const eventsToTrigger = eventsToTriggerFirst.concat([eventName]); @@ -214,18 +266,6 @@ function triggerEvent( }); } -/** - * Waits for an element to show up, and then simulates a user touch start by triggering a mouse event on that element. - * @param {string|jQuery} selector The jQuery selector or jQuery object to simulate touch start on. - * Note that the selector or jQuery object must represent exactly one (1) element in the app, or the call will fail. - * @param {object} [options] Any options to pass along to the simulated mouse event. - * @example - * mouseDown('.element-to-touch-start-on', {touches: [{ clientX: 1337, clientY: 1338 }]}); - */ -export function touchStart(selector, options) { - triggerTouchEvent(touchStart, selector, options); -} - /** * Waits for an input element to show up, and then simulates a user filling in the value of that input. * @param {string|jQuery} selector The jQuery selector or jQuery object to fill in. @@ -364,7 +404,7 @@ function createMouseEvent( relatedTarget = document.body.parentNode, } = {} ) { - var result; + let result; try { result = new MouseEvent(type, options); @@ -392,24 +432,41 @@ function createMouseEvent( return result; } -function createTouchEvent( - type, - { - touches = [{ - clientX: 0, - clientY: 0, - }], - } = {} -) { - var result; +function createTouchEvent(type, options = {}, target) { + let result; try { - result = new TouchEvent(type, {touches}); + const touches = mapTouchesToRealTouchInstances( + options.touches || [], + target + ); + + const changedTouches = mapTouchesToRealTouchInstances( + options.changedTouches || [], + target + ); + + const targetTouches = mapTouchesToRealTouchInstances( + options.targetTouches || [], + target + ); + result = new TouchEvent(type, { + touches, + changedTouches, + targetTouches, + }); } catch (e) { - console.log(touches) - result = document.createEvent('TouchEvent'); - result.initTouchEvent(type, {touches}); + throw new Error( + 'acast-test-helpers: Touch event helpers are currently only supported in Chrome.' + ); } return result; } + +function mapTouchesToRealTouchInstances(touches, target) { + let nextIdentifier = 0; + return touches.map( + touch => new Touch({ target, identifier: nextIdentifier++, ...touch }) + ); +} diff --git a/testem/testem-dev.js b/testem/testem-dev.js index a8b605c..c3c67ba 100644 --- a/testem/testem-dev.js +++ b/testem/testem-dev.js @@ -1,6 +1,6 @@ const baseConfig = require('./testem'); module.exports = Object.assign({}, baseConfig, { - launch_in_dev: ['PhantomJS', 'Chrome'], + launch_in_dev: ['Chrome'], on_start: './node_modules/.bin/webpack --watch' }); diff --git a/testem/testem.js b/testem/testem.js index 0e5d393..f7e085b 100644 --- a/testem/testem.js +++ b/testem/testem.js @@ -5,5 +5,8 @@ module.exports = { 'tmp/tests-bundle.js' ]), framework: 'mocha', - launch_in_ci: ['PhantomJS'], + launch_in_ci: ['Chrome'], + browser_args: { + 'Chrome': [ '--headless', '--disable-gpu', '--remote-debugging-port=9222' ], + }, }; diff --git a/tests/acceptance/mouse-events.test.js b/tests/acceptance/mouse-events.test.js index d8ee9e6..46734cf 100644 --- a/tests/acceptance/mouse-events.test.js +++ b/tests/acceptance/mouse-events.test.js @@ -31,9 +31,9 @@ describe('Mouse Events', () => { describeMouseEventHelper(mouseUp, 'mouseup'); describeMouseEventHelper(mouseMove, 'mousemove'); - function describeMouseEventHelper(func, eventName, extraTests = ()=> { + function describeMouseEventHelper(helperToTest, eventName, extraTests = ()=> { }) { - describe(func.name, () => { + describe(helperToTest.name, () => { setupAsync(); let elementToInteractWith; @@ -56,7 +56,7 @@ describe('Mouse Events', () => { attachElementToBody(); const spy = sinon.spy(); $(elementToInteractWith).on(eventName, spy); - func('.element-to-interact-with'); + helperToTest('.element-to-interact-with'); andThen(() => { expect(spy).to.have.been.calledOnce(); }); @@ -65,7 +65,7 @@ describe('Mouse Events', () => { it('waits until element shows up before trying to interact with it', () => { const spy = sinon.spy(); $(elementToInteractWith).on(eventName, spy); - func('.element-to-interact-with'); + helperToTest('.element-to-interact-with'); andThen(() => { expect(spy).to.have.been.calledOnce(); }); @@ -80,7 +80,7 @@ describe('Mouse Events', () => { expect(e.clientY).to.equal(1338); done(); }); - func('.element-to-interact-with', { clientX: 1337, clientY: 1338 }); + helperToTest('.element-to-interact-with', { clientX: 1337, clientY: 1338 }); }); it('evaluates options lazily if passed as function', (done) => { @@ -90,7 +90,7 @@ describe('Mouse Events', () => { screenX = 1337; }); - func('.element-to-interact-with', () => ({ screenX })); + helperToTest('.element-to-interact-with', () => ({ screenX })); const element = attachElementToBody(); $(element).on(eventName, e => { diff --git a/tests/acceptance/touch-events.test.js b/tests/acceptance/touch-events.test.js index 6a60859..b28ece0 100644 --- a/tests/acceptance/touch-events.test.js +++ b/tests/acceptance/touch-events.test.js @@ -1,10 +1,21 @@ -import {touchStart, andThen, setupAsync, jQuery as $} from '../../src' +import { + touchStart, + touchMove, + touchCancel, + touchEnd, + andThen, + setupAsync, + jQuery as $ +} from '../../src' describe('Touch Events', () => { - describeMouseEventHelper(touchStart, 'touchstart'); + describeTouchEventHelper(touchStart, 'touchstart'); + describeTouchEventHelper(touchMove, 'touchmove'); + describeTouchEventHelper(touchCancel, 'touchcancel'); + describeTouchEventHelper(touchEnd, 'touchend'); - function describeMouseEventHelper(func, eventName, extraTests = ()=> {}) { - describe(func.name, () => { + function describeTouchEventHelper(helperToTest, eventName) { + describe(helperToTest.name, () => { setupAsync(); let elementToInteractWith; @@ -26,8 +37,8 @@ describe('Touch Events', () => { it(`triggers ${eventName} event on selected element`, () => { attachElementToBody(); const spy = sinon.spy(); - $(elementToInteractWith).on(eventName, spy); - func('.element-to-interact-with'); + elementToInteractWith.addEventListener(eventName, spy); + helperToTest('.element-to-interact-with'); andThen(() => { expect(spy).to.have.been.calledOnce(); }); @@ -36,7 +47,7 @@ describe('Touch Events', () => { it('waits until element shows up before trying to interact with it', () => { const spy = sinon.spy(); $(elementToInteractWith).on(eventName, spy); - func('.element-to-interact-with'); + helperToTest('.element-to-interact-with'); andThen(() => { expect(spy).to.have.been.calledOnce(); }); @@ -46,12 +57,32 @@ describe('Touch Events', () => { it('takes extra options as parameters', (done) => { const element = attachElementToBody(); - $(element).on(eventName, e => { + const handleEvent = e => { expect(e.touches[0].clientX).to.equal(1337); expect(e.touches[0].clientY).to.equal(1338); + expect(e.touches[0].target).to.equal(element); + expect(e.touches[0].identifier).to.equal(0); + + expect(e.changedTouches[0].clientX).to.equal(1337); + expect(e.changedTouches[0].clientY).to.equal(1338); + expect(e.changedTouches[0].target).to.equal(element); + expect(e.changedTouches[0].identifier).to.equal(42); + + expect(e.targetTouches[0].clientX).to.equal(1337); + expect(e.targetTouches[0].clientY).to.equal(1338); + expect(e.targetTouches[0].target).to.equal(element); + expect(e.targetTouches[0].identifier).to.equal(0); + + element.removeEventListener(eventName, handleEvent); done(); + }; + + element.addEventListener(eventName, handleEvent); + helperToTest('.element-to-interact-with', { + touches: [{ clientX: 1337, clientY: 1338 }], + changedTouches: [{ target: element, clientX: 1337, clientY: 1338, identifier: 42 }], + targetTouches: [{ target: element, clientX: 1337, clientY: 1338 }], }); - func('.element-to-interact-with', {touches: [{ clientX: 1337, clientY: 1338 }]}); }); it('evaluates options lazily if passed as function', (done) => { @@ -60,17 +91,17 @@ describe('Touch Events', () => { andThen(() => { screenX = 1337; }); - - func('.element-to-interact-with', () => ({ screenX })); + + helperToTest('.element-to-interact-with', () => ({ touches: [{ screenX }] })); const element = attachElementToBody(); - $(element).on(eventName, e => { - expect(e.screenX).to.equal(1337); + const handleEvent = e => { + expect(e.touches[0].screenX).to.equal(1337); + element.removeEventListener(eventName, handleEvent); done(); - }); + }; + element.addEventListener(eventName, handleEvent); }); - - extraTests(attachElementToBody); }); } })