diff options
Diffstat (limited to 'test/wpt/tests/resources/testdriver-actions.js')
-rw-r--r-- | test/wpt/tests/resources/testdriver-actions.js | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/test/wpt/tests/resources/testdriver-actions.js b/test/wpt/tests/resources/testdriver-actions.js new file mode 100644 index 0000000..3e5ba74 --- /dev/null +++ b/test/wpt/tests/resources/testdriver-actions.js @@ -0,0 +1,599 @@ +(function() { + let sourceNameIdx = 0; + + /** + * @class + * Builder for creating a sequence of actions + * + * + * The actions are dispatched once + * :js:func:`test_driver.Actions.send` is called. This returns a + * promise which resolves once the actions are complete. + * + * The other methods on :js:class:`test_driver.Actions` object are + * used to build the sequence of actions that will be sent. These + * return the `Actions` object itself, so the actions sequence can + * be constructed by chaining method calls. + * + * Internally :js:func:`test_driver.Actions.send` invokes + * :js:func:`test_driver.action_sequence`. + * + * @example + * let text_box = document.getElementById("text"); + * + * let actions = new test_driver.Actions() + * .pointerMove(0, 0, {origin: text_box}) + * .pointerDown() + * .pointerUp() + * .addTick() + * .keyDown("p") + * .keyUp("p"); + * + * await actions.send(); + * + * @param {number} [defaultTickDuration] - The default duration of a + * tick. Be default this is set ot 16ms, which is one frame time + * based on 60Hz display. + */ + function Actions(defaultTickDuration=16) { + this.sourceTypes = new Map([["key", KeySource], + ["pointer", PointerSource], + ["wheel", WheelSource], + ["none", GeneralSource]]); + this.sources = new Map(); + this.sourceOrder = []; + for (let sourceType of this.sourceTypes.keys()) { + this.sources.set(sourceType, new Map()); + } + this.currentSources = new Map(); + for (let sourceType of this.sourceTypes.keys()) { + this.currentSources.set(sourceType, null); + } + this.createSource("none"); + this.tickIdx = 0; + this.defaultTickDuration = defaultTickDuration; + this.context = null; + } + + Actions.prototype = { + ButtonType: { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2, + BACK: 3, + FORWARD: 4, + }, + + /** + * Generate the action sequence suitable for passing to + * test_driver.action_sequence + * + * @returns {Array} Array of WebDriver-compatible actions sequences + */ + serialize: function() { + let actions = []; + for (let [sourceType, sourceName] of this.sourceOrder) { + let source = this.sources.get(sourceType).get(sourceName); + let serialized = source.serialize(this.tickIdx + 1, this.defaultTickDuration); + if (serialized) { + serialized.id = sourceName; + actions.push(serialized); + } + } + return actions; + }, + + /** + * Generate and send the action sequence + * + * @returns {Promise} fulfilled after the sequence is executed, + * rejected if any actions fail. + */ + send: function() { + let actions; + try { + actions = this.serialize(); + } catch(e) { + return Promise.reject(e); + } + return test_driver.action_sequence(actions, this.context); + }, + + /** + * Set the context for the actions + * + * @param {WindowProxy} context - Context in which to run the action sequence + */ + setContext: function(context) { + this.context = context; + return this; + }, + + /** + * Get the action source with a particular source type and name. + * If no name is passed, a new source with the given type is + * created. + * + * @param {String} type - Source type ('none', 'key', 'pointer', or 'wheel') + * @param {String?} name - Name of the source + * @returns {Source} Source object for that source. + */ + getSource: function(type, name) { + if (!this.sources.has(type)) { + throw new Error(`${type} is not a valid action type`); + } + if (name === null || name === undefined) { + name = this.currentSources.get(type); + } + if (name === null || name === undefined) { + return this.createSource(type, null); + } + return this.sources.get(type).get(name); + }, + + setSource: function(type, name) { + if (!this.sources.has(type)) { + throw new Error(`${type} is not a valid action type`); + } + if (!this.sources.get(type).has(name)) { + throw new Error(`${name} is not a valid source for ${type}`); + } + this.currentSources.set(type, name); + return this; + }, + + /** + * Add a new key input source with the given name + * + * @param {String} name - Name of the key source + * @param {Bool} set - Set source as the default key source + * @returns {Actions} + */ + addKeyboard: function(name, set=true) { + this.createSource("key", name); + if (set) { + this.setKeyboard(name); + } + return this; + }, + + /** + * Set the current default key source + * + * @param {String} name - Name of the key source + * @returns {Actions} + */ + setKeyboard: function(name) { + this.setSource("key", name); + return this; + }, + + /** + * Add a new pointer input source with the given name + * + * @param {String} type - Name of the pointer source + * @param {String} pointerType - Type of pointing device + * @param {Bool} set - Set source as the default pointer source + * @returns {Actions} + */ + addPointer: function(name, pointerType="mouse", set=true) { + this.createSource("pointer", name, {pointerType: pointerType}); + if (set) { + this.setPointer(name); + } + return this; + }, + + /** + * Set the current default pointer source + * + * @param {String} name - Name of the pointer source + * @returns {Actions} + */ + setPointer: function(name) { + this.setSource("pointer", name); + return this; + }, + + /** + * Add a new wheel input source with the given name + * + * @param {String} type - Name of the wheel source + * @param {Bool} set - Set source as the default wheel source + * @returns {Actions} + */ + addWheel: function(name, set=true) { + this.createSource("wheel", name); + if (set) { + this.setWheel(name); + } + return this; + }, + + /** + * Set the current default wheel source + * + * @param {String} name - Name of the wheel source + * @returns {Actions} + */ + setWheel: function(name) { + this.setSource("wheel", name); + return this; + }, + + createSource: function(type, name, parameters={}) { + if (!this.sources.has(type)) { + throw new Error(`${type} is not a valid action type`); + } + let sourceNames = new Set(); + for (let [_, name] of this.sourceOrder) { + sourceNames.add(name); + } + if (!name) { + do { + name = "" + sourceNameIdx++; + } while (sourceNames.has(name)) + } else { + if (sourceNames.has(name)) { + throw new Error(`Alreay have a source of type ${type} named ${name}.`); + } + } + this.sources.get(type).set(name, new (this.sourceTypes.get(type))(parameters)); + this.currentSources.set(type, name); + this.sourceOrder.push([type, name]); + return this.sources.get(type).get(name); + }, + + /** + * Insert a new actions tick + * + * @param {Number?} duration - Minimum length of the tick in ms. + * @returns {Actions} + */ + addTick: function(duration) { + this.tickIdx += 1; + if (duration) { + this.pause(duration); + } + return this; + }, + + /** + * Add a pause to the current tick + * + * @param {Number?} duration - Minimum length of the tick in ms. + * @param {String} sourceType - source type + * @param {String?} sourceName - Named key, pointer or wheel source to use + * or null for the default key, pointer or + * wheel source + * @returns {Actions} + */ + pause: function(duration=0, sourceType="none", {sourceName=null}={}) { + if (sourceType=="none") + this.getSource("none").addPause(this, duration); + else + this.getSource(sourceType, sourceName).addPause(this, duration); + return this; + }, + + /** + * Create a keyDown event for the current default key source + * + * @param {String} key - Key to press + * @param {String?} sourceName - Named key source to use or null for the default key source + * @returns {Actions} + */ + keyDown: function(key, {sourceName=null}={}) { + let source = this.getSource("key", sourceName); + source.keyDown(this, key); + return this; + }, + + /** + * Create a keyDown event for the current default key source + * + * @param {String} key - Key to release + * @param {String?} sourceName - Named key source to use or null for the default key source + * @returns {Actions} + */ + keyUp: function(key, {sourceName=null}={}) { + let source = this.getSource("key", sourceName); + source.keyUp(this, key); + return this; + }, + + /** + * Create a pointerDown event for the current default pointer source + * + * @param {String} button - Button to press + * @param {String?} sourceName - Named pointer source to use or null for the default + * pointer source + * @returns {Actions} + */ + pointerDown: function({button=this.ButtonType.LEFT, sourceName=null, + width, height, pressure, tangentialPressure, + tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) { + let source = this.getSource("pointer", sourceName); + source.pointerDown(this, button, width, height, pressure, tangentialPressure, + tiltX, tiltY, twist, altitudeAngle, azimuthAngle); + return this; + }, + + /** + * Create a pointerUp event for the current default pointer source + * + * @param {String} button - Button to release + * @param {String?} sourceName - Named pointer source to use or null for the default pointer + * source + * @returns {Actions} + */ + pointerUp: function({button=this.ButtonType.LEFT, sourceName=null}={}) { + let source = this.getSource("pointer", sourceName); + source.pointerUp(this, button); + return this; + }, + + /** + * Create a move event for the current default pointer source + * + * @param {Number} x - Destination x coordinate + * @param {Number} y - Destination y coordinate + * @param {String|Element} origin - Origin of the coordinate system. + * Either "pointer", "viewport" or an Element + * @param {Number?} duration - Time in ms for the move + * @param {String?} sourceName - Named pointer source to use or null for the default pointer + * source + * @returns {Actions} + */ + pointerMove: function(x, y, + {origin="viewport", duration, sourceName=null, + width, height, pressure, tangentialPressure, + tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) { + let source = this.getSource("pointer", sourceName); + source.pointerMove(this, x, y, duration, origin, width, height, pressure, + tangentialPressure, tiltX, tiltY, twist, altitudeAngle, + azimuthAngle); + return this; + }, + + /** + * Create a scroll event for the current default wheel source + * + * @param {Number} x - mouse cursor x coordinate + * @param {Number} y - mouse cursor y coordinate + * @param {Number} deltaX - scroll delta value along the x-axis in pixels + * @param {Number} deltaY - scroll delta value along the y-axis in pixels + * @param {String|Element} origin - Origin of the coordinate system. + * Either "viewport" or an Element + * @param {Number?} duration - Time in ms for the scroll + * @param {String?} sourceName - Named wheel source to use or null for the + * default wheel source + * @returns {Actions} + */ + scroll: function(x, y, deltaX, deltaY, + {origin="viewport", duration, sourceName=null}={}) { + let source = this.getSource("wheel", sourceName); + source.scroll(this, x, y, deltaX, deltaY, duration, origin); + return this; + }, + }; + + function GeneralSource() { + this.actions = new Map(); + } + + GeneralSource.prototype = { + serialize: function(tickCount, defaultTickDuration) { + let actions = []; + let data = {"type": "none", "actions": actions}; + for (let i=0; i<tickCount; i++) { + if (this.actions.has(i)) { + actions.push(this.actions.get(i)); + } else { + actions.push({"type": "pause", duration: defaultTickDuration}); + } + } + return data; + }, + + addPause: function(actions, duration) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + throw new Error(`Already have a pause action for the current tick`); + } + this.actions.set(tick, {type: "pause", duration: duration}); + }, + }; + + function KeySource() { + this.actions = new Map(); + } + + KeySource.prototype = { + serialize: function(tickCount) { + if (!this.actions.size) { + return undefined; + } + let actions = []; + let data = {"type": "key", "actions": actions}; + for (let i=0; i<tickCount; i++) { + if (this.actions.has(i)) { + actions.push(this.actions.get(i)); + } else { + actions.push({"type": "pause"}); + } + } + return data; + }, + + keyDown: function(actions, key) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "keyDown", value: key}); + }, + + keyUp: function(actions, key) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "keyUp", value: key}); + }, + + addPause: function(actions, duration) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "pause", duration: duration}); + }, + }; + + function PointerSource(parameters={pointerType: "mouse"}) { + let pointerType = parameters.pointerType || "mouse"; + if (!["mouse", "pen", "touch"].includes(pointerType)) { + throw new Error(`Invalid pointerType ${pointerType}`); + } + this.type = pointerType; + this.actions = new Map(); + } + + function setPointerProperties(action, width, height, pressure, tangentialPressure, + tiltX, tiltY, twist, altitudeAngle, azimuthAngle) { + if (width) { + action.width = width; + } + if (height) { + action.height = height; + } + if (pressure) { + action.pressure = pressure; + } + if (tangentialPressure) { + action.tangentialPressure = tangentialPressure; + } + if (tiltX) { + action.tiltX = tiltX; + } + if (tiltY) { + action.tiltY = tiltY; + } + if (twist) { + action.twist = twist; + } + if (altitudeAngle) { + action.altitudeAngle = altitudeAngle; + } + if (azimuthAngle) { + action.azimuthAngle = azimuthAngle; + } + return action; + } + + PointerSource.prototype = { + serialize: function(tickCount) { + if (!this.actions.size) { + return undefined; + } + let actions = []; + let data = {"type": "pointer", "actions": actions, "parameters": {"pointerType": this.type}}; + for (let i=0; i<tickCount; i++) { + if (this.actions.has(i)) { + actions.push(this.actions.get(i)); + } else { + actions.push({"type": "pause"}); + } + } + return data; + }, + + pointerDown: function(actions, button, width, height, pressure, tangentialPressure, + tiltX, tiltY, twist, altitudeAngle, azimuthAngle) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + let actionProperties = setPointerProperties({type: "pointerDown", button}, width, height, + pressure, tangentialPressure, tiltX, tiltY, + twist, altitudeAngle, azimuthAngle); + this.actions.set(tick, actionProperties); + }, + + pointerUp: function(actions, button) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "pointerUp", button}); + }, + + pointerMove: function(actions, x, y, duration, origin, width, height, pressure, + tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + let moveAction = {type: "pointerMove", x, y, origin}; + if (duration) { + moveAction.duration = duration; + } + let actionProperties = setPointerProperties(moveAction, width, height, pressure, + tangentialPressure, tiltX, tiltY, twist, + altitudeAngle, azimuthAngle); + this.actions.set(tick, actionProperties); + }, + + addPause: function(actions, duration) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "pause", duration: duration}); + }, + }; + + function WheelSource() { + this.actions = new Map(); + } + + WheelSource.prototype = { + serialize: function(tickCount) { + if (!this.actions.size) { + return undefined; + } + let actions = []; + let data = {"type": "wheel", "actions": actions}; + for (let i=0; i<tickCount; i++) { + if (this.actions.has(i)) { + actions.push(this.actions.get(i)); + } else { + actions.push({"type": "pause"}); + } + } + return data; + }, + + scroll: function(actions, x, y, deltaX, deltaY, duration, origin) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "scroll", x, y, deltaX, deltaY, origin}); + if (duration) { + this.actions.get(tick).duration = duration; + } + }, + + addPause: function(actions, duration) { + let tick = actions.tickIdx; + if (this.actions.has(tick)) { + tick = actions.addTick().tickIdx; + } + this.actions.set(tick, {type: "pause", duration: duration}); + }, + }; + + test_driver.Actions = Actions; +})(); |