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
35goog.provide('goog.testing.events');
36goog.provide('goog.testing.events.Event');
37
38goog.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');
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 */
65goog.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 */
81goog.testing.events.Event.prototype.propagationStopped_ = false;
82
83
84/** @override */
85goog.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 */
95goog.testing.events.Event.prototype.returnValue_ = true;
96
97
98/** @override */
99goog.testing.events.Event.prototype.stopPropagation = function() {
100 this.propagationStopped_ = true;
101};
102
103
104/** @override */
105goog.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 */
121goog.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 */
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;
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 */
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};
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 */
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 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 */
226goog.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 */
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;
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 */
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};
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 */
302goog.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 */
322goog.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 */
342goog.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 */
360goog.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 */
379goog.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 */
401goog.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 */
425goog.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 */
449goog.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 */
468goog.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 */
493goog.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 */
514goog.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 */
537goog.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 */
582goog.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 */
595goog.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 */
608goog.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 */
623goog.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 */
662goog.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 */
685goog.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 */
708goog.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 */
731goog.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 */
749goog.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};