events.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  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 at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // 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 and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Event Simulation.
  16. *
  17. * Utility functions for simulating events at the Closure level. All functions
  18. * in this package generate events by calling goog.events.fireListeners,
  19. * rather than interfacing with the browser directly. This is intended for
  20. * testing purposes, and should not be used in production code.
  21. *
  22. * The decision to use Closure events and dispatchers instead of the browser's
  23. * native events and dispatchers was conscious and deliberate. Native event
  24. * dispatchers have their own set of quirks and edge cases. Pure JS dispatchers
  25. * are more robust and transparent.
  26. *
  27. * If you think you need a testing mechanism that uses native Event objects,
  28. * please, please email closure-tech first to explain your use case before you
  29. * sink time into this.
  30. *
  31. * TODO(b/8933952): Migrate to explicitly non-nullable types. At present, many
  32. * functions in this file expect non-null inputs but do not explicitly
  33. * indicate this.
  34. *
  35. * @author nicksantos@google.com (Nick Santos)
  36. */
  37. goog.setTestOnly('goog.testing.events');
  38. goog.provide('goog.testing.events');
  39. goog.provide('goog.testing.events.Event');
  40. goog.require('goog.Disposable');
  41. goog.require('goog.asserts');
  42. goog.require('goog.dom.NodeType');
  43. goog.require('goog.events');
  44. goog.require('goog.events.BrowserEvent');
  45. goog.require('goog.events.BrowserFeature');
  46. goog.require('goog.events.EventTarget');
  47. goog.require('goog.events.EventType');
  48. goog.require('goog.events.KeyCodes');
  49. goog.require('goog.object');
  50. goog.require('goog.style');
  51. goog.require('goog.userAgent');
  52. /**
  53. * goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.
  54. *
  55. * This clones a lot of the functionality of goog.events.Event. This used to
  56. * use a mixin, but the mixin results in confusing the two types when compiled.
  57. *
  58. * @param {string} type Event Type.
  59. * @param {Object=} opt_target Reference to the object that is the target of
  60. * this event.
  61. * @constructor
  62. * @extends {Event}
  63. */
  64. goog.testing.events.Event = function(type, opt_target) {
  65. this.type = type;
  66. this.target = /** @type {EventTarget} */ (opt_target || null);
  67. this.currentTarget = this.target;
  68. };
  69. /**
  70. * Whether to cancel the event in internal capture/bubble processing for IE.
  71. * @type {boolean}
  72. * @public
  73. * @suppress {underscore|visibility} Technically public, but referencing this
  74. * outside this package is strongly discouraged.
  75. */
  76. goog.testing.events.Event.prototype.propagationStopped_ = false;
  77. /** @override */
  78. goog.testing.events.Event.prototype.defaultPrevented = false;
  79. /**
  80. * Return value for in internal capture/bubble processing for IE.
  81. * @type {boolean}
  82. * @public
  83. * @suppress {underscore|visibility} Technically public, but referencing this
  84. * outside this package is strongly discouraged.
  85. */
  86. goog.testing.events.Event.prototype.returnValue_ = true;
  87. /** @override */
  88. goog.testing.events.Event.prototype.stopPropagation = function() {
  89. this.propagationStopped_ = true;
  90. };
  91. /** @override */
  92. goog.testing.events.Event.prototype.preventDefault = function() {
  93. this.defaultPrevented = true;
  94. this.returnValue_ = false;
  95. };
  96. /**
  97. * Asserts an event target exists. This will fail if target is not defined.
  98. *
  99. * TODO(nnaze): Gradually add this to the methods in this file, and eventually
  100. * update the method signatures to not take nullables. See http://b/8961907
  101. *
  102. * @param {EventTarget} target A target to assert.
  103. * @return {!EventTarget} The target, guaranteed to exist.
  104. * @private
  105. */
  106. goog.testing.events.assertEventTarget_ = function(target) {
  107. return goog.asserts.assert(target, 'EventTarget should be defined.');
  108. };
  109. /**
  110. * A static helper function that sets the mouse position to the event.
  111. * @param {Event} event A simulated native event.
  112. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  113. * target's position (if available), otherwise (0, 0).
  114. * @private
  115. */
  116. goog.testing.events.setEventClientXY_ = function(event, opt_coords) {
  117. if (!opt_coords && event.target &&
  118. /** @type {!Node} */ (event.target).nodeType ==
  119. goog.dom.NodeType.ELEMENT) {
  120. try {
  121. opt_coords =
  122. goog.style.getClientPosition(/** @type {!Element} **/ (event.target));
  123. } catch (ex) {
  124. // IE sometimes throws if it can't get the position.
  125. }
  126. }
  127. event.clientX = opt_coords ? opt_coords.x : 0;
  128. event.clientY = opt_coords ? opt_coords.y : 0;
  129. // Pretend the browser window is at (0, 0).
  130. event.screenX = event.clientX;
  131. event.screenY = event.clientY;
  132. };
  133. /**
  134. * Simulates a mousedown, mouseup, and then click on the given event target,
  135. * with the left mouse button.
  136. * @param {EventTarget} target The target for the event.
  137. * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
  138. * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
  139. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  140. * target's position (if available), otherwise (0, 0).
  141. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  142. * BrowserEvent.
  143. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  144. * was called on any of the events, true otherwise.
  145. */
  146. goog.testing.events.fireClickSequence = function(
  147. target, opt_button, opt_coords, opt_eventProperties) {
  148. // Fire mousedown, mouseup, and click. Then return the bitwise AND of the 3.
  149. return !!(
  150. goog.testing.events.fireMouseDownEvent(
  151. target, opt_button, opt_coords, opt_eventProperties) &
  152. goog.testing.events.fireMouseUpEvent(
  153. target, opt_button, opt_coords, opt_eventProperties) &
  154. goog.testing.events.fireClickEvent(
  155. target, opt_button, opt_coords, opt_eventProperties));
  156. };
  157. /**
  158. * Simulates the sequence of events fired by the browser when the user double-
  159. * clicks the given target.
  160. * @param {EventTarget} target The target for the event.
  161. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  162. * target's position (if available), otherwise (0, 0).
  163. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  164. * BrowserEvent.
  165. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  166. * was called on any of the events, true otherwise.
  167. */
  168. goog.testing.events.fireDoubleClickSequence = function(
  169. target, opt_coords, opt_eventProperties) {
  170. // Fire mousedown, mouseup, click, mousedown, mouseup, click, dblclick.
  171. // Then return the bitwise AND of the 7.
  172. var btn = goog.events.BrowserEvent.MouseButton.LEFT;
  173. return !!(
  174. goog.testing.events.fireMouseDownEvent(
  175. target, btn, opt_coords, opt_eventProperties) &
  176. goog.testing.events.fireMouseUpEvent(
  177. target, btn, opt_coords, opt_eventProperties) &
  178. goog.testing.events.fireClickEvent(
  179. target, btn, opt_coords, opt_eventProperties) &
  180. // IE fires a selectstart instead of the second mousedown in a
  181. // dblclick, but we don't care about selectstart.
  182. (goog.userAgent.IE ||
  183. goog.testing.events.fireMouseDownEvent(
  184. target, btn, opt_coords, opt_eventProperties)) &
  185. goog.testing.events.fireMouseUpEvent(
  186. target, btn, opt_coords, opt_eventProperties) &
  187. // IE doesn't fire the second click in a dblclick.
  188. (goog.userAgent.IE ||
  189. goog.testing.events.fireClickEvent(
  190. target, btn, opt_coords, opt_eventProperties)) &
  191. goog.testing.events.fireDoubleClickEvent(
  192. target, opt_coords, opt_eventProperties));
  193. };
  194. /**
  195. * Simulates a complete keystroke (keydown, keypress, and keyup). Note that
  196. * if preventDefault is called on the keydown, the keypress will not fire.
  197. *
  198. * @param {EventTarget} target The target for the event.
  199. * @param {string|number} keyOrKeyCode The key value or keycode of the key
  200. * pressed.
  201. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  202. * BrowserEvent.
  203. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  204. * was called on any of the events, true otherwise.
  205. */
  206. goog.testing.events.fireKeySequence = function(
  207. target, keyOrKeyCode, opt_eventProperties) {
  208. return goog.testing.events.fireNonAsciiKeySequence(
  209. target, keyOrKeyCode, keyOrKeyCode, opt_eventProperties);
  210. };
  211. /**
  212. * Simulates a complete keystroke (keydown, keypress, and keyup) when typing
  213. * a non-ASCII character. Same as fireKeySequence, the keypress will not fire
  214. * if preventDefault is called on the keydown.
  215. *
  216. * @param {EventTarget} target The target for the event.
  217. * @param {string|number} keyOrKeyCode The key value or keycode of the keydown
  218. * and keyup events.
  219. * @param {string|number} keyPressKeyOrKeyCode The key value or keycode of the
  220. * keypress event.
  221. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  222. * BrowserEvent.
  223. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  224. * was called on any of the events, true otherwise.
  225. */
  226. goog.testing.events.fireNonAsciiKeySequence = function(
  227. target, keyOrKeyCode, keyPressKeyOrKeyCode, opt_eventProperties) {
  228. var keydown =
  229. new goog.testing.events.Event(goog.events.EventType.KEYDOWN, target);
  230. var keyup =
  231. new goog.testing.events.Event(goog.events.EventType.KEYUP, target);
  232. var keypress =
  233. new goog.testing.events.Event(goog.events.EventType.KEYPRESS, target);
  234. if (goog.isString(keyOrKeyCode)) {
  235. keydown.key = keyup.key = /** @type {string} */ (keyOrKeyCode);
  236. keypress.key = /** @type {string} */ (keyPressKeyOrKeyCode);
  237. } else {
  238. keydown.keyCode = keyup.keyCode = /** @type {number} */ (keyOrKeyCode);
  239. keypress.keyCode = /** @type {number} */ (keyPressKeyOrKeyCode);
  240. }
  241. if (opt_eventProperties) {
  242. goog.object.extend(keydown, opt_eventProperties);
  243. goog.object.extend(keyup, opt_eventProperties);
  244. goog.object.extend(keypress, opt_eventProperties);
  245. }
  246. // Fire keydown, keypress, and keyup. Note that if the keydown is
  247. // prevent-defaulted, then the keypress will not fire.
  248. var result = true;
  249. if (!goog.testing.events.isBrokenGeckoMacActionKey_(keydown)) {
  250. result = goog.testing.events.fireBrowserEvent(keydown);
  251. }
  252. if (goog.isString(keyOrKeyCode)) {
  253. if (/** @type {string} */ (keyPressKeyOrKeyCode) != '' && result) {
  254. result &= goog.testing.events.fireBrowserEvent(keypress);
  255. }
  256. } else {
  257. if (goog.events.KeyCodes.firesKeyPressEvent(
  258. /** @type {number} */ (keyOrKeyCode), undefined, keydown.shiftKey,
  259. keydown.ctrlKey, keydown.altKey) &&
  260. result) {
  261. result &= goog.testing.events.fireBrowserEvent(keypress);
  262. }
  263. }
  264. return !!(result & goog.testing.events.fireBrowserEvent(keyup));
  265. };
  266. /**
  267. * @param {goog.testing.events.Event} e The event.
  268. * @return {boolean} Whether this is the Gecko/Mac's Meta-C/V/X, which
  269. * is broken and requires special handling.
  270. * @private
  271. */
  272. goog.testing.events.isBrokenGeckoMacActionKey_ = function(e) {
  273. return goog.userAgent.MAC && goog.userAgent.GECKO &&
  274. (e.keyCode == goog.events.KeyCodes.C ||
  275. e.keyCode == goog.events.KeyCodes.X ||
  276. e.keyCode == goog.events.KeyCodes.V) &&
  277. e.metaKey;
  278. };
  279. /**
  280. * Simulates a mouseenter event on the given target.
  281. * @param {!EventTarget} target The target for the event.
  282. * @param {?EventTarget} relatedTarget The related target for the event (e.g.,
  283. * the node that the mouse is being moved out of).
  284. * @param {!goog.math.Coordinate=} opt_coords Mouse position. Defaults to
  285. * event's target's position (if available), otherwise (0, 0).
  286. * @return {boolean} The returnValue of the event: false if preventDefault() was
  287. * called on it, true otherwise.
  288. */
  289. goog.testing.events.fireMouseEnterEvent = function(
  290. target, relatedTarget, opt_coords) {
  291. var mouseenter =
  292. new goog.testing.events.Event(goog.events.EventType.MOUSEENTER, target);
  293. mouseenter.relatedTarget = relatedTarget;
  294. goog.testing.events.setEventClientXY_(mouseenter, opt_coords);
  295. return goog.testing.events.fireBrowserEvent(mouseenter);
  296. };
  297. /**
  298. * Simulates a mouseleave event on the given target.
  299. * @param {!EventTarget} target The target for the event.
  300. * @param {?EventTarget} relatedTarget The related target for the event (e.g.,
  301. * the node that the mouse is being moved into).
  302. * @param {!goog.math.Coordinate=} opt_coords Mouse position. Defaults to
  303. * event's target's position (if available), otherwise (0, 0).
  304. * @return {boolean} The returnValue of the event: false if preventDefault() was
  305. * called on it, true otherwise.
  306. */
  307. goog.testing.events.fireMouseLeaveEvent = function(
  308. target, relatedTarget, opt_coords) {
  309. var mouseleave =
  310. new goog.testing.events.Event(goog.events.EventType.MOUSELEAVE, target);
  311. mouseleave.relatedTarget = relatedTarget;
  312. goog.testing.events.setEventClientXY_(mouseleave, opt_coords);
  313. return goog.testing.events.fireBrowserEvent(mouseleave);
  314. };
  315. /**
  316. * Simulates a mouseover event on the given target.
  317. * @param {EventTarget} target The target for the event.
  318. * @param {EventTarget} relatedTarget The related target for the event (e.g.,
  319. * the node that the mouse is being moved out of).
  320. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  321. * target's position (if available), otherwise (0, 0).
  322. * @return {boolean} The returnValue of the event: false if preventDefault() was
  323. * called on it, true otherwise.
  324. */
  325. goog.testing.events.fireMouseOverEvent = function(
  326. target, relatedTarget, opt_coords) {
  327. var mouseover =
  328. new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);
  329. mouseover.relatedTarget = relatedTarget;
  330. goog.testing.events.setEventClientXY_(mouseover, opt_coords);
  331. return goog.testing.events.fireBrowserEvent(mouseover);
  332. };
  333. /**
  334. * Simulates a mousemove event on the given target.
  335. * @param {EventTarget} target The target for the event.
  336. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  337. * target's position (if available), otherwise (0, 0).
  338. * @return {boolean} The returnValue of the event: false if preventDefault() was
  339. * called on it, true otherwise.
  340. */
  341. goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {
  342. var mousemove =
  343. new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);
  344. goog.testing.events.setEventClientXY_(mousemove, opt_coords);
  345. return goog.testing.events.fireBrowserEvent(mousemove);
  346. };
  347. /**
  348. * Simulates a mouseout event on the given target.
  349. * @param {EventTarget} target The target for the event.
  350. * @param {EventTarget} relatedTarget The related target for the event (e.g.,
  351. * the node that the mouse is being moved into).
  352. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  353. * target's position (if available), otherwise (0, 0).
  354. * @return {boolean} The returnValue of the event: false if preventDefault() was
  355. * called on it, true otherwise.
  356. */
  357. goog.testing.events.fireMouseOutEvent = function(
  358. target, relatedTarget, opt_coords) {
  359. var mouseout =
  360. new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);
  361. mouseout.relatedTarget = relatedTarget;
  362. goog.testing.events.setEventClientXY_(mouseout, opt_coords);
  363. return goog.testing.events.fireBrowserEvent(mouseout);
  364. };
  365. /**
  366. * Simulates a mousedown event on the given target.
  367. * @param {EventTarget} target The target for the event.
  368. * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
  369. * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
  370. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  371. * target's position (if available), otherwise (0, 0).
  372. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  373. * BrowserEvent.
  374. * @return {boolean} The returnValue of the event: false if preventDefault() was
  375. * called on it, true otherwise.
  376. */
  377. goog.testing.events.fireMouseDownEvent = function(
  378. target, opt_button, opt_coords, opt_eventProperties) {
  379. var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
  380. button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
  381. goog.events.BrowserEvent.IEButtonMap[button] :
  382. button;
  383. return goog.testing.events.fireMouseButtonEvent_(
  384. goog.events.EventType.MOUSEDOWN, target, button, opt_coords,
  385. opt_eventProperties);
  386. };
  387. /**
  388. * Simulates a mouseup event on the given target.
  389. * @param {EventTarget} target The target for the event.
  390. * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
  391. * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
  392. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  393. * target's position (if available), otherwise (0, 0).
  394. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  395. * BrowserEvent.
  396. * @return {boolean} The returnValue of the event: false if preventDefault() was
  397. * called on it, true otherwise.
  398. */
  399. goog.testing.events.fireMouseUpEvent = function(
  400. target, opt_button, opt_coords, opt_eventProperties) {
  401. var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
  402. button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
  403. goog.events.BrowserEvent.IEButtonMap[button] :
  404. button;
  405. return goog.testing.events.fireMouseButtonEvent_(
  406. goog.events.EventType.MOUSEUP, target, button, opt_coords,
  407. opt_eventProperties);
  408. };
  409. /**
  410. * Simulates a click event on the given target. IE only supports click with
  411. * the left mouse button.
  412. * @param {EventTarget} target The target for the event.
  413. * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
  414. * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
  415. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  416. * target's position (if available), otherwise (0, 0).
  417. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  418. * BrowserEvent.
  419. * @return {boolean} The returnValue of the event: false if preventDefault() was
  420. * called on it, true otherwise.
  421. */
  422. goog.testing.events.fireClickEvent = function(
  423. target, opt_button, opt_coords, opt_eventProperties) {
  424. return goog.testing.events.fireMouseButtonEvent_(
  425. goog.events.EventType.CLICK, target, opt_button, opt_coords,
  426. opt_eventProperties);
  427. };
  428. /**
  429. * Simulates a double-click event on the given target. Always double-clicks
  430. * with the left mouse button since no browser supports double-clicking with
  431. * any other buttons.
  432. * @param {EventTarget} target The target for the event.
  433. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  434. * target's position (if available), otherwise (0, 0).
  435. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  436. * BrowserEvent.
  437. * @return {boolean} The returnValue of the event: false if preventDefault() was
  438. * called on it, true otherwise.
  439. */
  440. goog.testing.events.fireDoubleClickEvent = function(
  441. target, opt_coords, opt_eventProperties) {
  442. return goog.testing.events.fireMouseButtonEvent_(
  443. goog.events.EventType.DBLCLICK, target,
  444. goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,
  445. opt_eventProperties);
  446. };
  447. /**
  448. * Helper function to fire a mouse event.
  449. * with the left mouse button since no browser supports double-clicking with
  450. * any other buttons.
  451. * @param {string} type The event type.
  452. * @param {EventTarget} target The target for the event.
  453. * @param {number=} opt_button Mouse button; defaults to
  454. * {@code goog.events.BrowserEvent.MouseButton.LEFT}.
  455. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  456. * target's position (if available), otherwise (0, 0).
  457. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  458. * BrowserEvent.
  459. * @return {boolean} The returnValue of the event: false if preventDefault() was
  460. * called on it, true otherwise.
  461. * @private
  462. */
  463. goog.testing.events.fireMouseButtonEvent_ = function(
  464. type, target, opt_button, opt_coords, opt_eventProperties) {
  465. var e = new goog.testing.events.Event(type, target);
  466. e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
  467. goog.testing.events.setEventClientXY_(e, opt_coords);
  468. if (opt_eventProperties) {
  469. goog.object.extend(e, opt_eventProperties);
  470. }
  471. return goog.testing.events.fireBrowserEvent(e);
  472. };
  473. /**
  474. * Simulates a contextmenu event on the given target.
  475. * @param {EventTarget} target The target for the event.
  476. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  477. * target's position (if available), otherwise (0, 0).
  478. * @return {boolean} The returnValue of the event: false if preventDefault() was
  479. * called on it, true otherwise.
  480. */
  481. goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {
  482. var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
  483. goog.events.BrowserEvent.MouseButton.LEFT :
  484. goog.events.BrowserEvent.MouseButton.RIGHT;
  485. var contextmenu =
  486. new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);
  487. contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
  488. goog.events.BrowserEvent.IEButtonMap[button] :
  489. button;
  490. contextmenu.ctrlKey = goog.userAgent.MAC;
  491. goog.testing.events.setEventClientXY_(contextmenu, opt_coords);
  492. return goog.testing.events.fireBrowserEvent(contextmenu);
  493. };
  494. /**
  495. * Simulates a mousedown, contextmenu, and the mouseup on the given event
  496. * target, with the right mouse button.
  497. * @param {EventTarget} target The target for the event.
  498. * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
  499. * target's position (if available), otherwise (0, 0).
  500. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  501. * was called on any of the events, true otherwise.
  502. */
  503. goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {
  504. var props = goog.userAgent.MAC ? {ctrlKey: true} : {};
  505. var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
  506. goog.events.BrowserEvent.MouseButton.LEFT :
  507. goog.events.BrowserEvent.MouseButton.RIGHT;
  508. var result =
  509. goog.testing.events.fireMouseDownEvent(target, button, opt_coords, props);
  510. if (goog.userAgent.WINDOWS) {
  511. // All browsers are consistent on Windows.
  512. result &= goog.testing.events.fireMouseUpEvent(target, button, opt_coords) &
  513. goog.testing.events.fireContextMenuEvent(target, opt_coords);
  514. } else {
  515. result &= goog.testing.events.fireContextMenuEvent(target, opt_coords);
  516. // GECKO on Mac and Linux always fires the mouseup after the contextmenu.
  517. // WEBKIT is really weird.
  518. //
  519. // On Linux, it sometimes fires mouseup, but most of the time doesn't.
  520. // It's really hard to reproduce consistently. I think there's some
  521. // internal race condition. If contextmenu is preventDefaulted, then
  522. // mouseup always fires.
  523. //
  524. // On Mac, it always fires mouseup and then fires a click.
  525. result &=
  526. goog.testing.events.fireMouseUpEvent(target, button, opt_coords, props);
  527. if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {
  528. result &=
  529. goog.testing.events.fireClickEvent(target, button, opt_coords, props);
  530. }
  531. }
  532. return !!result;
  533. };
  534. /**
  535. * Simulates a popstate event on the given target.
  536. * @param {EventTarget} target The target for the event.
  537. * @param {Object} state History state object.
  538. * @return {boolean} The returnValue of the event: false if preventDefault() was
  539. * called on it, true otherwise.
  540. */
  541. goog.testing.events.firePopStateEvent = function(target, state) {
  542. var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target);
  543. e.state = state;
  544. return goog.testing.events.fireBrowserEvent(e);
  545. };
  546. /**
  547. * Simulate a blur event on the given target.
  548. * @param {EventTarget} target The target for the event.
  549. * @return {boolean} The value returned by firing the blur browser event,
  550. * which returns false iff 'preventDefault' was invoked.
  551. */
  552. goog.testing.events.fireBlurEvent = function(target) {
  553. var e = new goog.testing.events.Event(goog.events.EventType.BLUR, target);
  554. return goog.testing.events.fireBrowserEvent(e);
  555. };
  556. /**
  557. * Simulate a focus event on the given target.
  558. * @param {EventTarget} target The target for the event.
  559. * @return {boolean} The value returned by firing the focus browser event,
  560. * which returns false iff 'preventDefault' was invoked.
  561. */
  562. goog.testing.events.fireFocusEvent = function(target) {
  563. var e = new goog.testing.events.Event(goog.events.EventType.FOCUS, target);
  564. return goog.testing.events.fireBrowserEvent(e);
  565. };
  566. /**
  567. * Simulate a focus-in event on the given target.
  568. * @param {!EventTarget} target The target for the event.
  569. * @return {boolean} The value returned by firing the focus-in browser event,
  570. * which returns false iff 'preventDefault' was invoked.
  571. */
  572. goog.testing.events.fireFocusInEvent = function(target) {
  573. var e = new goog.testing.events.Event(goog.events.EventType.FOCUSIN, target);
  574. return goog.testing.events.fireBrowserEvent(e);
  575. };
  576. /**
  577. * Simulates an event's capturing and bubbling phases.
  578. * @param {Event} event A simulated native event. It will be wrapped in a
  579. * normalized BrowserEvent and dispatched to Closure listeners on all
  580. * ancestors of its target (inclusive).
  581. * @return {boolean} The returnValue of the event: false if preventDefault() was
  582. * called on it, true otherwise.
  583. */
  584. goog.testing.events.fireBrowserEvent = function(event) {
  585. event.returnValue_ = true;
  586. // generate a list of ancestors
  587. var ancestors = [];
  588. for (var current = event.target; current; current = current.parentNode) {
  589. ancestors.push(current);
  590. }
  591. // dispatch capturing listeners
  592. for (var j = ancestors.length - 1; j >= 0 && !event.propagationStopped_;
  593. j--) {
  594. goog.events.fireListeners(
  595. ancestors[j], event.type, true,
  596. new goog.events.BrowserEvent(event, ancestors[j]));
  597. }
  598. // dispatch bubbling listeners
  599. for (var j = 0; j < ancestors.length && !event.propagationStopped_; j++) {
  600. goog.events.fireListeners(
  601. ancestors[j], event.type, false,
  602. new goog.events.BrowserEvent(event, ancestors[j]));
  603. }
  604. return event.returnValue_;
  605. };
  606. /**
  607. * Simulates a touchstart event on the given target.
  608. * @param {EventTarget} target The target for the event.
  609. * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
  610. * target's position (if available), otherwise (0, 0).
  611. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  612. * BrowserEvent.
  613. * @return {boolean} The returnValue of the event: false if preventDefault() was
  614. * called on it, true otherwise.
  615. */
  616. goog.testing.events.fireTouchStartEvent = function(
  617. target, opt_coords, opt_eventProperties) {
  618. // TODO: Support multi-touch events with array of coordinates.
  619. var touchstart =
  620. new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);
  621. goog.testing.events.setEventClientXY_(touchstart, opt_coords);
  622. if (opt_eventProperties) {
  623. goog.object.extend(touchstart, opt_eventProperties);
  624. }
  625. return goog.testing.events.fireBrowserEvent(touchstart);
  626. };
  627. /**
  628. * Simulates a touchmove event on the given target.
  629. * @param {EventTarget} target The target for the event.
  630. * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
  631. * target's position (if available), otherwise (0, 0).
  632. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  633. * BrowserEvent.
  634. * @return {boolean} The returnValue of the event: false if preventDefault() was
  635. * called on it, true otherwise.
  636. */
  637. goog.testing.events.fireTouchMoveEvent = function(
  638. target, opt_coords, opt_eventProperties) {
  639. // TODO: Support multi-touch events with array of coordinates.
  640. var touchmove =
  641. new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);
  642. goog.testing.events.setEventClientXY_(touchmove, opt_coords);
  643. if (opt_eventProperties) {
  644. goog.object.extend(touchmove, opt_eventProperties);
  645. }
  646. return goog.testing.events.fireBrowserEvent(touchmove);
  647. };
  648. /**
  649. * Simulates a touchend event on the given target.
  650. * @param {EventTarget} target The target for the event.
  651. * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
  652. * target's position (if available), otherwise (0, 0).
  653. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  654. * BrowserEvent.
  655. * @return {boolean} The returnValue of the event: false if preventDefault() was
  656. * called on it, true otherwise.
  657. */
  658. goog.testing.events.fireTouchEndEvent = function(
  659. target, opt_coords, opt_eventProperties) {
  660. // TODO: Support multi-touch events with array of coordinates.
  661. var touchend =
  662. new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);
  663. goog.testing.events.setEventClientXY_(touchend, opt_coords);
  664. if (opt_eventProperties) {
  665. goog.object.extend(touchend, opt_eventProperties);
  666. }
  667. return goog.testing.events.fireBrowserEvent(touchend);
  668. };
  669. /**
  670. * Simulates a simple touch sequence on the given target.
  671. * @param {EventTarget} target The target for the event.
  672. * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event
  673. * target's position (if available), otherwise (0, 0).
  674. * @param {Object=} opt_eventProperties Event properties to be mixed into the
  675. * BrowserEvent.
  676. * @return {boolean} The returnValue of the sequence: false if preventDefault()
  677. * was called on any of the events, true otherwise.
  678. */
  679. goog.testing.events.fireTouchSequence = function(
  680. target, opt_coords, opt_eventProperties) {
  681. // TODO: Support multi-touch events with array of coordinates.
  682. // Fire touchstart, touchmove, touchend then return the bitwise AND of the 3.
  683. return !!(
  684. goog.testing.events.fireTouchStartEvent(
  685. target, opt_coords, opt_eventProperties) &
  686. goog.testing.events.fireTouchEndEvent(
  687. target, opt_coords, opt_eventProperties));
  688. };
  689. /**
  690. * Mixins a listenable into the given object. This turns the object
  691. * into a goog.events.Listenable. This is useful, for example, when
  692. * you need to mock a implementation of listenable and still want it
  693. * to work with goog.events.
  694. * @param {!Object} obj The object to mixin into.
  695. */
  696. goog.testing.events.mixinListenable = function(obj) {
  697. var listenable = new goog.events.EventTarget();
  698. listenable.setTargetForTesting(obj);
  699. var listenablePrototype = goog.events.EventTarget.prototype;
  700. var disposablePrototype = goog.Disposable.prototype;
  701. for (var key in listenablePrototype) {
  702. if (listenablePrototype.hasOwnProperty(key) ||
  703. disposablePrototype.hasOwnProperty(key)) {
  704. var member = listenablePrototype[key];
  705. if (goog.isFunction(member)) {
  706. obj[key] = goog.bind(member, listenable);
  707. } else {
  708. obj[key] = member;
  709. }
  710. }
  711. }
  712. };