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 mouseover event on the given target. |
294 | * @param {EventTarget} target The target for the event. |
295 | * @param {EventTarget} relatedTarget The related target for the event (e.g., |
296 | * the node that the mouse is being moved out of). |
297 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
298 | * 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.fireMouseOverEvent = function(target, relatedTarget, |
303 | opt_coords) { |
304 | var mouseover = |
305 | new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target); |
306 | mouseover.relatedTarget = relatedTarget; |
307 | goog.testing.events.setEventClientXY_(mouseover, opt_coords); |
308 | return goog.testing.events.fireBrowserEvent(mouseover); |
309 | }; |
310 | |
311 | |
312 | /** |
313 | * Simulates a mousemove event on the given target. |
314 | * @param {EventTarget} target The target for the event. |
315 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
316 | * target's position (if available), otherwise (0, 0). |
317 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
318 | * called on it, true otherwise. |
319 | */ |
320 | goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) { |
321 | var mousemove = |
322 | new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target); |
323 | |
324 | goog.testing.events.setEventClientXY_(mousemove, opt_coords); |
325 | return goog.testing.events.fireBrowserEvent(mousemove); |
326 | }; |
327 | |
328 | |
329 | /** |
330 | * Simulates a mouseout event on the given target. |
331 | * @param {EventTarget} target The target for the event. |
332 | * @param {EventTarget} relatedTarget The related target for the event (e.g., |
333 | * the node that the mouse is being moved into). |
334 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
335 | * target's position (if available), otherwise (0, 0). |
336 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
337 | * called on it, true otherwise. |
338 | */ |
339 | goog.testing.events.fireMouseOutEvent = function(target, relatedTarget, |
340 | opt_coords) { |
341 | var mouseout = |
342 | new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target); |
343 | mouseout.relatedTarget = relatedTarget; |
344 | goog.testing.events.setEventClientXY_(mouseout, opt_coords); |
345 | return goog.testing.events.fireBrowserEvent(mouseout); |
346 | }; |
347 | |
348 | |
349 | /** |
350 | * Simulates a mousedown event on the given target. |
351 | * @param {EventTarget} target The target for the event. |
352 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button; |
353 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}. |
354 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
355 | * target's position (if available), otherwise (0, 0). |
356 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
357 | * BrowserEvent. |
358 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
359 | * called on it, true otherwise. |
360 | */ |
361 | goog.testing.events.fireMouseDownEvent = |
362 | function(target, opt_button, opt_coords, opt_eventProperties) { |
363 | |
364 | var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT; |
365 | button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ? |
366 | goog.events.BrowserEvent.IEButtonMap[button] : button; |
367 | return goog.testing.events.fireMouseButtonEvent_( |
368 | goog.events.EventType.MOUSEDOWN, target, button, opt_coords, |
369 | opt_eventProperties); |
370 | }; |
371 | |
372 | |
373 | /** |
374 | * Simulates a mouseup event on the given target. |
375 | * @param {EventTarget} target The target for the event. |
376 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button; |
377 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}. |
378 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
379 | * target's position (if available), otherwise (0, 0). |
380 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
381 | * BrowserEvent. |
382 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
383 | * called on it, true otherwise. |
384 | */ |
385 | goog.testing.events.fireMouseUpEvent = |
386 | function(target, opt_button, opt_coords, opt_eventProperties) { |
387 | var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT; |
388 | button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ? |
389 | goog.events.BrowserEvent.IEButtonMap[button] : button; |
390 | return goog.testing.events.fireMouseButtonEvent_( |
391 | goog.events.EventType.MOUSEUP, target, button, opt_coords, |
392 | opt_eventProperties); |
393 | }; |
394 | |
395 | |
396 | /** |
397 | * Simulates a click event on the given target. IE only supports click with |
398 | * the left mouse button. |
399 | * @param {EventTarget} target The target for the event. |
400 | * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button; |
401 | * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}. |
402 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
403 | * target's position (if available), otherwise (0, 0). |
404 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
405 | * BrowserEvent. |
406 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
407 | * called on it, true otherwise. |
408 | */ |
409 | goog.testing.events.fireClickEvent = |
410 | function(target, opt_button, opt_coords, opt_eventProperties) { |
411 | return goog.testing.events.fireMouseButtonEvent_(goog.events.EventType.CLICK, |
412 | target, opt_button, opt_coords, opt_eventProperties); |
413 | }; |
414 | |
415 | |
416 | /** |
417 | * Simulates a double-click event on the given target. Always double-clicks |
418 | * with the left mouse button since no browser supports double-clicking with |
419 | * any other buttons. |
420 | * @param {EventTarget} target The target for the event. |
421 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
422 | * target's position (if available), otherwise (0, 0). |
423 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
424 | * BrowserEvent. |
425 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
426 | * called on it, true otherwise. |
427 | */ |
428 | goog.testing.events.fireDoubleClickEvent = |
429 | function(target, opt_coords, opt_eventProperties) { |
430 | return goog.testing.events.fireMouseButtonEvent_( |
431 | goog.events.EventType.DBLCLICK, target, |
432 | goog.events.BrowserEvent.MouseButton.LEFT, opt_coords, |
433 | opt_eventProperties); |
434 | }; |
435 | |
436 | |
437 | /** |
438 | * Helper function to fire a mouse event. |
439 | * with the left mouse button since no browser supports double-clicking with |
440 | * any other buttons. |
441 | * @param {string} type The event type. |
442 | * @param {EventTarget} target The target for the event. |
443 | * @param {number=} opt_button Mouse button; defaults to |
444 | * {@code goog.events.BrowserEvent.MouseButton.LEFT}. |
445 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
446 | * target's position (if available), otherwise (0, 0). |
447 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
448 | * BrowserEvent. |
449 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
450 | * called on it, true otherwise. |
451 | * @private |
452 | */ |
453 | goog.testing.events.fireMouseButtonEvent_ = |
454 | function(type, target, opt_button, opt_coords, opt_eventProperties) { |
455 | var e = |
456 | new goog.testing.events.Event(type, target); |
457 | e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT; |
458 | goog.testing.events.setEventClientXY_(e, opt_coords); |
459 | if (opt_eventProperties) { |
460 | goog.object.extend(e, opt_eventProperties); |
461 | } |
462 | return goog.testing.events.fireBrowserEvent(e); |
463 | }; |
464 | |
465 | |
466 | /** |
467 | * Simulates a contextmenu event on the given target. |
468 | * @param {EventTarget} target The target for the event. |
469 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
470 | * target's position (if available), otherwise (0, 0). |
471 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
472 | * called on it, true otherwise. |
473 | */ |
474 | goog.testing.events.fireContextMenuEvent = function(target, opt_coords) { |
475 | var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ? |
476 | goog.events.BrowserEvent.MouseButton.LEFT : |
477 | goog.events.BrowserEvent.MouseButton.RIGHT; |
478 | var contextmenu = |
479 | new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target); |
480 | contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ? |
481 | goog.events.BrowserEvent.IEButtonMap[button] : button; |
482 | contextmenu.ctrlKey = goog.userAgent.MAC; |
483 | goog.testing.events.setEventClientXY_(contextmenu, opt_coords); |
484 | return goog.testing.events.fireBrowserEvent(contextmenu); |
485 | }; |
486 | |
487 | |
488 | /** |
489 | * Simulates a mousedown, contextmenu, and the mouseup on the given event |
490 | * target, with the right mouse button. |
491 | * @param {EventTarget} target The target for the event. |
492 | * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's |
493 | * target's position (if available), otherwise (0, 0). |
494 | * @return {boolean} The returnValue of the sequence: false if preventDefault() |
495 | * was called on any of the events, true otherwise. |
496 | */ |
497 | goog.testing.events.fireContextMenuSequence = function(target, opt_coords) { |
498 | var props = goog.userAgent.MAC ? {ctrlKey: true} : {}; |
499 | var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ? |
500 | goog.events.BrowserEvent.MouseButton.LEFT : |
501 | goog.events.BrowserEvent.MouseButton.RIGHT; |
502 | |
503 | var result = goog.testing.events.fireMouseDownEvent(target, |
504 | button, opt_coords, props); |
505 | if (goog.userAgent.WINDOWS) { |
506 | // All browsers are consistent on Windows. |
507 | result &= goog.testing.events.fireMouseUpEvent(target, |
508 | button, opt_coords) & |
509 | goog.testing.events.fireContextMenuEvent(target, opt_coords); |
510 | } else { |
511 | result &= goog.testing.events.fireContextMenuEvent(target, opt_coords); |
512 | |
513 | // GECKO on Mac and Linux always fires the mouseup after the contextmenu. |
514 | |
515 | // WEBKIT is really weird. |
516 | // |
517 | // On Linux, it sometimes fires mouseup, but most of the time doesn't. |
518 | // It's really hard to reproduce consistently. I think there's some |
519 | // internal race condition. If contextmenu is preventDefaulted, then |
520 | // mouseup always fires. |
521 | // |
522 | // On Mac, it always fires mouseup and then fires a click. |
523 | result &= goog.testing.events.fireMouseUpEvent(target, |
524 | button, opt_coords, props); |
525 | |
526 | if (goog.userAgent.WEBKIT && goog.userAgent.MAC) { |
527 | result &= goog.testing.events.fireClickEvent( |
528 | target, button, opt_coords, props); |
529 | } |
530 | } |
531 | return !!result; |
532 | }; |
533 | |
534 | |
535 | /** |
536 | * Simulates a popstate event on the given target. |
537 | * @param {EventTarget} target The target for the event. |
538 | * @param {Object} state History state object. |
539 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
540 | * called on it, true otherwise. |
541 | */ |
542 | goog.testing.events.firePopStateEvent = function(target, state) { |
543 | var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target); |
544 | e.state = state; |
545 | return goog.testing.events.fireBrowserEvent(e); |
546 | }; |
547 | |
548 | |
549 | /** |
550 | * Simulate a blur event on the given target. |
551 | * @param {EventTarget} target The target for the event. |
552 | * @return {boolean} The value returned by firing the blur browser event, |
553 | * which returns false iff 'preventDefault' was invoked. |
554 | */ |
555 | goog.testing.events.fireBlurEvent = function(target) { |
556 | var e = new goog.testing.events.Event( |
557 | goog.events.EventType.BLUR, target); |
558 | return goog.testing.events.fireBrowserEvent(e); |
559 | }; |
560 | |
561 | |
562 | /** |
563 | * Simulate a focus event on the given target. |
564 | * @param {EventTarget} target The target for the event. |
565 | * @return {boolean} The value returned by firing the focus browser event, |
566 | * which returns false iff 'preventDefault' was invoked. |
567 | */ |
568 | goog.testing.events.fireFocusEvent = function(target) { |
569 | var e = new goog.testing.events.Event( |
570 | goog.events.EventType.FOCUS, target); |
571 | return goog.testing.events.fireBrowserEvent(e); |
572 | }; |
573 | |
574 | |
575 | /** |
576 | * Simulates an event's capturing and bubbling phases. |
577 | * @param {Event} event A simulated native event. It will be wrapped in a |
578 | * normalized BrowserEvent and dispatched to Closure listeners on all |
579 | * ancestors of its target (inclusive). |
580 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
581 | * called on it, true otherwise. |
582 | */ |
583 | goog.testing.events.fireBrowserEvent = function(event) { |
584 | event.returnValue_ = true; |
585 | |
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 | |
592 | // dispatch capturing listeners |
593 | for (var j = ancestors.length - 1; |
594 | j >= 0 && !event.propagationStopped_; |
595 | j--) { |
596 | goog.events.fireListeners(ancestors[j], event.type, true, |
597 | new goog.events.BrowserEvent(event, ancestors[j])); |
598 | } |
599 | |
600 | // dispatch bubbling listeners |
601 | for (var j = 0; |
602 | j < ancestors.length && !event.propagationStopped_; |
603 | j++) { |
604 | goog.events.fireListeners(ancestors[j], event.type, false, |
605 | new goog.events.BrowserEvent(event, ancestors[j])); |
606 | } |
607 | |
608 | return event.returnValue_; |
609 | }; |
610 | |
611 | |
612 | /** |
613 | * Simulates a touchstart event on the given target. |
614 | * @param {EventTarget} target The target for the event. |
615 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's |
616 | * target's position (if available), otherwise (0, 0). |
617 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
618 | * BrowserEvent. |
619 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
620 | * called on it, true otherwise. |
621 | */ |
622 | goog.testing.events.fireTouchStartEvent = function( |
623 | target, opt_coords, opt_eventProperties) { |
624 | // TODO: Support multi-touch events with array of coordinates. |
625 | var touchstart = |
626 | new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target); |
627 | goog.testing.events.setEventClientXY_(touchstart, opt_coords); |
628 | if (opt_eventProperties) { |
629 | goog.object.extend(touchstart, opt_eventProperties); |
630 | } |
631 | return goog.testing.events.fireBrowserEvent(touchstart); |
632 | }; |
633 | |
634 | |
635 | /** |
636 | * Simulates a touchmove event on the given target. |
637 | * @param {EventTarget} target The target for the event. |
638 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's |
639 | * target's position (if available), otherwise (0, 0). |
640 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
641 | * BrowserEvent. |
642 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
643 | * called on it, true otherwise. |
644 | */ |
645 | goog.testing.events.fireTouchMoveEvent = function( |
646 | target, opt_coords, opt_eventProperties) { |
647 | // TODO: Support multi-touch events with array of coordinates. |
648 | var touchmove = |
649 | new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target); |
650 | goog.testing.events.setEventClientXY_(touchmove, opt_coords); |
651 | if (opt_eventProperties) { |
652 | goog.object.extend(touchmove, opt_eventProperties); |
653 | } |
654 | return goog.testing.events.fireBrowserEvent(touchmove); |
655 | }; |
656 | |
657 | |
658 | /** |
659 | * Simulates a touchend event on the given target. |
660 | * @param {EventTarget} target The target for the event. |
661 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's |
662 | * target's position (if available), otherwise (0, 0). |
663 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
664 | * BrowserEvent. |
665 | * @return {boolean} The returnValue of the event: false if preventDefault() was |
666 | * called on it, true otherwise. |
667 | */ |
668 | goog.testing.events.fireTouchEndEvent = function( |
669 | target, opt_coords, opt_eventProperties) { |
670 | // TODO: Support multi-touch events with array of coordinates. |
671 | var touchend = |
672 | new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target); |
673 | goog.testing.events.setEventClientXY_(touchend, opt_coords); |
674 | if (opt_eventProperties) { |
675 | goog.object.extend(touchend, opt_eventProperties); |
676 | } |
677 | return goog.testing.events.fireBrowserEvent(touchend); |
678 | }; |
679 | |
680 | |
681 | /** |
682 | * Simulates a simple touch sequence on the given target. |
683 | * @param {EventTarget} target The target for the event. |
684 | * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event |
685 | * target's position (if available), otherwise (0, 0). |
686 | * @param {Object=} opt_eventProperties Event properties to be mixed into the |
687 | * BrowserEvent. |
688 | * @return {boolean} The returnValue of the sequence: false if preventDefault() |
689 | * was called on any of the events, true otherwise. |
690 | */ |
691 | goog.testing.events.fireTouchSequence = function( |
692 | target, opt_coords, opt_eventProperties) { |
693 | // TODO: Support multi-touch events with array of coordinates. |
694 | // Fire touchstart, touchmove, touchend then return the bitwise AND of the 3. |
695 | return !!(goog.testing.events.fireTouchStartEvent( |
696 | target, opt_coords, opt_eventProperties) & |
697 | goog.testing.events.fireTouchEndEvent( |
698 | target, opt_coords, opt_eventProperties)); |
699 | }; |
700 | |
701 | |
702 | /** |
703 | * Mixins a listenable into the given object. This turns the object |
704 | * into a goog.events.Listenable. This is useful, for example, when |
705 | * you need to mock a implementation of listenable and still want it |
706 | * to work with goog.events. |
707 | * @param {!Object} obj The object to mixin into. |
708 | */ |
709 | goog.testing.events.mixinListenable = function(obj) { |
710 | var listenable = new goog.events.EventTarget(); |
711 | |
712 | listenable.setTargetForTesting(obj); |
713 | |
714 | var listenablePrototype = goog.events.EventTarget.prototype; |
715 | var disposablePrototype = goog.Disposable.prototype; |
716 | for (var key in listenablePrototype) { |
717 | if (listenablePrototype.hasOwnProperty(key) || |
718 | disposablePrototype.hasOwnProperty(key)) { |
719 | var member = listenablePrototype[key]; |
720 | if (goog.isFunction(member)) { |
721 | obj[key] = goog.bind(member, listenable); |
722 | } else { |
723 | obj[key] = member; |
724 | } |
725 | } |
726 | } |
727 | }; |