siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
532 lines (430 loc) • 23.6 kB
JavaScript
/*
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);
}
}
});