# 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 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 | /**
16 | * @fileoverview Event Simulation.
17 | *
18 | * Utility functions for simulating events at the Closure level. All functions
19 | * in this package generate events by calling goog.events.fireListeners,
20 | * rather than interfacing with the browser directly. This is intended for
21 | * testing purposes, and should not be used in production code.
22 | *
23 | * The decision to use Closure events and dispatchers instead of the browser's
24 | * native events and dispatchers was conscious and deliberate. Native event
25 | * dispatchers have their own set of quirks and edge cases. Pure JS dispatchers
26 | * 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 you
30 | * sink time into this.
31 | *
32 | * @author nicksantos@google.com (Nick Santos)
33 | */
34 |
35 | goog.provide('goog.testing.events');
36 | goog.provide('goog.testing.events.Event');
37 |
38 | goog.require('goog.Disposable');
39 | goog.require('goog.asserts');
40 | goog.require('goog.dom.NodeType');
41 | goog.require('goog.events');
42 | goog.require('goog.events.BrowserEvent');
43 | goog.require('goog.events.BrowserFeature');
44 | goog.require('goog.events.EventTarget');
45 | goog.require('goog.events.EventType');
46 | goog.require('goog.events.KeyCodes');
47 | goog.require('goog.object');
48 | goog.require('goog.style');
49 | goog.require('goog.userAgent');
50 |
51 |
52 |
53 | /**
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 to
57 | * 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 of
61 | * this event.
62 | * @constructor
63 | * @extends {Event}
64 | */
65 | goog.testing.events.Event = function(type, opt_target) {
66 | this.type = type;
67 |
68 | this.target = /** @type {EventTarget} */ (opt_target || null);
69 |
70 | this.currentTarget = this.target;
71 | };
72 |
73 |
74 | /**
75 | * Whether to cancel the event in internal capture/bubble processing for IE.
76 | * @type {boolean}
77 | * @public
78 | * @suppress {underscore|visibility} Technically public, but referencing this
79 | * outside this package is strongly discouraged.
80 | */
81 | goog.testing.events.Event.prototype.propagationStopped_ = false;
82 |
83 |
84 | /** @override */
85 | goog.testing.events.Event.prototype.defaultPrevented = false;
86 |
87 |
88 | /**
89 | * Return value for in internal capture/bubble processing for IE.
90 | * @type {boolean}
91 | * @public
92 | * @suppress {underscore|visibility} Technically public, but referencing this
93 | * outside this package is strongly discouraged.
94 | */
95 | goog.testing.events.Event.prototype.returnValue_ = true;
96 |
97 |
98 | /** @override */
99 | goog.testing.events.Event.prototype.stopPropagation = function() {
100 | this.propagationStopped_ = true;
101 | };
102 |
103 |
104 | /** @override */
105 | goog.testing.events.Event.prototype.preventDefault = function() {
106 | this.defaultPrevented = true;
107 | this.returnValue_ = false;
108 | };
109 |
110 |
111 | /**
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 eventually
115 | * update the method signatures to not take nullables. See http://b/8961907
116 | *
117 | * @param {EventTarget} target A target to assert.
118 | * @return {!EventTarget} The target, guaranteed to exist.
119 | * @private
120 | */
121 | goog.testing.events.assertEventTarget_ = function(target) {
122 | return goog.asserts.assert(target, 'EventTarget should be defined.');
123 | };
124 |
125 |
126 | /**
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's
130 | * target's position (if available), otherwise (0, 0).
131 | * @private
132 | */
133 | goog.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;
145 |
146 | // Pretend the browser window is at (0, 0).
147 | event.screenX = event.clientX;
148 | event.screenY = event.clientY;
149 | };
150 |
151 |
152 | /**
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's
159 | * target's position (if available), otherwise (0, 0).
160 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
161 | * BrowserEvent.
162 | * @return {boolean} The returnValue of the sequence: false if preventDefault()
163 | * was called on any of the events, true otherwise.
164 | */
165 | goog.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 | };
175 |
176 |
177 | /**
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's
182 | * target's position (if available), otherwise (0, 0).
183 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
184 | * BrowserEvent.
185 | * @return {boolean} The returnValue of the sequence: false if preventDefault()
186 | * was called on any of the events, true otherwise.
187 | */
188 | goog.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 a
200 | // 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 | };
213 |
214 |
215 | /**
216 | * Simulates a complete keystroke (keydown, keypress, and keyup). Note that
217 | * 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 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.fireKeySequence = function(
227 | target, keyCode, opt_eventProperties) {
228 | return goog.testing.events.fireNonAsciiKeySequence(target, keyCode, keyCode,
229 | opt_eventProperties);
230 | };
231 |
232 |
233 | /**
234 | * Simulates a complete keystroke (keydown, keypress, and keyup) when typing
235 | * a non-ASCII character. Same as fireKeySequence, the keypress will not fire
236 | * 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 the
242 | * BrowserEvent.
243 | * @return {boolean} The returnValue of the sequence: false if preventDefault()
244 | * was called on any of the events, true otherwise.
245 | */
246 | goog.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;
256 |
257 | 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 | }
262 |
263 | // Fire keydown, keypress, and keyup. Note that if the keydown is
264 | // 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 | };
276 |
277 |
278 | /**
279 | * @param {goog.testing.events.Event} e The event.
280 | * @return {boolean} Whether this is the Gecko/Mac's Meta-C/V/X, which
281 | * is broken and requires special handling.
282 | * @private
283 | */
284 | goog.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 | };
290 |
291 |
292 | /**
293 | * Simulates a mouseenter 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
298 | * event's target's position (if available), otherwise (0, 0).
299 | * @return {boolean} The returnValue of the event: false if preventDefault() was
300 | * called on it, true otherwise.
301 | */
302 | goog.testing.events.fireMouseEnterEvent = function(target, relatedTarget,
303 | opt_coords) {
304 | var mouseenter =
305 | new goog.testing.events.Event(goog.events.EventType.MOUSEENTER, target);
306 | mouseenter.relatedTarget = relatedTarget;
307 | goog.testing.events.setEventClientXY_(mouseenter, opt_coords);
308 | return goog.testing.events.fireBrowserEvent(mouseenter);
309 | };
310 |
311 |
312 | /**
313 | * Simulates a mouseleave event on the given target.
314 | * @param {!EventTarget} target The target for the event.
315 | * @param {?EventTarget} relatedTarget The related target for the event (e.g.,
316 | * the node that the mouse is being moved into).
317 | * @param {!goog.math.Coordinate=} opt_coords Mouse position. Defaults to
318 | * event's target's position (if available), otherwise (0, 0).
319 | * @return {boolean} The returnValue of the event: false if preventDefault() was
320 | * called on it, true otherwise.
321 | */
322 | goog.testing.events.fireMouseLeaveEvent = function(target, relatedTarget,
323 | opt_coords) {
324 | var mouseleave =
325 | new goog.testing.events.Event(goog.events.EventType.MOUSELEAVE, target);
326 | mouseleave.relatedTarget = relatedTarget;
327 | goog.testing.events.setEventClientXY_(mouseleave, opt_coords);
328 | return goog.testing.events.fireBrowserEvent(mouseleave);
329 | };
330 |
331 |
332 | /**
333 | * Simulates a mouseover event on the given target.
334 | * @param {EventTarget} target The target for the event.
335 | * @param {EventTarget} relatedTarget The related target for the event (e.g.,
336 | * the node that the mouse is being moved out of).
337 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
338 | * target's position (if available), otherwise (0, 0).
339 | * @return {boolean} The returnValue of the event: false if preventDefault() was
340 | * called on it, true otherwise.
341 | */
342 | goog.testing.events.fireMouseOverEvent = function(target, relatedTarget,
343 | opt_coords) {
344 | var mouseover =
345 | new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);
346 | mouseover.relatedTarget = relatedTarget;
347 | goog.testing.events.setEventClientXY_(mouseover, opt_coords);
348 | return goog.testing.events.fireBrowserEvent(mouseover);
349 | };
350 |
351 |
352 | /**
353 | * Simulates a mousemove event on the given target.
354 | * @param {EventTarget} target The target for the event.
355 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
356 | * target's position (if available), otherwise (0, 0).
357 | * @return {boolean} The returnValue of the event: false if preventDefault() was
358 | * called on it, true otherwise.
359 | */
360 | goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {
361 | var mousemove =
362 | new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);
363 |
364 | goog.testing.events.setEventClientXY_(mousemove, opt_coords);
365 | return goog.testing.events.fireBrowserEvent(mousemove);
366 | };
367 |
368 |
369 | /**
370 | * Simulates a mouseout event on the given target.
371 | * @param {EventTarget} target The target for the event.
372 | * @param {EventTarget} relatedTarget The related target for the event (e.g.,
373 | * the node that the mouse is being moved into).
374 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
375 | * target's position (if available), otherwise (0, 0).
376 | * @return {boolean} The returnValue of the event: false if preventDefault() was
377 | * called on it, true otherwise.
378 | */
379 | goog.testing.events.fireMouseOutEvent = function(target, relatedTarget,
380 | opt_coords) {
381 | var mouseout =
382 | new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);
383 | mouseout.relatedTarget = relatedTarget;
384 | goog.testing.events.setEventClientXY_(mouseout, opt_coords);
385 | return goog.testing.events.fireBrowserEvent(mouseout);
386 | };
387 |
388 |
389 | /**
390 | * Simulates a mousedown event on the given target.
391 | * @param {EventTarget} target The target for the event.
392 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
393 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
394 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
395 | * target's position (if available), otherwise (0, 0).
396 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
397 | * BrowserEvent.
398 | * @return {boolean} The returnValue of the event: false if preventDefault() was
399 | * called on it, true otherwise.
400 | */
401 | goog.testing.events.fireMouseDownEvent =
402 | function(target, opt_button, opt_coords, opt_eventProperties) {
403 |
404 | var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
405 | button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
406 | goog.events.BrowserEvent.IEButtonMap[button] : button;
407 | return goog.testing.events.fireMouseButtonEvent_(
408 | goog.events.EventType.MOUSEDOWN, target, button, opt_coords,
409 | opt_eventProperties);
410 | };
411 |
412 |
413 | /**
414 | * Simulates a mouseup event on the given target.
415 | * @param {EventTarget} target The target for the event.
416 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
417 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
418 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
419 | * target's position (if available), otherwise (0, 0).
420 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
421 | * BrowserEvent.
422 | * @return {boolean} The returnValue of the event: false if preventDefault() was
423 | * called on it, true otherwise.
424 | */
425 | goog.testing.events.fireMouseUpEvent =
426 | function(target, opt_button, opt_coords, opt_eventProperties) {
427 | var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
428 | button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
429 | goog.events.BrowserEvent.IEButtonMap[button] : button;
430 | return goog.testing.events.fireMouseButtonEvent_(
431 | goog.events.EventType.MOUSEUP, target, button, opt_coords,
432 | opt_eventProperties);
433 | };
434 |
435 |
436 | /**
437 | * Simulates a click event on the given target. IE only supports click with
438 | * the left mouse button.
439 | * @param {EventTarget} target The target for the event.
440 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
441 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
442 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
443 | * target's position (if available), otherwise (0, 0).
444 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
445 | * BrowserEvent.
446 | * @return {boolean} The returnValue of the event: false if preventDefault() was
447 | * called on it, true otherwise.
448 | */
449 | goog.testing.events.fireClickEvent =
450 | function(target, opt_button, opt_coords, opt_eventProperties) {
451 | return goog.testing.events.fireMouseButtonEvent_(goog.events.EventType.CLICK,
452 | target, opt_button, opt_coords, opt_eventProperties);
453 | };
454 |
455 |
456 | /**
457 | * Simulates a double-click event on the given target. Always double-clicks
458 | * with the left mouse button since no browser supports double-clicking with
459 | * any other buttons.
460 | * @param {EventTarget} target The target for the event.
461 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
462 | * target's position (if available), otherwise (0, 0).
463 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
464 | * BrowserEvent.
465 | * @return {boolean} The returnValue of the event: false if preventDefault() was
466 | * called on it, true otherwise.
467 | */
468 | goog.testing.events.fireDoubleClickEvent =
469 | function(target, opt_coords, opt_eventProperties) {
470 | return goog.testing.events.fireMouseButtonEvent_(
471 | goog.events.EventType.DBLCLICK, target,
472 | goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,
473 | opt_eventProperties);
474 | };
475 |
476 |
477 | /**
478 | * Helper function to fire a mouse event.
479 | * with the left mouse button since no browser supports double-clicking with
480 | * any other buttons.
481 | * @param {string} type The event type.
482 | * @param {EventTarget} target The target for the event.
483 | * @param {number=} opt_button Mouse button; defaults to
484 | * {@code goog.events.BrowserEvent.MouseButton.LEFT}.
485 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
486 | * target's position (if available), otherwise (0, 0).
487 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
488 | * BrowserEvent.
489 | * @return {boolean} The returnValue of the event: false if preventDefault() was
490 | * called on it, true otherwise.
491 | * @private
492 | */
493 | goog.testing.events.fireMouseButtonEvent_ =
494 | function(type, target, opt_button, opt_coords, opt_eventProperties) {
495 | var e =
496 | new goog.testing.events.Event(type, target);
497 | e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
498 | goog.testing.events.setEventClientXY_(e, opt_coords);
499 | if (opt_eventProperties) {
500 | goog.object.extend(e, opt_eventProperties);
501 | }
502 | return goog.testing.events.fireBrowserEvent(e);
503 | };
504 |
505 |
506 | /**
507 | * Simulates a contextmenu event on the given target.
508 | * @param {EventTarget} target The target for the event.
509 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
510 | * target's position (if available), otherwise (0, 0).
511 | * @return {boolean} The returnValue of the event: false if preventDefault() was
512 | * called on it, true otherwise.
513 | */
514 | goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {
515 | var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
516 | goog.events.BrowserEvent.MouseButton.LEFT :
517 | goog.events.BrowserEvent.MouseButton.RIGHT;
518 | var contextmenu =
519 | new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);
520 | contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
521 | goog.events.BrowserEvent.IEButtonMap[button] : button;
522 | contextmenu.ctrlKey = goog.userAgent.MAC;
523 | goog.testing.events.setEventClientXY_(contextmenu, opt_coords);
524 | return goog.testing.events.fireBrowserEvent(contextmenu);
525 | };
526 |
527 |
528 | /**
529 | * Simulates a mousedown, contextmenu, and the mouseup on the given event
530 | * target, with the right mouse button.
531 | * @param {EventTarget} target The target for the event.
532 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
533 | * target's position (if available), otherwise (0, 0).
534 | * @return {boolean} The returnValue of the sequence: false if preventDefault()
535 | * was called on any of the events, true otherwise.
536 | */
537 | goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {
538 | var props = goog.userAgent.MAC ? {ctrlKey: true} : {};
539 | var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
540 | goog.events.BrowserEvent.MouseButton.LEFT :
541 | goog.events.BrowserEvent.MouseButton.RIGHT;
542 |
543 | var result = goog.testing.events.fireMouseDownEvent(target,
544 | button, opt_coords, props);
545 | if (goog.userAgent.WINDOWS) {
546 | // All browsers are consistent on Windows.
547 | result &= goog.testing.events.fireMouseUpEvent(target,
548 | button, opt_coords) &
549 | goog.testing.events.fireContextMenuEvent(target, opt_coords);
550 | } else {
551 | result &= goog.testing.events.fireContextMenuEvent(target, opt_coords);
552 |
553 | // GECKO on Mac and Linux always fires the mouseup after the contextmenu.
554 |
555 | // WEBKIT is really weird.
556 | //
557 | // On Linux, it sometimes fires mouseup, but most of the time doesn't.
558 | // It's really hard to reproduce consistently. I think there's some
559 | // internal race condition. If contextmenu is preventDefaulted, then
560 | // mouseup always fires.
561 | //
562 | // On Mac, it always fires mouseup and then fires a click.
563 | result &= goog.testing.events.fireMouseUpEvent(target,
564 | button, opt_coords, props);
565 |
566 | if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {
567 | result &= goog.testing.events.fireClickEvent(
568 | target, button, opt_coords, props);
569 | }
570 | }
571 | return !!result;
572 | };
573 |
574 |
575 | /**
576 | * Simulates a popstate event on the given target.
577 | * @param {EventTarget} target The target for the event.
578 | * @param {Object} state History state object.
579 | * @return {boolean} The returnValue of the event: false if preventDefault() was
580 | * called on it, true otherwise.
581 | */
582 | goog.testing.events.firePopStateEvent = function(target, state) {
583 | var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target);
584 | e.state = state;
585 | return goog.testing.events.fireBrowserEvent(e);
586 | };
587 |
588 |
589 | /**
590 | * Simulate a blur event on the given target.
591 | * @param {EventTarget} target The target for the event.
592 | * @return {boolean} The value returned by firing the blur browser event,
593 | * which returns false iff 'preventDefault' was invoked.
594 | */
595 | goog.testing.events.fireBlurEvent = function(target) {
596 | var e = new goog.testing.events.Event(
597 | goog.events.EventType.BLUR, target);
598 | return goog.testing.events.fireBrowserEvent(e);
599 | };
600 |
601 |
602 | /**
603 | * Simulate a focus event on the given target.
604 | * @param {EventTarget} target The target for the event.
605 | * @return {boolean} The value returned by firing the focus browser event,
606 | * which returns false iff 'preventDefault' was invoked.
607 | */
608 | goog.testing.events.fireFocusEvent = function(target) {
609 | var e = new goog.testing.events.Event(
610 | goog.events.EventType.FOCUS, target);
611 | return goog.testing.events.fireBrowserEvent(e);
612 | };
613 |
614 |
615 | /**
616 | * Simulates an event's capturing and bubbling phases.
617 | * @param {Event} event A simulated native event. It will be wrapped in a
618 | * normalized BrowserEvent and dispatched to Closure listeners on all
619 | * ancestors of its target (inclusive).
620 | * @return {boolean} The returnValue of the event: false if preventDefault() was
621 | * called on it, true otherwise.
622 | */
623 | goog.testing.events.fireBrowserEvent = function(event) {
624 | event.returnValue_ = true;
625 |
626 | // generate a list of ancestors
627 | var ancestors = [];
628 | for (var current = event.target; current; current = current.parentNode) {
629 | ancestors.push(current);
630 | }
631 |
632 | // dispatch capturing listeners
633 | for (var j = ancestors.length - 1;
634 | j >= 0 && !event.propagationStopped_;
635 | j--) {
636 | goog.events.fireListeners(ancestors[j], event.type, true,
637 | new goog.events.BrowserEvent(event, ancestors[j]));
638 | }
639 |
640 | // dispatch bubbling listeners
641 | for (var j = 0;
642 | j < ancestors.length && !event.propagationStopped_;
643 | j++) {
644 | goog.events.fireListeners(ancestors[j], event.type, false,
645 | new goog.events.BrowserEvent(event, ancestors[j]));
646 | }
647 |
648 | return event.returnValue_;
649 | };
650 |
651 |
652 | /**
653 | * Simulates a touchstart event on the given target.
654 | * @param {EventTarget} target The target for the event.
655 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
656 | * target's position (if available), otherwise (0, 0).
657 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
658 | * BrowserEvent.
659 | * @return {boolean} The returnValue of the event: false if preventDefault() was
660 | * called on it, true otherwise.
661 | */
662 | goog.testing.events.fireTouchStartEvent = function(
663 | target, opt_coords, opt_eventProperties) {
664 | // TODO: Support multi-touch events with array of coordinates.
665 | var touchstart =
666 | new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);
667 | goog.testing.events.setEventClientXY_(touchstart, opt_coords);
668 | if (opt_eventProperties) {
669 | goog.object.extend(touchstart, opt_eventProperties);
670 | }
671 | return goog.testing.events.fireBrowserEvent(touchstart);
672 | };
673 |
674 |
675 | /**
676 | * Simulates a touchmove event on the given target.
677 | * @param {EventTarget} target The target for the event.
678 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
679 | * target's position (if available), otherwise (0, 0).
680 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
681 | * BrowserEvent.
682 | * @return {boolean} The returnValue of the event: false if preventDefault() was
683 | * called on it, true otherwise.
684 | */
685 | goog.testing.events.fireTouchMoveEvent = function(
686 | target, opt_coords, opt_eventProperties) {
687 | // TODO: Support multi-touch events with array of coordinates.
688 | var touchmove =
689 | new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);
690 | goog.testing.events.setEventClientXY_(touchmove, opt_coords);
691 | if (opt_eventProperties) {
692 | goog.object.extend(touchmove, opt_eventProperties);
693 | }
694 | return goog.testing.events.fireBrowserEvent(touchmove);
695 | };
696 |
697 |
698 | /**
699 | * Simulates a touchend event on the given target.
700 | * @param {EventTarget} target The target for the event.
701 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
702 | * target's position (if available), otherwise (0, 0).
703 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
704 | * BrowserEvent.
705 | * @return {boolean} The returnValue of the event: false if preventDefault() was
706 | * called on it, true otherwise.
707 | */
708 | goog.testing.events.fireTouchEndEvent = function(
709 | target, opt_coords, opt_eventProperties) {
710 | // TODO: Support multi-touch events with array of coordinates.
711 | var touchend =
712 | new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);
713 | goog.testing.events.setEventClientXY_(touchend, opt_coords);
714 | if (opt_eventProperties) {
715 | goog.object.extend(touchend, opt_eventProperties);
716 | }
717 | return goog.testing.events.fireBrowserEvent(touchend);
718 | };
719 |
720 |
721 | /**
722 | * Simulates a simple touch sequence on the given target.
723 | * @param {EventTarget} target The target for the event.
724 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event
725 | * target's position (if available), otherwise (0, 0).
726 | * @param {Object=} opt_eventProperties Event properties to be mixed into the
727 | * BrowserEvent.
728 | * @return {boolean} The returnValue of the sequence: false if preventDefault()
729 | * was called on any of the events, true otherwise.
730 | */
731 | goog.testing.events.fireTouchSequence = function(
732 | target, opt_coords, opt_eventProperties) {
733 | // TODO: Support multi-touch events with array of coordinates.
734 | // Fire touchstart, touchmove, touchend then return the bitwise AND of the 3.
735 | return !!(goog.testing.events.fireTouchStartEvent(
736 | target, opt_coords, opt_eventProperties) &
737 | goog.testing.events.fireTouchEndEvent(
738 | target, opt_coords, opt_eventProperties));
739 | };
740 |
741 |
742 | /**
743 | * Mixins a listenable into the given object. This turns the object
744 | * into a goog.events.Listenable. This is useful, for example, when
745 | * you need to mock a implementation of listenable and still want it
746 | * to work with goog.events.
747 | * @param {!Object} obj The object to mixin into.
748 | */
749 | goog.testing.events.mixinListenable = function(obj) {
750 | var listenable = new goog.events.EventTarget();
751 |
752 | listenable.setTargetForTesting(obj);
753 |
754 | var listenablePrototype = goog.events.EventTarget.prototype;
755 | var disposablePrototype = goog.Disposable.prototype;
756 | for (var key in listenablePrototype) {
757 | if (listenablePrototype.hasOwnProperty(key) ||
758 | disposablePrototype.hasOwnProperty(key)) {
759 | var member = listenablePrototype[key];
760 | if (goog.isFunction(member)) {
761 | obj[key] = goog.bind(member, listenable);
762 | } else {
763 | obj[key] = member;
764 | }
765 | }
766 | }
767 | };