Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81159 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
var EventPluginHub;
15
var EventConstants;
16
var EventPropagators;
17
var ReactInstanceHandles;
18
var ResponderEventPlugin;
19
var SyntheticEvent;
20
21
var GRANDPARENT_ID = '.0';
22
var PARENT_ID = '.0.0';
23
var CHILD_ID = '.0.0.0';
24
25
var topLevelTypes;
26
var responderEventTypes;
27
var spies;
28
29
var DUMMY_NATIVE_EVENT = {};
30
var DUMMY_RENDERED_TARGET = {};
31
32
var onStartShouldSetResponder = function(id, cb, capture) {
33
var registrationNames = responderEventTypes
34
.startShouldSetResponder
35
.phasedRegistrationNames;
36
EventPluginHub.putListener(
37
id,
38
capture ? registrationNames.captured : registrationNames.bubbled,
39
cb
40
);
41
};
42
43
var onScrollShouldSetResponder = function(id, cb, capture) {
44
var registrationNames = responderEventTypes
45
.scrollShouldSetResponder
46
.phasedRegistrationNames;
47
EventPluginHub.putListener(
48
id,
49
capture ? registrationNames.captured : registrationNames.bubbled,
50
cb
51
);
52
};
53
54
var onMoveShouldSetResponder = function(id, cb, capture) {
55
var registrationNames = responderEventTypes
56
.moveShouldSetResponder
57
.phasedRegistrationNames;
58
EventPluginHub.putListener(
59
id,
60
capture ? registrationNames.captured : registrationNames.bubbled,
61
cb
62
);
63
};
64
65
66
var onResponderGrant = function(id, cb) {
67
EventPluginHub.putListener(
68
id,
69
responderEventTypes.responderGrant.registrationName,
70
cb
71
);
72
};
73
74
var extractForTouchStart = function(renderedTargetID) {
75
return ResponderEventPlugin.extractEvents(
76
topLevelTypes.topTouchStart,
77
DUMMY_NATIVE_EVENT,
78
renderedTargetID,
79
DUMMY_RENDERED_TARGET
80
);
81
};
82
83
var extractForTouchMove = function(renderedTargetID) {
84
return ResponderEventPlugin.extractEvents(
85
topLevelTypes.topTouchMove,
86
DUMMY_NATIVE_EVENT,
87
renderedTargetID,
88
DUMMY_RENDERED_TARGET
89
);
90
};
91
92
var extractForTouchEnd = function(renderedTargetID) {
93
return ResponderEventPlugin.extractEvents(
94
topLevelTypes.topTouchEnd,
95
DUMMY_NATIVE_EVENT,
96
renderedTargetID,
97
DUMMY_RENDERED_TARGET
98
);
99
};
100
101
var extractForMouseDown = function(renderedTargetID) {
102
return ResponderEventPlugin.extractEvents(
103
topLevelTypes.topMouseDown,
104
DUMMY_NATIVE_EVENT,
105
renderedTargetID,
106
DUMMY_RENDERED_TARGET
107
);
108
};
109
110
var extractForMouseMove = function(renderedTargetID) {
111
return ResponderEventPlugin.extractEvents(
112
topLevelTypes.topMouseMove,
113
DUMMY_NATIVE_EVENT,
114
renderedTargetID,
115
DUMMY_RENDERED_TARGET
116
);
117
};
118
119
120
var extractForMouseUp = function(renderedTargetID) {
121
return ResponderEventPlugin.extractEvents(
122
topLevelTypes.topMouseUp,
123
DUMMY_NATIVE_EVENT,
124
renderedTargetID,
125
DUMMY_RENDERED_TARGET
126
);
127
};
128
129
var extractForScroll = function(renderedTargetID) {
130
return ResponderEventPlugin.extractEvents(
131
topLevelTypes.topScroll,
132
DUMMY_NATIVE_EVENT,
133
renderedTargetID,
134
DUMMY_RENDERED_TARGET
135
);
136
};
137
138
139
var onGrantChild;
140
var onGrantParent;
141
var onGrantGrandParent;
142
143
144
var existsInExtraction = function(extracted, test) {
145
if (Array.isArray(extracted)) {
146
for (var i = 0; i < extracted.length; i++) {
147
if (test(extracted[i])) {
148
return true;
149
}
150
}
151
} else if (extracted) {
152
return test(extracted);
153
}
154
return false;
155
};
156
157
/**
158
* Helper validators.
159
*/
160
function assertGrantEvent(id, extracted) {
161
var test = function(event) {
162
return event instanceof SyntheticEvent &&
163
event.dispatchConfig === responderEventTypes.responderGrant &&
164
event.dispatchMarker === id;
165
};
166
expect(ResponderEventPlugin.getResponderID()).toBe(id);
167
expect(existsInExtraction(extracted, test)).toBe(true);
168
}
169
170
function assertResponderMoveEvent(id, extracted) {
171
var test = function(event) {
172
return event instanceof SyntheticEvent &&
173
event.dispatchConfig === responderEventTypes.responderMove &&
174
event.dispatchMarker === id;
175
};
176
expect(ResponderEventPlugin.getResponderID()).toBe(id);
177
expect(existsInExtraction(extracted, test)).toBe(true);
178
}
179
180
function assertTerminateEvent(id, extracted) {
181
var test = function(event) {
182
return event instanceof SyntheticEvent &&
183
event.dispatchConfig === responderEventTypes.responderTerminate &&
184
event.dispatchMarker === id;
185
};
186
expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
187
expect(existsInExtraction(extracted, test)).toBe(true);
188
}
189
190
function assertRelease(id, extracted) {
191
var test = function(event) {
192
return event instanceof SyntheticEvent &&
193
event.dispatchConfig === responderEventTypes.responderRelease &&
194
event.dispatchMarker === id;
195
};
196
expect(ResponderEventPlugin.getResponderID()).toBe(null);
197
expect(existsInExtraction(extracted, test)).toBe(true);
198
}
199
200
201
function assertNothingExtracted(extracted) {
202
expect(Array.isArray(extracted)).toBe(false); // No grant events.
203
expect(Array.isArray(extracted)).toBeFalsy();
204
}
205
206
207
/**
208
* TODO:
209
* - Test that returning false from `responderTerminationRequest` will never
210
* cause the responder to be lost.
211
* - Automate some of this testing by providing config data - generalize.
212
*/
213
214
describe('ResponderEventPlugin', function() {
215
beforeEach(function() {
216
require('mock-modules').dumpCache();
217
218
EventPluginHub = require('EventPluginHub');
219
EventConstants = require('EventConstants');
220
EventPropagators = require('EventPropagators');
221
ReactInstanceHandles = require('ReactInstanceHandles');
222
ResponderEventPlugin = require('ResponderEventPlugin');
223
SyntheticEvent = require('SyntheticEvent');
224
EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
225
226
// dumpCache, in open-source tests, only resets existing mocks. It does not
227
// reset module-state though -- so we need to do this explicitly in the test
228
// for now. Once that's no longer the case, we can delete this line.
229
EventPluginHub.__purge();
230
231
topLevelTypes = EventConstants.topLevelTypes;
232
responderEventTypes = ResponderEventPlugin.eventTypes;
233
234
spies = {
235
onStartShouldSetResponderChild: function() {},
236
onStartShouldSetResponderParent: function() {},
237
onStartShouldSetResponderParentCapture: function() {},
238
onStartShouldSetResponderGrandParent: function() {},
239
onMoveShouldSetResponderParent: function() {},
240
onScrollShouldSetResponderParent: function() {}
241
};
242
243
onGrantChild = function() {};
244
onGrantParent = function() {};
245
onGrantGrandParent = function() {};
246
});
247
248
it('should not auto-set responder on touch start', function() {
249
// Notice we're not registering the startShould* handler.
250
var extracted = extractForTouchStart(CHILD_ID);
251
assertNothingExtracted(extracted);
252
expect(ResponderEventPlugin.getResponderID()).toBe(null);
253
});
254
255
it('should not auto-set responder on mouse down', function() {
256
// Notice we're not registering the startShould* handler.
257
var extracted = extractForMouseDown(CHILD_ID);
258
assertNothingExtracted(extracted);
259
expect(ResponderEventPlugin.getResponderID()).toBe(null);
260
extractForMouseUp(CHILD_ID); // Let up!
261
expect(ResponderEventPlugin.getResponderID()).toBe(null);
262
263
// Register `onMoveShould*` handler.
264
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
265
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
266
onResponderGrant(PARENT_ID, onGrantParent);
267
// Move mouse while not pressing down
268
extracted = extractForMouseMove(CHILD_ID);
269
assertNothingExtracted(extracted);
270
// Not going to call `onMoveShould`* if not touching.
271
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0);
272
expect(ResponderEventPlugin.getResponderID()).toBe(null);
273
274
// Now try the move extraction again, this time while holding down, and not
275
// letting up.
276
extracted = extractForMouseDown(CHILD_ID);
277
assertNothingExtracted(extracted);
278
expect(ResponderEventPlugin.getResponderID()).toBe(null);
279
280
// Now moving can set the responder, if pressing down, even if there is no
281
// current responder.
282
extracted = extractForMouseMove(CHILD_ID);
283
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
284
expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
285
assertGrantEvent(PARENT_ID, extracted);
286
287
extractForMouseUp(CHILD_ID);
288
expect(ResponderEventPlugin.getResponderID()).toBe(null);
289
});
290
291
it('should not extract a grant/release event if double start', function() {
292
// Return true - we should become the responder.
293
var extracted;
294
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
295
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
296
onResponderGrant(CHILD_ID, onGrantChild);
297
298
extracted = extractForTouchStart(CHILD_ID);
299
assertGrantEvent(CHILD_ID, extracted);
300
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
301
302
// Now we do *not* clear out the touch via a simulated touch end. This mocks
303
// out an environment that likely will never happen, but could in some odd
304
// error state so it's nice to make sure we recover gracefully.
305
// extractForTouchEnd(CHILD_ID); // Clear the responder
306
extracted = extractForTouchStart(CHILD_ID);
307
assertNothingExtracted();
308
expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
309
});
310
311
it('should bubble/capture responder on start', function() {
312
// Return true - we should become the responder.
313
var extracted;
314
spyOn(spies, 'onStartShouldSetResponderParent').andReturn(true);
315
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
316
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
317
onStartShouldSetResponder(PARENT_ID, spies.onStartShouldSetResponderParent);
318
onResponderGrant(CHILD_ID, onGrantChild);
319
onResponderGrant(PARENT_ID, onGrantParent);
320
321
// Nothing extracted if no responder.
322
extracted = extractForTouchMove(GRANDPARENT_ID);
323
assertNothingExtracted(extracted);
324
325
extracted = extractForTouchStart(CHILD_ID);
326
assertGrantEvent(CHILD_ID, extracted);
327
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
328
expect(spies.onStartShouldSetResponderParent.callCount).toBe(0);
329
330
// Even if moving on the grandparent, the child will receive responder moves
331
// (This is even true for mouse interactions - which we should absolutely
332
// test)
333
extracted = extractForTouchMove(GRANDPARENT_ID);
334
assertResponderMoveEvent(CHILD_ID, extracted);
335
extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
336
assertResponderMoveEvent(CHILD_ID, extracted);
337
338
// Reset the responder - id passed here shouldn't matter:
339
// TODO: Test varying the id here.
340
extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
341
assertRelease(CHILD_ID, extracted);
342
343
// Now make sure the parent requests responder on capture.
344
spyOn(spies, 'onStartShouldSetResponderParentCapture').andReturn(true);
345
onStartShouldSetResponder(
346
PARENT_ID,
347
spies.onStartShouldSetResponderParent,
348
true // Capture
349
);
350
onResponderGrant(PARENT_ID, onGrantGrandParent);
351
extracted = extractForTouchStart(PARENT_ID);
352
expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
353
assertGrantEvent(PARENT_ID, extracted);
354
// Now move on various nodes, ensuring that the responder move is emitted to
355
// the parent node.
356
extracted = extractForTouchMove(GRANDPARENT_ID);
357
assertResponderMoveEvent(PARENT_ID, extracted);
358
extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
359
assertResponderMoveEvent(PARENT_ID, extracted);
360
361
// Reset the responder - id passed here shouldn't matter:
362
// TODO: Test varying the id here.
363
extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
364
assertRelease(PARENT_ID, extracted);
365
366
});
367
368
it('should invoke callback to ask if responder is desired', function() {
369
// Return true - we should become the responder.
370
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
371
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
372
373
var extracted = extractForTouchStart(CHILD_ID);
374
assertNothingExtracted(extracted);
375
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
376
expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
377
extractForTouchEnd(CHILD_ID); // Clear the responder
378
379
// Now try returning false - we should not become the responder.
380
spies.onStartShouldSetResponderChild.andReturn(false);
381
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
382
extracted = extractForTouchStart(CHILD_ID);
383
assertNothingExtracted(extracted);
384
expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
385
expect(ResponderEventPlugin.getResponderID()).toBe(null);
386
extractForTouchEnd(CHILD_ID);
387
expect(ResponderEventPlugin.getResponderID()).toBe(null); // Still null
388
389
// Same thing as before but return true from "shouldSet".
390
spies.onStartShouldSetResponderChild.andReturn(true);
391
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
392
onResponderGrant(CHILD_ID, onGrantChild);
393
extracted = extractForTouchStart(CHILD_ID);
394
expect(spies.onStartShouldSetResponderChild.callCount).toBe(3);
395
assertGrantEvent(CHILD_ID, extracted);
396
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
397
assertRelease(CHILD_ID, extracted);
398
});
399
400
it('should give up responder to parent on move iff allowed', function() {
401
// Return true - we should become the responder.
402
var extracted;
403
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
404
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
405
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
406
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
407
onResponderGrant(CHILD_ID, onGrantChild);
408
onResponderGrant(PARENT_ID, onGrantParent);
409
410
spies.onStartShouldSetResponderChild.andReturn(true);
411
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
412
extracted = extractForTouchStart(CHILD_ID);
413
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
414
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0); // none yet
415
assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
416
417
extracted = extractForTouchMove(CHILD_ID);
418
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
419
assertGrantEvent(PARENT_ID, extracted);
420
assertTerminateEvent(CHILD_ID, extracted);
421
422
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
423
assertRelease(PARENT_ID, extracted);
424
});
425
426
it('should responder move only on direct responder', function() {
427
// Return true - we should become the responder.
428
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
429
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
430
431
var extracted = extractForTouchStart(CHILD_ID);
432
assertNothingExtracted(extracted);
433
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
434
expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
435
extractForTouchEnd(CHILD_ID); // Clear the responder
436
expect(ResponderEventPlugin.getResponderID()).toBe(null);
437
438
// Now try returning false - we should not become the responder.
439
spies.onStartShouldSetResponderChild.andReturn(false);
440
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
441
extracted = extractForTouchStart(CHILD_ID);
442
assertNothingExtracted(extracted);
443
expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
444
expect(ResponderEventPlugin.getResponderID()).toBe(null);
445
extractForTouchEnd(CHILD_ID); // Clear the responder
446
447
// Same thing as before but return true from "shouldSet".
448
spies.onStartShouldSetResponderChild.andReturn(true);
449
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
450
onResponderGrant(CHILD_ID, onGrantChild);
451
extracted = extractForTouchStart(CHILD_ID);
452
expect(spies.onStartShouldSetResponderChild.callCount).toBe(3);
453
assertGrantEvent(CHILD_ID, extracted);
454
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
455
assertRelease(CHILD_ID, extracted);
456
});
457
458
it('should give up responder to parent on scroll iff allowed', function() {
459
// Return true - we should become the responder.
460
var extracted;
461
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
462
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(false);
463
spyOn(spies, 'onScrollShouldSetResponderParent').andReturn(true);
464
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
465
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
466
onScrollShouldSetResponder(
467
PARENT_ID,
468
spies.onScrollShouldSetResponderParent
469
);
470
onResponderGrant(CHILD_ID, onGrantChild);
471
onResponderGrant(PARENT_ID, onGrantParent);
472
473
spies.onStartShouldSetResponderChild.andReturn(true);
474
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
475
extracted = extractForTouchStart(CHILD_ID);
476
expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
477
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0); // none yet
478
assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
479
480
extracted = extractForTouchMove(CHILD_ID);
481
expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
482
assertNothingExtracted(extracted);
483
484
extracted = extractForScroll(CHILD_ID); // Could have been parent here too.
485
expect(spies.onScrollShouldSetResponderParent.callCount).toBe(1);
486
assertGrantEvent(PARENT_ID, extracted);
487
assertTerminateEvent(CHILD_ID, extracted);
488
489
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
490
assertRelease(PARENT_ID, extracted);
491
});
492
493
494
});
495
496