# lib/goog/testing/events/events.js

    1// Copyright 2008 The Closure Library Authors. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS-IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @fileoverview Event Simulation.17 *18 * Utility functions for simulating events at the Closure level. All functions19 * in this package generate events by calling goog.events.fireListeners,20 * rather than interfacing with the browser directly. This is intended for21 * testing purposes, and should not be used in production code.22 *23 * The decision to use Closure events and dispatchers instead of the browser's24 * native events and dispatchers was conscious and deliberate. Native event25 * dispatchers have their own set of quirks and edge cases. Pure JS dispatchers26 * are more robust and transparent.27 *28 * If you think you need a testing mechanism that uses native Event objects,29 * please, please email closure-tech first to explain your use case before you30 * sink time into this.31 *32 * @author nicksantos@google.com (Nick Santos)33 */3435goog.provide('goog.testing.events');36goog.provide('goog.testing.events.Event');3738goog.require('goog.Disposable');39goog.require('goog.asserts');40goog.require('goog.dom.NodeType');41goog.require('goog.events');42goog.require('goog.events.BrowserEvent');43goog.require('goog.events.BrowserFeature');44goog.require('goog.events.EventTarget');45goog.require('goog.events.EventType');46goog.require('goog.events.KeyCodes');47goog.require('goog.object');48goog.require('goog.style');49goog.require('goog.userAgent');50515253/**54 * goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.55 *56 * This clones a lot of the functionality of goog.events.Event. This used to57 * use a mixin, but the mixin results in confusing the two types when compiled.58 *59 * @param {string} type Event Type.60 * @param {Object=} opt_target Reference to the object that is the target of61 *     this event.62 * @constructor63 * @extends {Event}64 */65goog.testing.events.Event = function(type, opt_target) {66  this.type = type;6768  this.target = /** @type {EventTarget} */ (opt_target || null);6970  this.currentTarget = this.target;71};727374/**75 * Whether to cancel the event in internal capture/bubble processing for IE.76 * @type {boolean}77 * @public78 * @suppress {underscore|visibility} Technically public, but referencing this79 *     outside this package is strongly discouraged.80 */81goog.testing.events.Event.prototype.propagationStopped_ = false;828384/** @override */85goog.testing.events.Event.prototype.defaultPrevented = false;868788/**89 * Return value for in internal capture/bubble processing for IE.90 * @type {boolean}91 * @public92 * @suppress {underscore|visibility} Technically public, but referencing this93 *     outside this package is strongly discouraged.94 */95goog.testing.events.Event.prototype.returnValue_ = true;969798/** @override */99goog.testing.events.Event.prototype.stopPropagation = function() {100  this.propagationStopped_ = true;101};102103104/** @override */105goog.testing.events.Event.prototype.preventDefault = function() {106  this.defaultPrevented = true;107  this.returnValue_ = false;108};109110111/**112 * Asserts an event target exists.  This will fail if target is not defined.113 *114 * TODO(nnaze): Gradually add this to the methods in this file, and eventually115 *     update the method signatures to not take nullables.  See http://b/8961907116 *117 * @param {EventTarget} target A target to assert.118 * @return {!EventTarget} The target, guaranteed to exist.119 * @private120 */121goog.testing.events.assertEventTarget_ = function(target) {122  return goog.asserts.assert(target, 'EventTarget should be defined.');123};124125126/**127 * A static helper function that sets the mouse position to the event.128 * @param {Event} event A simulated native event.129 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's130 * target's position (if available), otherwise (0, 0).131 * @private132 */133goog.testing.events.setEventClientXY_ = function(event, opt_coords) {134  if (!opt_coords && event.target &&135      event.target.nodeType == goog.dom.NodeType.ELEMENT) {136    try {137      opt_coords =138          goog.style.getClientPosition(/** @type {Element} **/ (event.target));139    } catch (ex) {140      // IE sometimes throws if it can't get the position.141    }142  }143  event.clientX = opt_coords ? opt_coords.x : 0;144  event.clientY = opt_coords ? opt_coords.y : 0;145146  // Pretend the browser window is at (0, 0).147  event.screenX = event.clientX;148  event.screenY = event.clientY;149};150151152/**153 * Simulates a mousedown, mouseup, and then click on the given event target,154 * with the left mouse button.155 * @param {EventTarget} target The target for the event.156 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;157 *     defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.158 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's159 * target's position (if available), otherwise (0, 0).160 * @param {Object=} opt_eventProperties Event properties to be mixed into the161 *     BrowserEvent.162 * @return {boolean} The returnValue of the sequence: false if preventDefault()163 *     was called on any of the events, true otherwise.164 */165goog.testing.events.fireClickSequence =166    function(target, opt_button, opt_coords, opt_eventProperties) {167  // Fire mousedown, mouseup, and click. Then return the bitwise AND of the 3.168  return !!(goog.testing.events.fireMouseDownEvent(169      target, opt_button, opt_coords, opt_eventProperties) &170            goog.testing.events.fireMouseUpEvent(171                target, opt_button, opt_coords, opt_eventProperties) &172            goog.testing.events.fireClickEvent(173                target, opt_button, opt_coords, opt_eventProperties));174};175176177/**178 * Simulates the sequence of events fired by the browser when the user double-179 * clicks the given target.180 * @param {EventTarget} target The target for the event.181 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's182 * target's position (if available), otherwise (0, 0).183 * @param {Object=} opt_eventProperties Event properties to be mixed into the184 *     BrowserEvent.185 * @return {boolean} The returnValue of the sequence: false if preventDefault()186 *     was called on any of the events, true otherwise.187 */188goog.testing.events.fireDoubleClickSequence = function(189    target, opt_coords, opt_eventProperties) {190  // Fire mousedown, mouseup, click, mousedown, mouseup, click, dblclick.191  // Then return the bitwise AND of the 7.192  var btn = goog.events.BrowserEvent.MouseButton.LEFT;193  return !!(goog.testing.events.fireMouseDownEvent(194      target, btn, opt_coords, opt_eventProperties) &195            goog.testing.events.fireMouseUpEvent(196                target, btn, opt_coords, opt_eventProperties) &197            goog.testing.events.fireClickEvent(198                target, btn, opt_coords, opt_eventProperties) &199            // IE fires a selectstart instead of the second mousedown in a200            // dblclick, but we don't care about selectstart.201            (goog.userAgent.IE ||202            goog.testing.events.fireMouseDownEvent(203                target, btn, opt_coords, opt_eventProperties)) &204            goog.testing.events.fireMouseUpEvent(205                target, btn, opt_coords, opt_eventProperties) &206            // IE doesn't fire the second click in a dblclick.207            (goog.userAgent.IE ||208            goog.testing.events.fireClickEvent(209                target, btn, opt_coords, opt_eventProperties)) &210            goog.testing.events.fireDoubleClickEvent(211                target, opt_coords, opt_eventProperties));212};213214215/**216 * Simulates a complete keystroke (keydown, keypress, and keyup). Note that217 * if preventDefault is called on the keydown, the keypress will not fire.218 *219 * @param {EventTarget} target The target for the event.220 * @param {number} keyCode The keycode of the key pressed.221 * @param {Object=} opt_eventProperties Event properties to be mixed into the222 *     BrowserEvent.223 * @return {boolean} The returnValue of the sequence: false if preventDefault()224 *     was called on any of the events, true otherwise.225 */226goog.testing.events.fireKeySequence = function(227    target, keyCode, opt_eventProperties) {228  return goog.testing.events.fireNonAsciiKeySequence(target, keyCode, keyCode,229                                                     opt_eventProperties);230};231232233/**234 * Simulates a complete keystroke (keydown, keypress, and keyup) when typing235 * a non-ASCII character. Same as fireKeySequence, the keypress will not fire236 * if preventDefault is called on the keydown.237 *238 * @param {EventTarget} target The target for the event.239 * @param {number} keyCode The keycode of the keydown and keyup events.240 * @param {number} keyPressKeyCode The keycode of the keypress event.241 * @param {Object=} opt_eventProperties Event properties to be mixed into the242 *     BrowserEvent.243 * @return {boolean} The returnValue of the sequence: false if preventDefault()244 *     was called on any of the events, true otherwise.245 */246goog.testing.events.fireNonAsciiKeySequence = function(247    target, keyCode, keyPressKeyCode, opt_eventProperties) {248  var keydown =249      new goog.testing.events.Event(goog.events.EventType.KEYDOWN, target);250  var keyup =251      new goog.testing.events.Event(goog.events.EventType.KEYUP, target);252  var keypress =253      new goog.testing.events.Event(goog.events.EventType.KEYPRESS, target);254  keydown.keyCode = keyup.keyCode = keyCode;255  keypress.keyCode = keyPressKeyCode;256257  if (opt_eventProperties) {258    goog.object.extend(keydown, opt_eventProperties);259    goog.object.extend(keyup, opt_eventProperties);260    goog.object.extend(keypress, opt_eventProperties);261  }262263  // Fire keydown, keypress, and keyup. Note that if the keydown is264  // prevent-defaulted, then the keypress will not fire.265  var result = true;266  if (!goog.testing.events.isBrokenGeckoMacActionKey_(keydown)) {267    result = goog.testing.events.fireBrowserEvent(keydown);268  }269  if (goog.events.KeyCodes.firesKeyPressEvent(270      keyCode, undefined, keydown.shiftKey, keydown.ctrlKey,271      keydown.altKey) && result) {272    result &= goog.testing.events.fireBrowserEvent(keypress);273  }274  return !!(result & goog.testing.events.fireBrowserEvent(keyup));275};276277278/**279 * @param {goog.testing.events.Event} e The event.280 * @return {boolean} Whether this is the Gecko/Mac's Meta-C/V/X, which281 *     is broken and requires special handling.282 * @private283 */284goog.testing.events.isBrokenGeckoMacActionKey_ = function(e) {285  return goog.userAgent.MAC && goog.userAgent.GECKO &&286      (e.keyCode == goog.events.KeyCodes.C ||287       e.keyCode == goog.events.KeyCodes.X ||288       e.keyCode == goog.events.KeyCodes.V) && e.metaKey;289};290291292/**293 * Simulates a mouseover event on the given target.294 * @param {EventTarget} target The target for the event.295 * @param {EventTarget} relatedTarget The related target for the event (e.g.,296 *     the node that the mouse is being moved out of).297 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's298 * target's position (if available), otherwise (0, 0).299 * @return {boolean} The returnValue of the event: false if preventDefault() was300 *     called on it, true otherwise.301 */302goog.testing.events.fireMouseOverEvent = function(target, relatedTarget,303    opt_coords) {304  var mouseover =305      new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);306  mouseover.relatedTarget = relatedTarget;307  goog.testing.events.setEventClientXY_(mouseover, opt_coords);308  return goog.testing.events.fireBrowserEvent(mouseover);309};310311312/**313 * Simulates a mousemove event on the given target.314 * @param {EventTarget} target The target for the event.315 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's316 * target's position (if available), otherwise (0, 0).317 * @return {boolean} The returnValue of the event: false if preventDefault() was318 *     called on it, true otherwise.319 */320goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {321  var mousemove =322      new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);323324  goog.testing.events.setEventClientXY_(mousemove, opt_coords);325  return goog.testing.events.fireBrowserEvent(mousemove);326};327328329/**330 * Simulates a mouseout event on the given target.331 * @param {EventTarget} target The target for the event.332 * @param {EventTarget} relatedTarget The related target for the event (e.g.,333 *     the node that the mouse is being moved into).334 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's335 * target's position (if available), otherwise (0, 0).336 * @return {boolean} The returnValue of the event: false if preventDefault() was337 *     called on it, true otherwise.338 */339goog.testing.events.fireMouseOutEvent = function(target, relatedTarget,340    opt_coords) {341  var mouseout =342      new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);343  mouseout.relatedTarget = relatedTarget;344  goog.testing.events.setEventClientXY_(mouseout, opt_coords);345  return goog.testing.events.fireBrowserEvent(mouseout);346};347348349/**350 * Simulates a mousedown event on the given target.351 * @param {EventTarget} target The target for the event.352 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;353 *     defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.354 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's355 * target's position (if available), otherwise (0, 0).356 * @param {Object=} opt_eventProperties Event properties to be mixed into the357 *     BrowserEvent.358 * @return {boolean} The returnValue of the event: false if preventDefault() was359 *     called on it, true otherwise.360 */361goog.testing.events.fireMouseDownEvent =362    function(target, opt_button, opt_coords, opt_eventProperties) {363364  var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;365  button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?366      goog.events.BrowserEvent.IEButtonMap[button] : button;367  return goog.testing.events.fireMouseButtonEvent_(368      goog.events.EventType.MOUSEDOWN, target, button, opt_coords,369      opt_eventProperties);370};371372373/**374 * Simulates a mouseup event on the given target.375 * @param {EventTarget} target The target for the event.376 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;377 *     defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.378 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's379 * target's position (if available), otherwise (0, 0).380 * @param {Object=} opt_eventProperties Event properties to be mixed into the381 *     BrowserEvent.382 * @return {boolean} The returnValue of the event: false if preventDefault() was383 *     called on it, true otherwise.384 */385goog.testing.events.fireMouseUpEvent =386    function(target, opt_button, opt_coords, opt_eventProperties) {387  var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;388  button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?389      goog.events.BrowserEvent.IEButtonMap[button] : button;390  return goog.testing.events.fireMouseButtonEvent_(391      goog.events.EventType.MOUSEUP, target, button, opt_coords,392      opt_eventProperties);393};394395396/**397 * Simulates a click event on the given target. IE only supports click with398 * the left mouse button.399 * @param {EventTarget} target The target for the event.400 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;401 *     defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.402 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's403 * target's position (if available), otherwise (0, 0).404 * @param {Object=} opt_eventProperties Event properties to be mixed into the405 *     BrowserEvent.406 * @return {boolean} The returnValue of the event: false if preventDefault() was407 *     called on it, true otherwise.408 */409goog.testing.events.fireClickEvent =410    function(target, opt_button, opt_coords, opt_eventProperties) {411  return goog.testing.events.fireMouseButtonEvent_(goog.events.EventType.CLICK,412      target, opt_button, opt_coords, opt_eventProperties);413};414415416/**417 * Simulates a double-click event on the given target. Always double-clicks418 * with the left mouse button since no browser supports double-clicking with419 * any other buttons.420 * @param {EventTarget} target The target for the event.421 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's422 * target's position (if available), otherwise (0, 0).423 * @param {Object=} opt_eventProperties Event properties to be mixed into the424 *     BrowserEvent.425 * @return {boolean} The returnValue of the event: false if preventDefault() was426 *     called on it, true otherwise.427 */428goog.testing.events.fireDoubleClickEvent =429    function(target, opt_coords, opt_eventProperties) {430  return goog.testing.events.fireMouseButtonEvent_(431      goog.events.EventType.DBLCLICK, target,432      goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,433      opt_eventProperties);434};435436437/**438 * Helper function to fire a mouse event.439 * with the left mouse button since no browser supports double-clicking with440 * any other buttons.441 * @param {string} type The event type.442 * @param {EventTarget} target The target for the event.443 * @param {number=} opt_button Mouse button; defaults to444 *     {@code goog.events.BrowserEvent.MouseButton.LEFT}.445 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's446 * target's position (if available), otherwise (0, 0).447 * @param {Object=} opt_eventProperties Event properties to be mixed into the448 *     BrowserEvent.449 * @return {boolean} The returnValue of the event: false if preventDefault() was450 *     called on it, true otherwise.451 * @private452 */453goog.testing.events.fireMouseButtonEvent_ =454    function(type, target, opt_button, opt_coords, opt_eventProperties) {455  var e =456      new goog.testing.events.Event(type, target);457  e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;458  goog.testing.events.setEventClientXY_(e, opt_coords);459  if (opt_eventProperties) {460    goog.object.extend(e, opt_eventProperties);461  }462  return goog.testing.events.fireBrowserEvent(e);463};464465466/**467 * Simulates a contextmenu event on the given target.468 * @param {EventTarget} target The target for the event.469 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's470 * target's position (if available), otherwise (0, 0).471 * @return {boolean} The returnValue of the event: false if preventDefault() was472 *     called on it, true otherwise.473 */474goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {475  var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?476      goog.events.BrowserEvent.MouseButton.LEFT :477      goog.events.BrowserEvent.MouseButton.RIGHT;478  var contextmenu =479      new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);480  contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?481      goog.events.BrowserEvent.IEButtonMap[button] : button;482  contextmenu.ctrlKey = goog.userAgent.MAC;483  goog.testing.events.setEventClientXY_(contextmenu, opt_coords);484  return goog.testing.events.fireBrowserEvent(contextmenu);485};486487488/**489 * Simulates a mousedown, contextmenu, and the mouseup on the given event490 * target, with the right mouse button.491 * @param {EventTarget} target The target for the event.492 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's493 * target's position (if available), otherwise (0, 0).494 * @return {boolean} The returnValue of the sequence: false if preventDefault()495 *     was called on any of the events, true otherwise.496 */497goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {498  var props = goog.userAgent.MAC ? {ctrlKey: true} : {};499  var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?500      goog.events.BrowserEvent.MouseButton.LEFT :501      goog.events.BrowserEvent.MouseButton.RIGHT;502503  var result = goog.testing.events.fireMouseDownEvent(target,504      button, opt_coords, props);505  if (goog.userAgent.WINDOWS) {506    // All browsers are consistent on Windows.507    result &= goog.testing.events.fireMouseUpEvent(target,508        button, opt_coords) &509              goog.testing.events.fireContextMenuEvent(target, opt_coords);510  } else {511    result &= goog.testing.events.fireContextMenuEvent(target, opt_coords);512513    // GECKO on Mac and Linux always fires the mouseup after the contextmenu.514515    // WEBKIT is really weird.516    //517    // On Linux, it sometimes fires mouseup, but most of the time doesn't.518    // It's really hard to reproduce consistently. I think there's some519    // internal race condition. If contextmenu is preventDefaulted, then520    // mouseup always fires.521    //522    // On Mac, it always fires mouseup and then fires a click.523    result &= goog.testing.events.fireMouseUpEvent(target,524        button, opt_coords, props);525526    if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {527      result &= goog.testing.events.fireClickEvent(528          target, button, opt_coords, props);529    }530  }531  return !!result;532};533534535/**536 * Simulates a popstate event on the given target.537 * @param {EventTarget} target The target for the event.538 * @param {Object} state History state object.539 * @return {boolean} The returnValue of the event: false if preventDefault() was540 *     called on it, true otherwise.541 */542goog.testing.events.firePopStateEvent = function(target, state) {543  var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target);544  e.state = state;545  return goog.testing.events.fireBrowserEvent(e);546};547548549/**550 * Simulate a blur event on the given target.551 * @param {EventTarget} target The target for the event.552 * @return {boolean} The value returned by firing the blur browser event,553 *      which returns false iff 'preventDefault' was invoked.554 */555goog.testing.events.fireBlurEvent = function(target) {556  var e = new goog.testing.events.Event(557      goog.events.EventType.BLUR, target);558  return goog.testing.events.fireBrowserEvent(e);559};560561562/**563 * Simulate a focus event on the given target.564 * @param {EventTarget} target The target for the event.565 * @return {boolean} The value returned by firing the focus browser event,566 *     which returns false iff 'preventDefault' was invoked.567 */568goog.testing.events.fireFocusEvent = function(target) {569  var e = new goog.testing.events.Event(570      goog.events.EventType.FOCUS, target);571  return goog.testing.events.fireBrowserEvent(e);572};573574575/**576 * Simulates an event's capturing and bubbling phases.577 * @param {Event} event A simulated native event. It will be wrapped in a578 *     normalized BrowserEvent and dispatched to Closure listeners on all579 *     ancestors of its target (inclusive).580 * @return {boolean} The returnValue of the event: false if preventDefault() was581 *     called on it, true otherwise.582 */583goog.testing.events.fireBrowserEvent = function(event) {584  event.returnValue_ = true;585586  // generate a list of ancestors587  var ancestors = [];588  for (var current = event.target; current; current = current.parentNode) {589    ancestors.push(current);590  }591592  // dispatch capturing listeners593  for (var j = ancestors.length - 1;594       j >= 0 && !event.propagationStopped_;595       j--) {596    goog.events.fireListeners(ancestors[j], event.type, true,597        new goog.events.BrowserEvent(event, ancestors[j]));598  }599600  // dispatch bubbling listeners601  for (var j = 0;602       j < ancestors.length && !event.propagationStopped_;603       j++) {604    goog.events.fireListeners(ancestors[j], event.type, false,605        new goog.events.BrowserEvent(event, ancestors[j]));606  }607608  return event.returnValue_;609};610611612/**613 * Simulates a touchstart event on the given target.614 * @param {EventTarget} target The target for the event.615 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's616 *     target's position (if available), otherwise (0, 0).617 * @param {Object=} opt_eventProperties Event properties to be mixed into the618 *     BrowserEvent.619 * @return {boolean} The returnValue of the event: false if preventDefault() was620 *     called on it, true otherwise.621 */622goog.testing.events.fireTouchStartEvent = function(623    target, opt_coords, opt_eventProperties) {624  // TODO: Support multi-touch events with array of coordinates.625  var touchstart =626      new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);627  goog.testing.events.setEventClientXY_(touchstart, opt_coords);628  if (opt_eventProperties) {629    goog.object.extend(touchstart, opt_eventProperties);630  }631  return goog.testing.events.fireBrowserEvent(touchstart);632};633634635/**636 * Simulates a touchmove event on the given target.637 * @param {EventTarget} target The target for the event.638 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's639 *     target's position (if available), otherwise (0, 0).640 * @param {Object=} opt_eventProperties Event properties to be mixed into the641 *     BrowserEvent.642 * @return {boolean} The returnValue of the event: false if preventDefault() was643 *     called on it, true otherwise.644 */645goog.testing.events.fireTouchMoveEvent = function(646    target, opt_coords, opt_eventProperties) {647  // TODO: Support multi-touch events with array of coordinates.648  var touchmove =649      new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);650  goog.testing.events.setEventClientXY_(touchmove, opt_coords);651  if (opt_eventProperties) {652    goog.object.extend(touchmove, opt_eventProperties);653  }654  return goog.testing.events.fireBrowserEvent(touchmove);655};656657658/**659 * Simulates a touchend event on the given target.660 * @param {EventTarget} target The target for the event.661 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's662 *     target's position (if available), otherwise (0, 0).663 * @param {Object=} opt_eventProperties Event properties to be mixed into the664 *     BrowserEvent.665 * @return {boolean} The returnValue of the event: false if preventDefault() was666 *     called on it, true otherwise.667 */668goog.testing.events.fireTouchEndEvent = function(669    target, opt_coords, opt_eventProperties) {670  // TODO: Support multi-touch events with array of coordinates.671  var touchend =672      new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);673  goog.testing.events.setEventClientXY_(touchend, opt_coords);674  if (opt_eventProperties) {675    goog.object.extend(touchend, opt_eventProperties);676  }677  return goog.testing.events.fireBrowserEvent(touchend);678};679680681/**682 * Simulates a simple touch sequence on the given target.683 * @param {EventTarget} target The target for the event.684 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event685 *     target's position (if available), otherwise (0, 0).686 * @param {Object=} opt_eventProperties Event properties to be mixed into the687 *     BrowserEvent.688 * @return {boolean} The returnValue of the sequence: false if preventDefault()689 *     was called on any of the events, true otherwise.690 */691goog.testing.events.fireTouchSequence = function(692    target, opt_coords, opt_eventProperties) {693  // TODO: Support multi-touch events with array of coordinates.694  // Fire touchstart, touchmove, touchend then return the bitwise AND of the 3.695  return !!(goog.testing.events.fireTouchStartEvent(696      target, opt_coords, opt_eventProperties) &697            goog.testing.events.fireTouchEndEvent(698                target, opt_coords, opt_eventProperties));699};700701702/**703 * Mixins a listenable into the given object. This turns the object704 * into a goog.events.Listenable. This is useful, for example, when705 * you need to mock a implementation of listenable and still want it706 * to work with goog.events.707 * @param {!Object} obj The object to mixin into.708 */709goog.testing.events.mixinListenable = function(obj) {710  var listenable = new goog.events.EventTarget();711712  listenable.setTargetForTesting(obj);713714  var listenablePrototype = goog.events.EventTarget.prototype;715  var disposablePrototype = goog.Disposable.prototype;716  for (var key in listenablePrototype) {717    if (listenablePrototype.hasOwnProperty(key) ||718        disposablePrototype.hasOwnProperty(key)) {719      var member = listenablePrototype[key];720      if (goog.isFunction(member)) {721        obj[key] = goog.bind(member, listenable);722      } else {723        obj[key] = member;724      }725    }726  }727};