UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

532 lines (430 loc) 23.6 kB
/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ /** @class Siesta.Test.UserAgent.Touch This is a mixin, providing the touch events simulation functionality. */ Role('Siesta.Test.UserAgent.Touch', { requires : [ 'normalizeElement' ], has: { notSupportedWarned : false }, methods: { checkTouchEventsSupport : function () { var supports = Siesta.Project.Browser.FeatureSupport().supports var root = this.getRootTest() if (!supports.TouchEvents && !supports.PointerEvents && !supports.MSPointerEvents && !root.notSupportedWarned) { root.notSupportedWarned = true this.warn("Touch events are not supported by browser. For Chrome, you can enable them, by launching it with: --args --touch-events") } }, /** * This method simulates a `touchstart` for the passed target, first waiting to make sure target exists and is reachable * * @param {Siesta.Test.ActionTarget} target Target for this action * @param {Function} callback (optional) A function to call after action. * @param {Object} scope (optional) The scope for the callback * @param {Object} options (optional) Any options that will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or * ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ touchStart : function (target, callback, scope, options, offset, performTargetCheck) { var me = this; this.checkTouchEventsSupport() target = target || this.getCursorPagePosition() if (performTargetCheck !== false && callback) { this.waitForTargetAndSyncMousePosition( target, offset, this.touchStart, [ target, callback, scope, options, offset, false ] ); return; } var context = this.getNormalizedTopElementInfo(target, true, 'tap', offset); if (!context) { callback && callback.call(scope || this); return; } return me.runPromiseAsync( new Promise(function (resolve) { me.simulator.touchStart(context.el, offset, options, context), resolve(); }), 'touchStart', callback, scope ) }, /** * This method simulates a `touchend` for a single active touch * */ touchEnd : function (callback) { var me = this; return me.runPromiseAsync( new Promise(function (resolve) { me.simulator.touchEnd(); resolve(); }), 'touchEnd', callback ) }, /** * This method taps the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget} * * @param {Siesta.Test.ActionTarget} target Target for this action * @param {Function} callback (optional) A function to call after action. * @param {Object} scope (optional) The scope for the callback * @param {Object} options (optional) Any options that will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or * ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ tap : function (target, callback, scope, options, offset, performTargetCheck) { this.checkTouchEventsSupport() target = target || this.getCursorPagePosition() if (performTargetCheck !== false && callback) { this.waitForTargetAndSyncMousePosition( target, offset, this.tap, [ target, callback, scope, options, offset, false ] ); return; } var context = this.getNormalizedTopElementInfo(target, true, 'tap', offset); if (!context) { callback && callback.call(scope || this); return; } return this.runPromiseAsync( this.simulator.simulateTap(context, options), 'tap', callback, scope ) }, /** * This method double taps the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget} * * @param {Siesta.Test.ActionTarget} target Target for this action * @param {Function} callback (optional) A function to call after action. * @param {Object} scope (optional) The scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ doubleTap : function (target, callback, scope, options, offset, performTargetCheck) { this.checkTouchEventsSupport() target = target || this.getCursorPagePosition() if (performTargetCheck !== false && callback) { this.waitForTargetAndSyncMousePosition( target, offset, this.doubleTap, [ target, callback, scope, options, offset, false ] ); return; } var context = this.getNormalizedTopElementInfo(target, true, 'doubleTap', offset); if (!context) { callback && callback.call(scope || this); return; } return this.runPromiseAsync( this.simulator.simulateDoubleTap(context, options), 'doubleTap', callback, scope ) }, // backward-compat with SenchaTouch class, which used to have all lower-cased method longpress : function () { return this.longPress.apply(this, arguments) }, /** * This performs a long press on the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget} * * @param {Siesta.Test.ActionTarget} target Target for this action * @param {Function} callback (optional) A function to call after action. * @param {Object} scope (optional) The scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ longPress : function (target, callback, scope, options, offset, performTargetCheck) { this.checkTouchEventsSupport() target = target || this.getCursorPagePosition() if (performTargetCheck !== false && callback) { this.waitForTargetAndSyncMousePosition( target, offset, this.longPress, [ target, callback, scope, options, offset, false ] ); return; } var context = this.getNormalizedTopElementInfo(target, true, 'longPress', offset); if (!context) { callback && callback.call(scope || this); return; } return this.runPromiseAsync( this.simulator.simulateLongPress(context, options), 'longPress', callback, scope ) }, /** * This method performs a pinch between the two specified points. It draws a line between the specified points and then moves 2 touches along that line, * so that the final distance between the touches becomes `scale * original distance`. * * This method can be called either in the full form with 2 different targets: * t.pinch("#grid > .col1", "#grid > .col2", 3, function () { ... }) * or, in the short form, where the 2nd target argument is omitted: * t.pinch("#grid > .col1", 3, function () { ... }) * In the latter form, `target2` is considered to be the same as `target1`. * * If `target1` and `target2` are the same, and no offsets are provided, offsets are set to the following values: * offset1 = [ '25%', '50%' ] offset2 = [ '75%', '50%' ] * * * @param {Siesta.Test.ActionTarget} target1 First point for pinch * @param {Siesta.Test.ActionTarget} target2 Second point for pinch. Can be omitted, in this case both points will belong to `target1` * @param {Number} scale The multiplier for a final distance between the points * @param {Function} callback A function to call after the pinch has completed * @param {Object} scope A scope for the `callback` * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset1 An X,Y offset relative to the target1. Example: [20, 20] for 20px or ["50%", "100%-2"] * for the point in the center horizontally and 2px from the bottom edge. * @param {Array} offset2 An X,Y offset relative to the target1. Example: [20, 20] for 20px or ["50%", "100%-2"] * for the point in the center horizontally and 2px from the bottom edge. */ pinch : function (target1, target2, scale, callback, scope, options, offset1, offset2) { this.checkTouchEventsSupport() var me = this; if (this.typeOf(target2) === 'Number') { offset2 = offset1 offset1 = options options = scope scope = callback callback = scale scale = target2 target2 = target1 } if (target2 == null) target2 = target1 if (target1 === target2 && !offset1 && !offset2) { offset1 = [ '25%', '50%' ] offset2 = [ '75%', '50%' ] } var context1 = this.getNormalizedTopElementInfo(target1, true, 'pinch: target1', offset1); var context2 = this.getNormalizedTopElementInfo(target2, true, 'pinch: target2', offset2); if (!context1 || !context2) { var R = Siesta.Resource('Siesta.Test.Browser'); this.waitFor({ method : function () { var el1 = me.normalizeElement(target1, true) var el2 = me.normalizeElement(target2, true) return el1 && me.elementIsTop(el1, true, offset) && el2 && me.elementIsTop(el2, true, offset) }, callback : function () { me.pinch(target1, target2, scope, callback, scope, options, offset1, offset2) }, assertionName : 'waitForTarget', description : ' ' + R.get('target') + ' "' + target1 + '" and "' + target2 + '" ' + R.get('toAppear') }); return } return this.runPromiseAsync( this.simulator.simulatePinch(context1, context2, options), 'pinch', callback, scope ) }, /** * This method will simulate a drag and drop operation between either two points or two DOM elements. * * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value for the drag starting point * @param {Siesta.Test.ActionTarget} target {@link Siesta.Test.ActionTarget} value for the drag end point * @param {Function} callback A function to call after the drag operation is completed. * @param {Object} scope (optional) the scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation. * @param {Array} sourceOffset (optional) An X,Y offset relative to the source. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. * @param {Array} targetOffset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ touchDragTo : function (source, target, callback, scope, options, dragOnly, sourceOffset, targetOffset) { var me = this var context1 = this.getNormalizedTopElementInfo(source, true, 'touchDragTo: source', sourceOffset); var context2 = this.getNormalizedTopElementInfo(target, true, 'touchDragTo: target', targetOffset); if (!context1 || !context2) { var R = Siesta.Resource('Siesta.Test.Browser'); this.waitFor({ method : function () { var el1 = me.normalizeElement(source, true) var el2 = me.normalizeElement(target, true) return el1 && me.elementIsTop(el1, true, sourceOffset) && el2 && me.elementIsTop(el2, true, targetOffset) }, callback : function () { me.touchDragTo(source, target, callback, scope, options, dragOnly, sourceOffset, targetOffset) }, assertionName : 'waitForTarget', description : ' ' + R.get('target') + ' "' + source + '" and "' + target + '" ' + R.get('toAppear') }); return } return this.runPromiseAsync( this.simulator.simulateTouchDrag(context1.localXY, context2.localXY, options, dragOnly), 'touchDragTo', callback, scope ) }, /** * This method will simulate a drag and drop operation from a point (or DOM element) and move by a delta. * * @param {Siesta.Test.ActionTarget} source {@link Siesta.Test.ActionTarget} value as the drag starting point * @param {Array} delta The amount to drag from the source coordinate, expressed as [ x, y ]. E.g. [ 50, 10 ] will drag 50px to the right and 10px down. * @param {Function} callback A function to call after the drag operation is completed. * @param {Object} scope (optional) the scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Boolean} dragOnly true to skip the mouseup and not finish the drop operation. * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to click in the center horizontally and 2px from the bottom edge. */ touchDragBy : function (source, delta, callback, scope, options, dragOnly, offset) { var me = this; var context = this.getNormalizedTopElementInfo(source, true, 'touchDragBy', offset); if (!context) { this.waitForTarget(source, function() { this.touchDragBy(source, delta, callback, scope, options, dragOnly, offset) }, this, null, offset) return } var sourceXY = context.globalXY; var targetXY = [ sourceXY[ 0 ] + delta[ 0 ], sourceXY[ 1 ] + delta[ 1 ] ]; return this.runPromiseAsync( this.simulator.simulateTouchDrag(sourceXY, targetXY, options, dragOnly), 'touchDragBy', callback, scope ) }, /** * This method will simulate a move the pointer/finger to the passed target. * * @param {Siesta.Test.ActionTarget} target {@link Siesta.Test.ActionTarget} value for the drag end point * @param {Function} callback A function to call after the drag operation is completed. * @param {Object} scope (optional) the scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> * @param {Array} offset (optional) An X,Y offset relative to the target. Example: [20, 20] for 20px or ["50%", "100%-2"] to point in the center horizontally and 2px from the bottom edge. */ movePointerTo : function (target, callback, scope, options, offset, performTargetCheck) { var me = this if (performTargetCheck !== false && callback) { me.waitForTargetAndSyncMousePosition( target, null, me.movePointerTo, [ target, callback, scope, options, offset, false ] ); return; } var context = me.getNormalizedTopElementInfo(target, true, 'movePointerTo', offset); return me.runPromiseAsync( me.simulator.touchMoveTo(context.globalXY, options), 'touchMoveTo', callback, scope ) }, /** * This method will simulate moving the pointer/finger by the passed distance. * * @param {Array} delta The amount to drag from the source coordinate, expressed as [ x, y ]. E.g. [ 50, 10 ] will drag 50px to the right and 10px down. * @param {Function} callback A function to call after the drag operation is completed. * @param {Object} scope (optional) the scope for the callback * @param {Object} options (optional) Any optionsthat will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> */ movePointerBy : function (delta, callback, scope, options ) { return this.runPromiseAsync( this.simulator.touchMoveBy(delta, options), 'touchMoveBy', callback, scope ); }, /** * This method will simulate a swipe operation between either two points or on a single DOM element. * * @param {Siesta.Test.ActionTarget} target Target for this action * @param {String} direction Either 'left', 'right', 'up' or 'down' * @param {Function} callback A function to call after the swing operation is completed * @param {Object} scope (optional) the scope for the callback * @param {Object} options (optional) Any options that will be used when simulating the event. For information about possible * config options, please see: <https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent> */ swipe : function (target, direction, callback, scope, options, performTargetCheck) { this.checkTouchEventsSupport() target = target || this.getCursorPagePosition() if (performTargetCheck !== false && callback) { this.waitForTargetAndSyncMousePosition( target, null, this.swipe, [ target, direction, callback, scope, options, false ] ); return; } var context = this.getNormalizedTopElementInfo(target, true, 'swipe'); if (!context) { callback && callback.call(scope || this); return; } var Ext = this.Ext() var R = Siesta.Resource('Siesta.Test.SenchaTouch') var box = Ext.fly(context.el).getBox(), x = box.x, y = box.y, width = box.width, height = box.height, centerX = x + width / 2, centerY = y + height / 2, start, end, edgeCoef = 0.1 // Since this method accepts elements as target, we need to assure that we swipe at least about 150px // using Math.max below etc switch (direction) { case 'u': case 'up': start = [ centerX, y + height * (1 - edgeCoef) ]; end = [ centerX, y + height * edgeCoef ]; end[ 1 ] = Math.min(start[ 1 ] - 100, end[ 1 ]); break; case 'd': case 'down': start = [ centerX, y + height * edgeCoef ]; end = [ centerX, y + height * (1 - edgeCoef) ]; end[ 1 ] = Math.max(start[ 1 ] + 100, end[ 1 ]); break; case 'r': case 'right': start = [ x + width * edgeCoef, centerY ]; end = [ x + width * (1 - edgeCoef), centerY ]; end[ 0 ] = Math.max(start[ 0 ] + 100, end[ 0 ]); break; case 'l': case 'left': start = [ x + width * (1 - edgeCoef), centerY ]; end = [ x + width * edgeCoef, centerY ]; end[ 0 ] = Math.min(start[ 0 ] - 100, end[ 0 ]); break; default: throw R.get('invalidSwipeDir') + ': ' + direction; } return this.touchDragTo(start, end, callback, scope, options); } } });