Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81155 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @emails react-core
10
*/
11
12
"use strict";
13
14
require('mock-modules')
15
.dontMock('EventPluginHub')
16
.dontMock('ReactMount')
17
.dontMock('ReactBrowserEventEmitter')
18
.dontMock('ReactInstanceHandles')
19
.dontMock('EventPluginHub')
20
.dontMock('TapEventPlugin')
21
.dontMock('TouchEventUtils')
22
.dontMock('keyOf');
23
24
25
var keyOf = require('keyOf');
26
var mocks = require('mocks');
27
28
var ReactMount = require('ReactMount');
29
var idToNode = {};
30
var getID = ReactMount.getID;
31
var setID = function(el, id) {
32
ReactMount.setID(el, id);
33
idToNode[id] = el;
34
};
35
var oldGetNode = ReactMount.getNode;
36
37
var EventPluginHub;
38
var ReactBrowserEventEmitter;
39
var ReactTestUtils;
40
var TapEventPlugin;
41
var EventListener;
42
43
var tapMoveThreshold;
44
var idCallOrder = [];
45
var recordID = function(id) {
46
idCallOrder.push(id);
47
};
48
var recordIDAndStopPropagation = function(id, event) {
49
recordID(id);
50
event.stopPropagation();
51
};
52
var recordIDAndReturnFalse = function(id, event) {
53
recordID(id);
54
return false;
55
};
56
var LISTENER = mocks.getMockFunction();
57
var ON_CLICK_KEY = keyOf({onClick: null});
58
var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
59
var ON_CHANGE_KEY = keyOf({onChange: null});
60
61
62
/**
63
* Since `ReactBrowserEventEmitter` is fairly well separated from the DOM, we
64
* can test almost all of `ReactBrowserEventEmitter` without ever rendering
65
* anything in the DOM. As long as we provide IDs that follow `React's`
66
* conventional id namespace hierarchy. The only reason why we create these DOM
67
* nodes is so that when we feed them into `ReactBrowserEventEmitter` (through
68
* `ReactTestUtils`), the event handlers may receive a DOM node to inspect.
69
*/
70
var CHILD = document.createElement('div');
71
var PARENT = document.createElement('div');
72
var GRANDPARENT = document.createElement('div');
73
setID(CHILD, '.0.0.0.0');
74
setID(PARENT, '.0.0.0');
75
setID(GRANDPARENT, '.0.0');
76
77
function registerSimpleTestHandler() {
78
ReactBrowserEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
79
var listener =
80
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
81
expect(listener).toEqual(LISTENER);
82
return ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
83
}
84
85
86
describe('ReactBrowserEventEmitter', function() {
87
beforeEach(function() {
88
require('mock-modules').dumpCache();
89
LISTENER.mockClear();
90
EventPluginHub = require('EventPluginHub');
91
TapEventPlugin = require('TapEventPlugin');
92
ReactMount = require('ReactMount');
93
EventListener = require('EventListener');
94
ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
95
ReactTestUtils = require('ReactTestUtils');
96
ReactMount.getNode = function(id) {
97
return idToNode[id];
98
};
99
idCallOrder = [];
100
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
101
EventPluginHub.injection.injectEventPluginsByName({
102
TapEventPlugin: TapEventPlugin
103
});
104
});
105
106
afterEach(function() {
107
ReactMount.getNode = oldGetNode;
108
});
109
110
it('should store a listener correctly', function() {
111
registerSimpleTestHandler();
112
var listener =
113
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
114
expect(listener).toBe(LISTENER);
115
});
116
117
it('should retrieve a listener correctly', function() {
118
registerSimpleTestHandler();
119
var listener =
120
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
121
expect(listener).toEqual(LISTENER);
122
});
123
124
it('should clear all handlers when asked to', function() {
125
registerSimpleTestHandler();
126
ReactBrowserEventEmitter.deleteAllListeners(getID(CHILD));
127
var listener =
128
ReactBrowserEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
129
expect(listener).toBe(undefined);
130
});
131
132
it('should invoke a simple handler registered on a node', function() {
133
registerSimpleTestHandler();
134
ReactTestUtils.Simulate.click(CHILD);
135
expect(LISTENER.mock.calls.length).toBe(1);
136
});
137
138
it(
139
'should not invoke handlers if ReactBrowserEventEmitter is disabled',
140
function() {
141
registerSimpleTestHandler();
142
ReactBrowserEventEmitter.setEnabled(false);
143
ReactTestUtils.SimulateNative.click(CHILD);
144
expect(LISTENER.mock.calls.length).toBe(0);
145
ReactBrowserEventEmitter.setEnabled(true);
146
ReactTestUtils.SimulateNative.click(CHILD);
147
expect(LISTENER.mock.calls.length).toBe(1);
148
}
149
);
150
151
it('should bubble simply', function() {
152
ReactBrowserEventEmitter.putListener(
153
getID(CHILD),
154
ON_CLICK_KEY,
155
recordID.bind(null, getID(CHILD))
156
);
157
ReactBrowserEventEmitter.putListener(
158
getID(PARENT),
159
ON_CLICK_KEY,
160
recordID.bind(null, getID(PARENT))
161
);
162
ReactBrowserEventEmitter.putListener(
163
getID(GRANDPARENT),
164
ON_CLICK_KEY,
165
recordID.bind(null, getID(GRANDPARENT))
166
);
167
ReactTestUtils.Simulate.click(CHILD);
168
expect(idCallOrder.length).toBe(3);
169
expect(idCallOrder[0]).toBe(getID(CHILD));
170
expect(idCallOrder[1]).toBe(getID(PARENT));
171
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
172
});
173
174
it('should set currentTarget', function() {
175
ReactBrowserEventEmitter.putListener(
176
getID(CHILD),
177
ON_CLICK_KEY,
178
function(event) {
179
recordID(getID(CHILD));
180
expect(event.currentTarget).toBe(CHILD);
181
}
182
);
183
ReactBrowserEventEmitter.putListener(
184
getID(PARENT),
185
ON_CLICK_KEY,
186
function(event) {
187
recordID(getID(PARENT));
188
expect(event.currentTarget).toBe(PARENT);
189
}
190
);
191
ReactBrowserEventEmitter.putListener(
192
getID(GRANDPARENT),
193
ON_CLICK_KEY,
194
function(event) {
195
recordID(getID(GRANDPARENT));
196
expect(event.currentTarget).toBe(GRANDPARENT);
197
}
198
);
199
ReactTestUtils.Simulate.click(CHILD);
200
expect(idCallOrder.length).toBe(3);
201
expect(idCallOrder[0]).toBe(getID(CHILD));
202
expect(idCallOrder[1]).toBe(getID(PARENT));
203
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
204
});
205
206
it('should support stopPropagation()', function() {
207
ReactBrowserEventEmitter.putListener(
208
getID(CHILD),
209
ON_CLICK_KEY,
210
recordID.bind(null, getID(CHILD))
211
);
212
ReactBrowserEventEmitter.putListener(
213
getID(PARENT),
214
ON_CLICK_KEY,
215
recordIDAndStopPropagation.bind(null, getID(PARENT))
216
);
217
ReactBrowserEventEmitter.putListener(
218
getID(GRANDPARENT),
219
ON_CLICK_KEY,
220
recordID.bind(null, getID(GRANDPARENT))
221
);
222
ReactTestUtils.Simulate.click(CHILD);
223
expect(idCallOrder.length).toBe(2);
224
expect(idCallOrder[0]).toBe(getID(CHILD));
225
expect(idCallOrder[1]).toBe(getID(PARENT));
226
});
227
228
it('should stop after first dispatch if stopPropagation', function() {
229
ReactBrowserEventEmitter.putListener(
230
getID(CHILD),
231
ON_CLICK_KEY,
232
recordIDAndStopPropagation.bind(null, getID(CHILD))
233
);
234
ReactBrowserEventEmitter.putListener(
235
getID(PARENT),
236
ON_CLICK_KEY,
237
recordID.bind(null, getID(PARENT))
238
);
239
ReactBrowserEventEmitter.putListener(
240
getID(GRANDPARENT),
241
ON_CLICK_KEY,
242
recordID.bind(null, getID(GRANDPARENT))
243
);
244
ReactTestUtils.Simulate.click(CHILD);
245
expect(idCallOrder.length).toBe(1);
246
expect(idCallOrder[0]).toBe(getID(CHILD));
247
});
248
249
it('should stopPropagation if false is returned, but warn', function() {
250
ReactBrowserEventEmitter.putListener(
251
getID(CHILD),
252
ON_CLICK_KEY,
253
recordIDAndReturnFalse.bind(null, getID(CHILD))
254
);
255
ReactBrowserEventEmitter.putListener(
256
getID(PARENT),
257
ON_CLICK_KEY,
258
recordID.bind(null, getID(PARENT))
259
);
260
ReactBrowserEventEmitter.putListener(
261
getID(GRANDPARENT),
262
ON_CLICK_KEY,
263
recordID.bind(null, getID(GRANDPARENT))
264
);
265
spyOn(console, 'warn');
266
ReactTestUtils.Simulate.click(CHILD);
267
expect(idCallOrder.length).toBe(1);
268
expect(idCallOrder[0]).toBe(getID(CHILD));
269
expect(console.warn.calls.length).toEqual(1);
270
expect(console.warn.calls[0].args[0]).toBe(
271
'Warning: Returning `false` from an event handler is deprecated and ' +
272
'will be ignored in a future release. Instead, manually call ' +
273
'e.stopPropagation() or e.preventDefault(), as appropriate.'
274
);
275
});
276
277
/**
278
* The entire event registration state of the world should be "locked-in" at
279
* the time the event occurs. This is to resolve many edge cases that come
280
* about from a listener on a lower-in-DOM node causing structural changes at
281
* places higher in the DOM. If this lower-in-DOM node causes new content to
282
* be rendered at a place higher-in-DOM, we need to be careful not to invoke
283
* these new listeners.
284
*/
285
286
it('should invoke handlers that were removed while bubbling', function() {
287
var handleParentClick = mocks.getMockFunction();
288
var handleChildClick = function(event) {
289
ReactBrowserEventEmitter.deleteAllListeners(getID(PARENT));
290
};
291
ReactBrowserEventEmitter.putListener(
292
getID(CHILD),
293
ON_CLICK_KEY,
294
handleChildClick
295
);
296
ReactBrowserEventEmitter.putListener(
297
getID(PARENT),
298
ON_CLICK_KEY,
299
handleParentClick
300
);
301
ReactTestUtils.Simulate.click(CHILD);
302
expect(handleParentClick.mock.calls.length).toBe(1);
303
});
304
305
it('should not invoke newly inserted handlers while bubbling', function() {
306
var handleParentClick = mocks.getMockFunction();
307
var handleChildClick = function(event) {
308
ReactBrowserEventEmitter.putListener(
309
getID(PARENT),
310
ON_CLICK_KEY,
311
handleParentClick
312
);
313
};
314
ReactBrowserEventEmitter.putListener(
315
getID(CHILD),
316
ON_CLICK_KEY,
317
handleChildClick
318
);
319
ReactTestUtils.Simulate.click(CHILD);
320
expect(handleParentClick.mock.calls.length).toBe(0);
321
});
322
323
it('should infer onTouchTap from a touchStart/End', function() {
324
ReactBrowserEventEmitter.putListener(
325
getID(CHILD),
326
ON_TOUCH_TAP_KEY,
327
recordID.bind(null, getID(CHILD))
328
);
329
ReactTestUtils.SimulateNative.touchStart(
330
CHILD,
331
ReactTestUtils.nativeTouchData(0, 0)
332
);
333
ReactTestUtils.SimulateNative.touchEnd(
334
CHILD,
335
ReactTestUtils.nativeTouchData(0, 0)
336
);
337
expect(idCallOrder.length).toBe(1);
338
expect(idCallOrder[0]).toBe(getID(CHILD));
339
});
340
341
it('should infer onTouchTap from when dragging below threshold', function() {
342
ReactBrowserEventEmitter.putListener(
343
getID(CHILD),
344
ON_TOUCH_TAP_KEY,
345
recordID.bind(null, getID(CHILD))
346
);
347
ReactTestUtils.SimulateNative.touchStart(
348
CHILD,
349
ReactTestUtils.nativeTouchData(0, 0)
350
);
351
ReactTestUtils.SimulateNative.touchEnd(
352
CHILD,
353
ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1)
354
);
355
expect(idCallOrder.length).toBe(1);
356
expect(idCallOrder[0]).toBe(getID(CHILD));
357
});
358
359
it('should not onTouchTap from when dragging beyond threshold', function() {
360
ReactBrowserEventEmitter.putListener(
361
getID(CHILD),
362
ON_TOUCH_TAP_KEY,
363
recordID.bind(null, getID(CHILD))
364
);
365
ReactTestUtils.SimulateNative.touchStart(
366
CHILD,
367
ReactTestUtils.nativeTouchData(0, 0)
368
);
369
ReactTestUtils.SimulateNative.touchEnd(
370
CHILD,
371
ReactTestUtils.nativeTouchData(0, tapMoveThreshold + 1)
372
);
373
expect(idCallOrder.length).toBe(0);
374
});
375
376
it('should listen to events only once', function() {
377
spyOn(EventListener, 'listen');
378
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
379
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
380
expect(EventListener.listen.callCount).toBe(1);
381
});
382
383
it('should work with event plugins without dependencies', function() {
384
spyOn(EventListener, 'listen');
385
386
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
387
388
expect(EventListener.listen.argsForCall[0][1]).toBe('click');
389
});
390
391
it('should work with event plugins with dependencies', function() {
392
spyOn(EventListener, 'listen');
393
spyOn(EventListener, 'capture');
394
395
ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);
396
397
var setEventListeners = [];
398
var listenCalls = EventListener.listen.argsForCall;
399
var captureCalls = EventListener.capture.argsForCall;
400
for (var i = 0, l = listenCalls.length; i < l; i++) {
401
setEventListeners.push(listenCalls[i][1]);
402
}
403
for (i = 0, l = captureCalls.length; i < l; i++) {
404
setEventListeners.push(captureCalls[i][1]);
405
}
406
407
var module =
408
ReactBrowserEventEmitter.registrationNameModules[ON_CHANGE_KEY];
409
var dependencies = module.eventTypes.change.dependencies;
410
expect(setEventListeners.length).toEqual(dependencies.length);
411
412
for (i = 0, l = setEventListeners.length; i < l; i++) {
413
expect(dependencies.indexOf(setEventListeners[i])).toBeTruthy();
414
}
415
});
416
417
it('should bubble onTouchTap', function() {
418
ReactBrowserEventEmitter.putListener(
419
getID(CHILD),
420
ON_TOUCH_TAP_KEY,
421
recordID.bind(null, getID(CHILD))
422
);
423
ReactBrowserEventEmitter.putListener(
424
getID(PARENT),
425
ON_TOUCH_TAP_KEY,
426
recordID.bind(null, getID(PARENT))
427
);
428
ReactBrowserEventEmitter.putListener(
429
getID(GRANDPARENT),
430
ON_TOUCH_TAP_KEY,
431
recordID.bind(null, getID(GRANDPARENT))
432
);
433
ReactTestUtils.SimulateNative.touchStart(
434
CHILD,
435
ReactTestUtils.nativeTouchData(0, 0)
436
);
437
ReactTestUtils.SimulateNative.touchEnd(
438
CHILD,
439
ReactTestUtils.nativeTouchData(0, 0)
440
);
441
expect(idCallOrder.length).toBe(3);
442
expect(idCallOrder[0]).toBe(getID(CHILD));
443
expect(idCallOrder[1]).toBe(getID(PARENT));
444
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
445
});
446
447
});
448
449