0
# Event Spying
1
2
Comprehensive event spy system for testing jQuery event triggers, with support for event prevention, propagation control, and detailed event inspection. Essential for testing event-driven jQuery applications and components.
3
4
## Capabilities
5
6
### Global Event Spy Function
7
8
#### spyOnEvent
9
10
Creates an event spy for specified selector and event type.
11
12
```javascript { .api }
13
/**
14
* Creates an event spy for given selector and event type
15
* Returns spy object with call tracking and control methods
16
* @param selector - CSS selector or jQuery element to spy on
17
* @param eventName - Event type to spy on (e.g., 'click', 'submit', 'custom-event')
18
* @returns EventSpy object with tracking and control methods
19
*/
20
function spyOnEvent(selector, eventName);
21
```
22
23
**Usage Examples:**
24
```javascript
25
// Basic event spying
26
var clickSpy = spyOnEvent('#button', 'click');
27
$('#button').click();
28
expect(clickSpy).toHaveBeenTriggered();
29
30
// Spy on custom events
31
var customSpy = spyOnEvent('.widget', 'widget-updated');
32
$('.widget').trigger('widget-updated', {data: 'test'});
33
expect(customSpy).toHaveBeenTriggered();
34
35
// Spy on multiple events
36
var submitSpy = spyOnEvent('#form', 'submit');
37
var focusSpy = spyOnEvent('#input', 'focus');
38
```
39
40
### Event Spy Object
41
42
The spy object returned by `spyOnEvent()` provides tracking and control:
43
44
```javascript { .api }
45
/**
46
* Event spy object with tracking capabilities
47
*/
48
interface EventSpy {
49
/** CSS selector being spied on */
50
selector: string;
51
52
/** Event name being spied on */
53
eventName: string;
54
55
/** Internal event handler function */
56
handler: Function;
57
58
/** Reset spy state (clear call count and arguments) */
59
reset(): void;
60
61
/** Call tracking information */
62
calls: {
63
/** Number of times event was triggered */
64
count(): number;
65
66
/** Whether event was triggered at least once */
67
any(): boolean;
68
};
69
}
70
```
71
72
**Spy Object Usage:**
73
```javascript
74
var spy = spyOnEvent('#element', 'click');
75
76
// Trigger events
77
$('#element').click();
78
$('#element').click();
79
80
// Check call information
81
expect(spy.calls.count()).toBe(2);
82
expect(spy.calls.any()).toBe(true);
83
84
// Reset spy state
85
spy.reset();
86
expect(spy.calls.count()).toBe(0);
87
expect(spy.calls.any()).toBe(false);
88
```
89
90
### Event Spy Matchers
91
92
#### toHaveBeenTriggered
93
94
Checks if spied event was triggered (using spy object).
95
96
```javascript { .api }
97
/**
98
* Checks if event spy was triggered at least once
99
* Use with spy object returned by spyOnEvent()
100
*/
101
expect(eventSpy).toHaveBeenTriggered();
102
```
103
104
#### toHaveBeenTriggeredOn
105
106
Checks if event was triggered on specific selector (using event name).
107
108
```javascript { .api }
109
/**
110
* Checks if event was triggered on the specified selector
111
* Use with event name string and selector
112
* @param selector - CSS selector to check
113
*/
114
expect(eventName).toHaveBeenTriggeredOn(selector);
115
```
116
117
**Trigger Matcher Examples:**
118
```javascript
119
// Using spy object
120
var clickSpy = spyOnEvent('#button', 'click');
121
$('#button').click();
122
expect(clickSpy).toHaveBeenTriggered();
123
expect(clickSpy).not.toHaveBeenTriggered(); // After reset
124
125
// Using event name and selector
126
$('#link').click();
127
expect('click').toHaveBeenTriggeredOn('#link');
128
expect('hover').not.toHaveBeenTriggeredOn('#link');
129
```
130
131
#### toHaveBeenTriggeredOnAndWith
132
133
Checks if event was triggered with specific arguments.
134
135
```javascript { .api }
136
/**
137
* Checks if event was triggered on selector with specific arguments
138
* @param selector - CSS selector to check
139
* @param expectedArgs - Expected event arguments (array or single value)
140
*/
141
expect(eventName).toHaveBeenTriggeredOnAndWith(selector, expectedArgs);
142
```
143
144
**Usage Example:**
145
```javascript
146
spyOnEvent('#widget', 'data-updated');
147
148
// Trigger with custom data
149
$('#widget').trigger('data-updated', [{id: 123, name: 'test'}]);
150
151
// Check trigger with arguments
152
expect('data-updated').toHaveBeenTriggeredOnAndWith('#widget', [{id: 123, name: 'test'}]);
153
```
154
155
### Event Prevention and Propagation
156
157
#### toHaveBeenPrevented
158
159
Checks if spied event was prevented (using spy object).
160
161
```javascript { .api }
162
/**
163
* Checks if event's default action was prevented
164
* Use with spy object returned by spyOnEvent()
165
*/
166
expect(eventSpy).toHaveBeenPrevented();
167
```
168
169
#### toHaveBeenPreventedOn
170
171
Checks if event was prevented on specific selector (using event name).
172
173
```javascript { .api }
174
/**
175
* Checks if event's default action was prevented on selector
176
* @param selector - CSS selector to check
177
*/
178
expect(eventName).toHaveBeenPreventedOn(selector);
179
```
180
181
**Prevention Examples:**
182
```javascript
183
// Set up prevention
184
$('#form').on('submit', function(e) {
185
e.preventDefault();
186
});
187
188
var submitSpy = spyOnEvent('#form', 'submit');
189
$('#form').submit();
190
191
// Check prevention with spy object
192
expect(submitSpy).toHaveBeenPrevented();
193
194
// Check prevention with event name
195
expect('submit').toHaveBeenPreventedOn('#form');
196
```
197
198
#### toHaveBeenStopped
199
200
Checks if spied event propagation was stopped (using spy object).
201
202
```javascript { .api }
203
/**
204
* Checks if event propagation was stopped
205
* Use with spy object returned by spyOnEvent()
206
*/
207
expect(eventSpy).toHaveBeenStopped();
208
```
209
210
#### toHaveBeenStoppedOn
211
212
Checks if event propagation was stopped on specific selector (using event name).
213
214
```javascript { .api }
215
/**
216
* Checks if event propagation was stopped on selector
217
* @param selector - CSS selector to check
218
*/
219
expect(eventName).toHaveBeenStoppedOn(selector);
220
```
221
222
**Propagation Examples:**
223
```javascript
224
// Set up propagation stopping
225
$('.inner').on('click', function(e) {
226
e.stopPropagation();
227
});
228
229
var clickSpy = spyOnEvent('.inner', 'click');
230
$('.inner').click();
231
232
// Check propagation stopping
233
expect(clickSpy).toHaveBeenStopped();
234
expect('click').toHaveBeenStoppedOn('.inner');
235
```
236
237
### Advanced Event Spying Patterns
238
239
#### Multiple Event Types
240
```javascript
241
describe('Multi-event component', function() {
242
var clickSpy, hoverSpy, customSpy;
243
244
beforeEach(function() {
245
loadFixtures('<div id="widget">Widget</div>');
246
clickSpy = spyOnEvent('#widget', 'click');
247
hoverSpy = spyOnEvent('#widget', 'mouseenter');
248
customSpy = spyOnEvent('#widget', 'widget-activated');
249
});
250
251
it('should handle multiple event types', function() {
252
$('#widget').click().trigger('mouseenter').trigger('widget-activated');
253
254
expect(clickSpy).toHaveBeenTriggered();
255
expect(hoverSpy).toHaveBeenTriggered();
256
expect(customSpy).toHaveBeenTriggered();
257
});
258
});
259
```
260
261
#### Event Sequence Testing
262
```javascript
263
it('should trigger events in correct sequence', function() {
264
var focusSpy = spyOnEvent('#input', 'focus');
265
var changeSpy = spyOnEvent('#input', 'change');
266
var blurSpy = spyOnEvent('#input', 'blur');
267
268
// Simulate user interaction sequence
269
$('#input').focus().val('test value').change().blur();
270
271
expect(focusSpy.calls.count()).toBe(1);
272
expect(changeSpy.calls.count()).toBe(1);
273
expect(blurSpy.calls.count()).toBe(1);
274
});
275
```
276
277
#### Complex Event Data Testing
278
```javascript
279
it('should pass correct data with custom events', function() {
280
spyOnEvent('#component', 'data-loaded');
281
282
var testData = {
283
users: [{id: 1, name: 'Alice'}],
284
timestamp: Date.now()
285
};
286
287
$('#component').trigger('data-loaded', [testData]);
288
289
expect('data-loaded').toHaveBeenTriggeredOnAndWith('#component', [testData]);
290
});
291
```
292
293
#### Event Delegation Testing
294
```javascript
295
describe('Event delegation', function() {
296
beforeEach(function() {
297
setFixtures(`
298
<ul id="list">
299
<li class="item">Item 1</li>
300
<li class="item">Item 2</li>
301
</ul>
302
`);
303
304
// Set up delegated event handler
305
$('#list').on('click', '.item', function(e) {
306
$(this).addClass('clicked');
307
});
308
});
309
310
it('should handle delegated events', function() {
311
var clickSpy = spyOnEvent('.item', 'click');
312
313
$('.item').first().click();
314
315
expect(clickSpy).toHaveBeenTriggered();
316
expect($('.item').first()).toHaveClass('clicked');
317
});
318
});
319
```
320
321
### Event Spy Lifecycle
322
323
#### Automatic Cleanup
324
Event spies are automatically cleaned up between tests:
325
326
```javascript
327
// Automatic cleanup happens in afterEach
328
afterEach(function() {
329
jasmine.jQuery.events.cleanUp(); // Called automatically
330
});
331
```
332
333
#### Manual Spy Management
334
```javascript
335
describe('Manual spy management', function() {
336
var spy;
337
338
beforeEach(function() {
339
spy = spyOnEvent('#element', 'click');
340
});
341
342
it('should reset spy between tests', function() {
343
$('#element').click();
344
expect(spy.calls.count()).toBe(1);
345
346
// Reset manually if needed mid-test
347
spy.reset();
348
expect(spy.calls.count()).toBe(0);
349
});
350
});
351
```
352
353
### Error Handling
354
355
#### Missing Event Spy Errors
356
```javascript { .api }
357
/**
358
* Error thrown when accessing spy data for non-existent spy:
359
* "There is no spy for [eventName] on [selector]. Make sure to create a spy using spyOnEvent."
360
*/
361
```
362
363
**Error Example:**
364
```javascript
365
// This will throw error - no spy created
366
try {
367
expect('click').toHaveBeenTriggeredOn('#element');
368
} catch (error) {
369
expect(error.message).toContain('There is no spy for click on #element');
370
}
371
372
// Correct usage - create spy first
373
spyOnEvent('#element', 'click');
374
$('#element').click();
375
expect('click').toHaveBeenTriggeredOn('#element'); // Works
376
```
377
378
### Integration with jQuery Event System
379
380
#### Namespace Support
381
```javascript
382
// Spy on namespaced events
383
var namespacedSpy = spyOnEvent('#element', 'click.myapp');
384
$('#element').trigger('click.myapp');
385
expect(namespacedSpy).toHaveBeenTriggered();
386
```
387
388
#### Custom Event Support
389
```javascript
390
// Spy on custom events
391
var customSpy = spyOnEvent('#widget', 'widget:updated');
392
$('#widget').trigger('widget:updated', {version: '2.0'});
393
expect(customSpy).toHaveBeenTriggered();
394
```
395
396
#### Event Object Access
397
While jasmine-jquery doesn't directly expose event objects, you can combine with standard Jasmine spies:
398
399
```javascript
400
var handler = jasmine.createSpy('clickHandler');
401
$('#element').on('click', handler);
402
403
var eventSpy = spyOnEvent('#element', 'click');
404
$('#element').click();
405
406
expect(eventSpy).toHaveBeenTriggered();
407
expect(handler).toHaveBeenCalled();
408
// Can inspect handler.calls.argsFor(0)[0] for event object
409
```
410
411
### Advanced Event System Functions
412
413
#### jasmine.jQuery.events.args
414
415
Retrieves arguments from spied event for detailed inspection.
416
417
```javascript { .api }
418
/**
419
* Gets arguments passed to spied event
420
* @param selector - CSS selector that was spied on
421
* @param eventName - Event name that was spied on
422
* @returns Array of arguments passed to event handler
423
* @throws Error if no spy exists for the selector/event combination
424
*/
425
function jasmine.jQuery.events.args(selector, eventName);
426
```
427
428
**Usage Example:**
429
```javascript
430
spyOnEvent('#form', 'submit');
431
$('#form').trigger('submit', [{data: 'test'}, 'extra-arg']);
432
433
var eventArgs = jasmine.jQuery.events.args('#form', 'submit');
434
expect(eventArgs[1]).toEqual({data: 'test'}); // First custom argument
435
expect(eventArgs[2]).toBe('extra-arg'); // Second custom argument
436
```
437
438
#### jasmine.jQuery.events.wasTriggered
439
440
Checks if event was triggered (low-level boolean check).
441
442
```javascript { .api }
443
/**
444
* Checks if event was triggered on selector (returns boolean)
445
* Low-level alternative to toHaveBeenTriggeredOn matcher
446
* @param selector - CSS selector to check
447
* @param eventName - Event name to check
448
* @returns Boolean indicating if event was triggered
449
*/
450
function jasmine.jQuery.events.wasTriggered(selector, eventName);
451
```
452
453
#### jasmine.jQuery.events.wasTriggeredWith
454
455
Checks if event was triggered with specific arguments.
456
457
```javascript { .api }
458
/**
459
* Checks if event was triggered with specific arguments
460
* @param selector - CSS selector to check
461
* @param eventName - Event name to check
462
* @param expectedArgs - Expected arguments
463
* @param util - Jasmine utility object
464
* @param customEqualityTesters - Custom equality testers array
465
* @returns Boolean indicating if event was triggered with expected arguments
466
*/
467
function jasmine.jQuery.events.wasTriggeredWith(selector, eventName, expectedArgs, util, customEqualityTesters);
468
```
469
470
#### jasmine.jQuery.events.wasPrevented
471
472
Checks if event default action was prevented (low-level boolean check).
473
474
```javascript { .api }
475
/**
476
* Checks if event's default action was prevented (returns boolean)
477
* Low-level alternative to toHaveBeenPreventedOn matcher
478
* @param selector - CSS selector to check
479
* @param eventName - Event name to check
480
* @returns Boolean indicating if event was prevented
481
*/
482
function jasmine.jQuery.events.wasPrevented(selector, eventName);
483
```
484
485
#### jasmine.jQuery.events.wasStopped
486
487
Checks if event propagation was stopped (low-level boolean check).
488
489
```javascript { .api }
490
/**
491
* Checks if event propagation was stopped (returns boolean)
492
* Low-level alternative to toHaveBeenStoppedOn matcher
493
* @param selector - CSS selector to check
494
* @param eventName - Event name to check
495
* @returns Boolean indicating if event propagation was stopped
496
*/
497
function jasmine.jQuery.events.wasStopped(selector, eventName);
498
```
499
500
#### jasmine.jQuery.events.spyOn
501
502
Creates event spy (internal method, use spyOnEvent() instead).
503
504
```javascript { .api }
505
/**
506
* Creates event spy (internal method - use global spyOnEvent() instead)
507
* @param selector - CSS selector to spy on
508
* @param eventName - Event name to spy on
509
* @returns EventSpy object
510
* @internal
511
*/
512
function jasmine.jQuery.events.spyOn(selector, eventName);
513
```
514
515
#### jasmine.jQuery.events.cleanUp
516
517
Manually cleans up all event spies (automatically called after each test).
518
519
```javascript { .api }
520
/**
521
* Manually clean up all event spies and tracked data
522
* Normally called automatically in afterEach hook
523
*/
524
function jasmine.jQuery.events.cleanUp();
525
```
526
527
#### jasmine.spiedEventsKey
528
529
Generates unique key for tracking spied events (internal utility).
530
531
```javascript { .api }
532
/**
533
* Creates unique key for spied event storage
534
* @param selector - CSS selector being spied on
535
* @param eventName - Event name being spied on
536
* @returns String key for internal event tracking
537
*/
538
function jasmine.spiedEventsKey(selector, eventName);
539
```
540
541
### Error Handling and Messages
542
543
#### Specific Error Messages
544
545
```javascript { .api }
546
/**
547
* Error thrown when accessing spy data for non-existent spy:
548
* "There is no spy for [eventName] on [selector]. Make sure to create a spy using spyOnEvent."
549
*
550
* This error occurs when calling jasmine.jQuery.events.args() or using event
551
* matchers without first creating a spy with spyOnEvent()
552
*/
553
```
554
555
### Best Practices
556
557
#### Descriptive Spy Variables
558
```javascript
559
// Good - descriptive names
560
var submitSpy = spyOnEvent('#login-form', 'submit');
561
var validationSpy = spyOnEvent('.form-field', 'validation:failed');
562
563
// Less clear
564
var spy1 = spyOnEvent('#form', 'submit');
565
var spy2 = spyOnEvent('.field', 'validation:failed');
566
```
567
568
#### Combine with Fixture Loading
569
```javascript
570
describe('Interactive component', function() {
571
beforeEach(function() {
572
loadFixtures('interactive-widget.html');
573
loadStyleFixtures('widget-styles.css');
574
});
575
576
it('should handle user interactions', function() {
577
var clickSpy = spyOnEvent('.widget-button', 'click');
578
var hoverSpy = spyOnEvent('.widget', 'mouseenter');
579
580
$('.widget').trigger('mouseenter');
581
$('.widget-button').click();
582
583
expect(hoverSpy).toHaveBeenTriggered();
584
expect(clickSpy).toHaveBeenTriggered();
585
});
586
});
587
```