UNPKG

siesta-lite

Version:

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

659 lines (500 loc) 24.5 kB
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>The source code</title> <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="../resources/prettify/prettify.js"></script> <style type="text/css"> .highlight { display: block; background-color: #ddd; } </style> <script type="text/javascript"> function highlight() { document.getElementById(location.hash.replace(/#/, "")).className = "highlight"; } </script> </head> <body onload="prettyPrint(); highlight();"> <pre class="prettyprint lang-js">/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ // First fire a mouseover + mousemove on the target // Based on Chrome&#39;s behavior (function () { var postTapSequence = [&#39;mouseover&#39;, /*&#39;mouseenter&#39;*/, &#39;mousemove&#39;, &#39;mousedown&#39;, &#39;mouseup&#39;, &#39;click&#39;]; var postLongPressSequence = [&#39;mouseover&#39;, /*&#39;mouseenter&#39;*/, &#39;mousemove&#39;, &#39;contextmenu&#39;]; var postLongPressSequenceWithTouchStartPrevented = [&#39;mousemove&#39;, &#39;contextmenu&#39;]; Role(&#39;Siesta.Test.Simulate.Touch&#39;, { requires : [], has : { touchEventNamesMap : { lazy : &#39;this.buildTouchEventNamesMap&#39; }, currentTouchId : 1, activeTouches : Joose.I.Object, longPressDelay : { init : 1500, is : &#39;rw&#39; } }, methods : { simulateTap : function (context, options) { var queue = new Siesta.Util.Queue({ deferer : this.test.originalSetTimeout, deferClearer : this.test.originalClearTimeout, interval : 30, observeTest : this.test }) var me = this; var id queue.addStep({ processor : function () { id = me.touchStart(null, null, options, context) } }) queue.addStep({ processor : function () { me.touchEnd(id, options) } }) return new Promise(function (resolve, reject) { queue.run(resolve) }) }, simulateDoubleTap : function (context, options) { var queue = new Siesta.Util.Queue({ deferer : this.test.originalSetTimeout, deferClearer : this.test.originalClearTimeout, interval : 30, observeTest : this.test }) var me = this; var id queue.addStep({ processor : function () { id = me.touchStart(null, null, options, context) } }) queue.addStep({ processor : function () { me.touchEnd(id, options) } }) queue.addStep({ processor : function () { id = me.touchStart(null, null, options, context) } }) queue.addStep({ processor : function () { me.touchEnd(id, options) // iOS Safari fires dblclick event me.simulateEvent([], &#39;dblclick&#39;, options); } }) return new Promise(function (resolve, reject) { queue.run(resolve) }) }, simulateLongPress : function (context, options) { var queue = new Siesta.Util.Queue({ deferer : this.test.originalSetTimeout, deferClearer : this.test.originalClearTimeout, interval : 30, observeTest : this.test }) var me = this; var id queue.addStep({ processor : function () { id = me.touchStart(null, null, options, context) } }) queue.addDelayStep(this.getLongPressDelay()) queue.addStep({ processor : function () { me.touchEnd(id, options) } }) return new Promise(function (resolve, reject) { queue.run(resolve) }) }, simulatePinch : function (context1, context2, options) { var queue = new Siesta.Util.Queue({ deferer : this.test.originalSetTimeout, deferClearer : this.test.originalClearTimeout, interval : 30, observeTest : this.test }) var id1, id2 var dx = context1.localXY[ 0 ] - context2.localXY[ 0 ] var dy = context1.localXY[ 1 ] - context2.localXY[ 1 ] var distance = Math.sqrt(dx * dx + dy * dy) if (distance &lt; 1) distance = 1 var scaled = distance * scale var delta = (scaled - distance) / 2 var angle = Math.atan(dy / dx) var x1 = Math.round(context1.localXY[ 0 ] - delta * Math.cos(angle)) var y1 = Math.round(context1.localXY[ 1 ] - delta * Math.sin(angle)) var x2 = Math.round(context2.localXY[ 0 ] + delta * Math.cos(angle)) var y2 = Math.round(context2.localXY[ 1 ] + delta * Math.sin(angle)) var options2 = Joose.O.extend({}, options) queue.addStep({ processor : function () { id1 = me.touchStart(null, null, options, context1) id2 = me.touchStart(null, null, options2, context2) } }) queue.addAsyncStep({ processor : function (data) { var move1Done = false var move2Done = false me.touchMove(id1, x1, y1, function () { move1Done = true if (move1Done &amp;&amp; move2Done) data.next() }, null, options) me.touchMove(id2, x2, y2, function () { move2Done = true if (move1Done &amp;&amp; move2Done) data.next() }, null, options2) } }) queue.addStep({ processor : function () { me.touchEnd(id1, options) me.touchEnd(id2, options2) } }) return new Promise(function (resolve, reject) { queue.run(resolve) }) }, simulateTouchDrag : function (sourceXY, targetXY, options, dragOnly) { var me = this options = options || {}; // For drag operations we should always use the top level document.elementFromPoint var source = me.elementFromPoint(sourceXY[ 0 ], sourceXY[ 1 ], true); var target = me.elementFromPoint(targetXY[ 0 ], targetXY[ 1 ], true); var queue = new Siesta.Util.Queue({ deferer : this.test.originalSetTimeout, deferClearer : this.test.originalClearTimeout, interval : me.dragDelay, callbackDelay : me.afterActionDelay, observeTest : this.test }); var id queue.addStep({ processor : function () { id = me.touchStart(sourceXY, null, options, null) } }) queue.addAsyncStep({ processor : function (data) { me.touchMove(id, targetXY[ 0 ], targetXY[ 1 ], options).then(data.next) } }) queue.addStep({ processor : function () { // if `dragOnly` flag is set, do not finalize the touch, instead, pass the touch id // to the user in the callback (see below) if (!dragOnly) me.touchEnd(id, options, true) } }) return new Promise(function (resolve, reject) { queue.run(function () { // if `dragOnly` flag is set pass the touch id as promise result if (dragOnly) resolve(id) else resolve() }) }) }, touchStart : function (target, offset, options, context) { if (!context) context = this.test.getNormalizedTopElementInfo(target, true, &#39;touchStart&#39;, offset) options = Joose.O.extend({ clientX : context.localXY[0], clientY : context.localXY[1] }, options || {}) var event = this.simulateTouchEventGeneric(context.el, &#39;start&#39;, options, null) this.lastStartTouchWasOnNewTarget = !this.lastStartTouch || this.lastStartTouch.target !== context.el; this.lastStartTouchEvent = event; // IE11 compat check this.lastStartTouch = event.touches ? event.touches[0] : event; this.lastStartTouchTimeStamp = Date.now(); return event.pointerId != null ? event.pointerId : event.changedTouches[ 0 ].identifier }, touchEnd : function (touchId, options) { touchId = touchId || Object.keys(this.activeTouches)[0]; var touch = this.activeTouches[touchId] if (!touch) throw &quot;Can&#39;t find active touch: &quot; + touchId options = Joose.O.extend({ clientX : touch.clientX, clientY : touch.clientY }, options || {}) var target = touch.target if (this.test.nodeIsOrphan(target)) { touch.target = this.global.document.body } this.simulateTouchEventGeneric(touch.currentEl || touch.target, &#39;end&#39;, options, { touchId : touchId }) }, // Assumes an active touch exists touchMoveTo : function (toXY, options) { var touches = Object.keys(this.activeTouches); if (touches.length === 0) { throw new Error(&#39;No active touch detected&#39;); } var touch = this.activeTouches[ touches[0] ]; return this.touchMove(touches[0], toXY[0], toXY[1], options); }, // Assumes an active touch exists touchMoveBy : function (byXY, options) { var touches = Object.keys(this.activeTouches); if (touches.length === 0) { throw new Error(&#39;No active touch detected&#39;); } var touch = this.activeTouches[ touches[0] ]; return this.touchMove(touches[0],this.currentPosition[0] + byXY[0], this.currentPosition[1] + byXY[1], options); }, touchMove : function (touchId, toX, toY, options) { var touch = this.activeTouches[ touchId ] if (!touch) throw &quot;Can&#39;t find active touch: &quot; + touchId var me = this var overEls = [] return this.movePointerTemplate({ xy : [ touch.clientX, touch.clientY ], xy2 : [ toX, toY ], options : options || {}, overEls : overEls, interval : me.dragDelay, callbackDelay : me.afterActionDelay, pathBatchSize : me.pathBatchSize, onVoidOverEls : function () { return overEls = [] }, onPointerEnter : function (el, options) { }, onPointerLeave : function (el, options) { }, onPointerOver : function (el, options) { }, onPointerOut : function (el, options) { }, onPointerMove : function (el, options) { touch.clientX = options.clientX touch.clientY = options.clientY touch.pageX = me.viewportXtoPageX(options.clientX) touch.pageY = me.viewportYtoPageY(options.clientY) touch.currentEl = el me.simulateTouchEventGeneric(el, &#39;move&#39;, options, { touchId : touchId }) } }) }, // never used yet, should be called when touchMove goes out of the document touchCancel : function (touchId, options) { var touch = this.activeTouches[ touchId ] if (!touch) throw &quot;Can&#39;t find active touch: &quot; + touchId this.simulateTouchEventGeneric(touch.currentEl || touch.target, &#39;cancel&#39;, options, { touchId : touchId }) }, simulateTouchEvent : function (target, type, options, simOptions) { options = options || {} var global = this.global var doc = global.document var target = this.test.normalizeElement(target) var clientX, clientY if ((&quot;clientX&quot; in options) &amp;&amp; (&quot;clientY&quot; in options)) { clientX = options.clientX clientY = options.clientY } else { var center = this.test.findCenter(target); clientX = center[ 0 ] clientY = center[ 1 ] } var activeTouches = this.activeTouches var touch = simOptions.touch var touches = [] var targetTouches = [] for (var id in activeTouches) { var currentTouch = activeTouches[ id ] touches.push(currentTouch) if (currentTouch.target == target) targetTouches.push(currentTouch) } var config = { bubbles : true, cancelable : true, changedTouches : this.createTouchList([ touch ]), touches : this.createTouchList(touches), targetTouches : this.createTouchList(targetTouches), altKey : options.altKey, metaKey : options.metaKey, ctrlKey : options.ctrlKey, shiftKey : options.shiftKey }; try { var event = new global.TouchEvent(type, config) } catch(e) { // Legacy branch var event = new global.CustomEvent(type, { bubbles : true, cancelable : true }) Joose.O.extend(event, config); } target.dispatchEvent(event) return event }, createTouchList : function (touchList) { var doc = this.global.document if (doc.createTouch) { var touches = []; for (var i = 0; i &lt; touchList.length; i++) { var touchCfg = touchList[ i ]; touches.push(doc.createTouch( doc.defaultView || doc.parentWindow, touchCfg.target, touchCfg.identifier || this.currentTouchId++, touchCfg.pageX, touchCfg.pageY, touchCfg.screenX || touchCfg.pageX, touchCfg.screenY || touchCfg.pageY, touchCfg.clientX, touchCfg.clientY )) } return doc.createTouchList.apply(doc, touches); } return touchList; }, createTouch : function (target, clientX, clientY, identifier) { var config = { identifier : identifier || (this.currentTouchId++), target : target, clientX : clientX, clientY : clientY, screenX : 0, screenY : 0, // TODO should take scrolling into account pageX : clientX, pageY : clientY }; if (this.global.Touch) { return new this.global.Touch(config); } else { return config; } }, buildTouchEventNamesMap : function () { var supports = Siesta.Project.Browser.FeatureSupport().supports var supportsPointerEnterLeaveEvents = !this.test.bowser.safari; return { pointer : { start : [&#39;pointerover&#39;].concat(supportsPointerEnterLeaveEvents ? &#39;pointerenter&#39; : []).concat(&#39;pointerdown&#39;), move : [&#39;pointermove&#39;], end : [&#39;pointerup&#39;, &#39;pointerout&#39;].concat(supportsPointerEnterLeaveEvents ? &#39;pointerleave&#39; : []), cancel : [&#39;pointercancel&#39;] }, touch : { start : &#39;touchstart&#39;, move : &#39;touchmove&#39;, end : &#39;touchend&#39;, cancel : &#39;touchcancel&#39; } }; }, simulateTouchEventGeneric : function (target, type, options, simOptions) { simOptions = simOptions || {} var me = this; var target = me.test.normalizeElement(target) var clientX, clientY if ((&quot;clientX&quot; in options) &amp;&amp; (&quot;clientY&quot; in options)) { clientX = options.clientX clientY = options.clientY } else { var center = me.test.findCenter(target); clientX = center[ 0 ] clientY = center[ 1 ] } var activeTouches = me.activeTouches var touch if (type === &#39;start&#39;) { touch = me.createTouch(target, clientX, clientY) activeTouches[ touch.identifier ] = touch } else if (type === &#39;move&#39;) { touch = me.createTouch(target, options.clientX, options.clientY, simOptions.touchId) // &quot;*move&quot; events should be fired only from the &quot;movePointerTemplate&quot; method // which provides the &quot;clientX/clientY&quot; properties touch = activeTouches[ simOptions.touchId ] = touch; } else if (type === &#39;end&#39; || type === &#39;cancel&#39;) { touch = activeTouches[ simOptions.touchId ] target = touch.currentEl || touch.target delete activeTouches[ simOptions.touchId ] } if (!touch) throw &quot;Can&#39;t find active touch&quot; + (simOptions.touchId ? &#39;: &#39; + simOptions.touchId : &#39;&#39;) if (!simOptions.touchId) simOptions.touchId = touch.identifier simOptions.touch = touch var eventMap = this.getTouchEventNamesMap(); var supports = Siesta.Project.Browser.FeatureSupport().supports var pointerEvent; var touchEvent; if (supports.PointerEvents &amp;&amp; !(type === &#39;end&#39; &amp;&amp; me.isLongPressing())) { eventMap.pointer[type].forEach(function (event) { pointerEvent = me.simulateEvent(target, event, Object.assign({ pointerType : &#39;touch&#39; }, options), simOptions) }); } // IE11 compat check if (me.global.TouchEvent &amp;&amp; (type !== &#39;end&#39; || !this.isLongPressing())) { touchEvent = me.simulateTouchEvent(target, eventMap.touch[type], options, simOptions); } // IE11 compat check if (touchEvent &amp;&amp; type === &#39;start&#39;) { me.lastPointerDownPrevented = me.isEventPrevented(touchEvent); } if (type === &#39;end&#39;) { me.possiblySimulateMouseEventsForTouchEnd(target, touch, options); } // IE11 compat check return touchEvent || pointerEvent; }, isLongPressing : function() { return Date.now() - this.lastStartTouchTimeStamp &gt; this.getLongPressDelay(); }, // fires mouse events (done by the browser after native touch events) possiblySimulateMouseEventsForTouchEnd : function(target, touch, options) { var me = this; var didMove = me.lastStartTouch.clientX !== touch.clientX || me.lastStartTouch.clientY !== touch.clientY; if (!didMove) { var sequence; var startWasPrevented = this.isEventPrevented(this.lastStartTouchEvent); // Don&#39;t fire mouse events if this is a move pointer move (&quot;touchDrag&quot;) operation, // Chrome actually fires mouse events also when you move a little, probably since touch // by its nature is not exact typically. if (this.isLongPressing()) { if (startWasPrevented) { sequence = postLongPressSequenceWithTouchStartPrevented; } else { sequence = postLongPressSequence; } } else if (!startWasPrevented) { sequence = this.lastStartTouchWasOnNewTarget ? postTapSequence : postTapSequence.filter(function(event) { return event !== &#39;mouseover&#39;; }); } sequence &amp;&amp; sequence.forEach(function (event) { me.simulateEvent(target, event, options) }); } } } }); })(); </pre> </body> </html>