main
js 1,494 lines 49.3 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 */
9
10 'use strict';
11
12 // This test is a hot mess. It heavily uses internals and relies on DOM even
13 // though the responder plugin is only used in React Native. Sadness ensues.
14 // The coverage is valuable though, so we will keep it for now.
15 const {HostComponent} = require('react-reconciler/src/ReactWorkTags');
16
17 let EventBatching;
18 let EventPluginUtils;
19 let ResponderEventPlugin;
20
21 const touch = function (nodeHandle, i) {
22 return {target: nodeHandle, identifier: i};
23 };
24
25 function injectComponentTree(ComponentTree) {
26 EventPluginUtils.setComponentTree(
27 ComponentTree.getFiberCurrentPropsFromNode,
28 ComponentTree.getInstanceFromNode,
29 ComponentTree.getNodeFromInstance,
30 );
31 }
32
33 /**
34 * @param {NodeHandle} nodeHandle @see NodeHandle. Handle of target.
35 * @param {Array<Touch>} touches All active touches.
36 * @param {Array<Touch>} changedTouches Only the touches that have changed.
37 * @return {TouchEvent} Model of a touch event that is compliant with responder
38 * system plugin.
39 */
40 const touchEvent = function (nodeHandle, touches, changedTouches) {
41 return {
42 target: nodeHandle,
43 changedTouches: changedTouches,
44 touches: touches,
45 };
46 };
47
48 const subsequence = function (arr, indices) {
49 const ret = [];
50 for (let i = 0; i < indices.length; i++) {
51 const index = indices[i];
52 ret.push(arr[index]);
53 }
54 return ret;
55 };
56
57 const antiSubsequence = function (arr, indices) {
58 const ret = [];
59 for (let i = 0; i < arr.length; i++) {
60 if (indices.indexOf(i) === -1) {
61 ret.push(arr[i]);
62 }
63 }
64 return ret;
65 };
66
67 /**
68 * Helper for creating touch test config data.
69 * @param allTouchHandles
70 */
71 const _touchConfig = function (
72 topType,
73 targetNodeHandle,
74 allTouchHandles,
75 changedIndices,
76 eventTarget,
77 ) {
78 const allTouchObjects = allTouchHandles.map(touch);
79 const changedTouchObjects = subsequence(allTouchObjects, changedIndices);
80 const activeTouchObjects =
81 topType === 'topTouchStart'
82 ? allTouchObjects
83 : topType === 'topTouchMove'
84 ? allTouchObjects
85 : topType === 'topTouchEnd'
86 ? antiSubsequence(allTouchObjects, changedIndices)
87 : topType === 'topTouchCancel'
88 ? antiSubsequence(allTouchObjects, changedIndices)
89 : null;
90
91 return {
92 nativeEvent: touchEvent(
93 targetNodeHandle,
94 activeTouchObjects,
95 changedTouchObjects,
96 ),
97 topLevelType: topType,
98 targetInst: getInstanceFromNode(targetNodeHandle),
99 };
100 };
101
102 /**
103 * Creates test data for touch events using environment agnostic "node
104 * handles".
105 *
106 * @param {NodeHandle} nodeHandle Environment agnostic handle to DOM node.
107 * @param {Array<NodeHandle>} allTouchHandles Encoding of all "touches" in the
108 * form of a mapping from integer (touch `identifier`) to touch target. This is
109 * encoded in array form. Because of this, it is possible for two separate
110 * touches (meaning two separate indices) to have the same touch target ID -
111 * this corresponds to real world cases where two separate unique touches have
112 * the same target. These touches don't just represent all active touches,
113 * rather it also includes any touches that are not active, but are in the
114 * process of being removed.
115 * @param {Array<NodeHandle>} changedIndices Indices of `allTouchHandles` that
116 * have changed.
117 * @return {object} Config data used by test cases for extracting responder
118 * events.
119 */
120 const startConfig = function (nodeHandle, allTouchHandles, changedIndices) {
121 return _touchConfig(
122 'topTouchStart',
123 nodeHandle,
124 allTouchHandles,
125 changedIndices,
126 nodeHandle,
127 );
128 };
129
130 /**
131 * @see `startConfig`
132 */
133 const moveConfig = function (nodeHandle, allTouchHandles, changedIndices) {
134 return _touchConfig(
135 'topTouchMove',
136 nodeHandle,
137 allTouchHandles,
138 changedIndices,
139 nodeHandle,
140 );
141 };
142
143 /**
144 * @see `startConfig`
145 */
146 const endConfig = function (nodeHandle, allTouchHandles, changedIndices) {
147 return _touchConfig(
148 'topTouchEnd',
149 nodeHandle,
150 allTouchHandles,
151 changedIndices,
152 nodeHandle,
153 );
154 };
155
156 /**
157 * Test config for events that aren't negotiation related, but rather result of
158 * a negotiation.
159 *
160 * Returns object of the form:
161 *
162 * {
163 * responderReject: {
164 * // Whatever "readableIDToID" was passed in.
165 * grandParent: {order: NA, assertEvent: null, returnVal: blah},
166 * ...
167 * child: {order: NA, assertEvent: null, returnVal: blah},
168 * }
169 * responderGrant: {
170 * grandParent: {order: NA, assertEvent: null, returnVal: blah},
171 * ...
172 * child: {order: NA, assertEvent: null, returnVal: blah}
173 * }
174 * ...
175 * }
176 *
177 * After this is created, a test case would configure specific event orderings
178 * and optional assertions. Anything left with an `order` of `NA` will be
179 * required to never be invoked (the test runner will make sure it throws if
180 * ever invoked).
181 *
182 */
183 const NA = -1;
184 const oneEventLoopTestConfig = function (readableIDToID) {
185 const ret = {
186 // Negotiation
187 scrollShouldSetResponder: {bubbled: {}, captured: {}},
188 startShouldSetResponder: {bubbled: {}, captured: {}},
189 moveShouldSetResponder: {bubbled: {}, captured: {}},
190 responderTerminationRequest: {},
191
192 // Non-negotiation
193 responderReject: {}, // These do not bubble capture.
194 responderGrant: {},
195 responderStart: {},
196 responderMove: {},
197 responderTerminate: {},
198 responderEnd: {},
199 responderRelease: {},
200 };
201 for (const eventName in ret) {
202 for (const readableNodeName in readableIDToID) {
203 if (ret[eventName].bubbled) {
204 // Two phase
205 ret[eventName].bubbled[readableNodeName] = {
206 order: NA,
207 assertEvent: null,
208 returnVal: undefined,
209 };
210 ret[eventName].captured[readableNodeName] = {
211 order: NA,
212 assertEvent: null,
213 returnVal: undefined,
214 };
215 } else {
216 ret[eventName][readableNodeName] = {
217 order: NA,
218 assertEvent: null,
219 returnVal: undefined,
220 };
221 }
222 }
223 }
224 return ret;
225 };
226
227 /**
228 * @param {object} eventTestConfig
229 * @param {object} readableIDToID
230 */
231 const registerTestHandlers = function (eventTestConfig, readableIDToID) {
232 const runs = {dispatchCount: 0};
233 const neverFire = function (readableID, registrationName) {
234 runs.dispatchCount++;
235 expect('').toBe(
236 'Event type: ' +
237 registrationName +
238 '\nShould never occur on:' +
239 readableID +
240 '\nFor event test config:\n' +
241 JSON.stringify(eventTestConfig) +
242 '\n',
243 );
244 };
245
246 const registerOneEventType = function (
247 registrationName,
248 eventTypeTestConfig,
249 ) {
250 for (const readableID in eventTypeTestConfig) {
251 const nodeConfig = eventTypeTestConfig[readableID];
252 const id = readableIDToID[readableID];
253 const handler =
254 nodeConfig.order === NA
255 ? neverFire.bind(null, readableID, registrationName)
256 : // We partially apply readableID and nodeConfig, as they change in the
257 // parent closure across iterations.
258 function (rID, config, e) {
259 expect(
260 rID +
261 '->' +
262 registrationName +
263 ' index:' +
264 runs.dispatchCount++,
265 ).toBe(rID + '->' + registrationName + ' index:' + config.order);
266 if (config.assertEvent) {
267 config.assertEvent(e);
268 }
269 return config.returnVal;
270 }.bind(null, readableID, nodeConfig);
271 putListener(getInstanceFromNode(id), registrationName, handler);
272 }
273 };
274 for (const eventName in eventTestConfig) {
275 const oneEventTypeTestConfig = eventTestConfig[eventName];
276 const hasTwoPhase = !!oneEventTypeTestConfig.bubbled;
277 if (hasTwoPhase) {
278 registerOneEventType(
279 ResponderEventPlugin.eventTypes[eventName].phasedRegistrationNames
280 .bubbled,
281 oneEventTypeTestConfig.bubbled,
282 );
283 registerOneEventType(
284 ResponderEventPlugin.eventTypes[eventName].phasedRegistrationNames
285 .captured,
286 oneEventTypeTestConfig.captured,
287 );
288 } else {
289 registerOneEventType(
290 ResponderEventPlugin.eventTypes[eventName].registrationName,
291 oneEventTypeTestConfig,
292 );
293 }
294 }
295 return runs;
296 };
297
298 const run = function (config, hierarchyConfig, nativeEventConfig) {
299 let max = NA;
300 const searchForMax = function (nodeConfig) {
301 for (const readableID in nodeConfig) {
302 const order = nodeConfig[readableID].order;
303 max = order > max ? order : max;
304 }
305 };
306 for (const eventName in config) {
307 const eventConfig = config[eventName];
308 if (eventConfig.bubbled) {
309 searchForMax(eventConfig.bubbled);
310 searchForMax(eventConfig.captured);
311 } else {
312 searchForMax(eventConfig);
313 }
314 }
315
316 // Register the handlers
317 const runData = registerTestHandlers(config, hierarchyConfig);
318
319 // Trigger the event
320 const extractedEvents = ResponderEventPlugin.extractEvents(
321 nativeEventConfig.topLevelType,
322 nativeEventConfig.targetInst,
323 nativeEventConfig.nativeEvent,
324 nativeEventConfig.target,
325 0,
326 );
327
328 // At this point the negotiation events have been dispatched as part of the
329 // extraction process, but not the side effectful events. Below, we dispatch
330 // side effectful events.
331 EventBatching.runEventsInBatch(extractedEvents);
332
333 // Ensure that every event that declared an `order`, was actually dispatched.
334 expect('number of events dispatched:' + runData.dispatchCount).toBe(
335 'number of events dispatched:' + (max + 1),
336 ); // +1 for extra ++
337 };
338
339 const GRANDPARENT_HOST_NODE = {};
340 const PARENT_HOST_NODE = {};
341 const CHILD_HOST_NODE = {};
342 const CHILD_HOST_NODE2 = {};
343
344 // These intentionally look like Fibers. ReactTreeTraversal depends on their field names.
345 // TODO: we could test this with regular DOM nodes (and real fibers) instead.
346 const GRANDPARENT_INST = {
347 return: null,
348 tag: HostComponent,
349 stateNode: GRANDPARENT_HOST_NODE,
350 memoizedProps: {},
351 };
352 const PARENT_INST = {
353 return: GRANDPARENT_INST,
354 tag: HostComponent,
355 stateNode: PARENT_HOST_NODE,
356 memoizedProps: {},
357 };
358 const CHILD_INST = {
359 return: PARENT_INST,
360 tag: HostComponent,
361 stateNode: CHILD_HOST_NODE,
362 memoizedProps: {},
363 };
364 const CHILD_INST2 = {
365 return: PARENT_INST,
366 tag: HostComponent,
367 stateNode: CHILD_HOST_NODE2,
368 memoizedProps: {},
369 };
370
371 GRANDPARENT_HOST_NODE.testInstance = GRANDPARENT_INST;
372 PARENT_HOST_NODE.testInstance = PARENT_INST;
373 CHILD_HOST_NODE.testInstance = CHILD_INST;
374 CHILD_HOST_NODE2.testInstance = CHILD_INST2;
375
376 const three = {
377 grandParent: GRANDPARENT_HOST_NODE,
378 parent: PARENT_HOST_NODE,
379 child: CHILD_HOST_NODE,
380 };
381
382 const siblings = {
383 parent: PARENT_HOST_NODE,
384 childOne: CHILD_HOST_NODE,
385 childTwo: CHILD_HOST_NODE2,
386 };
387
388 function getInstanceFromNode(node) {
389 return node.testInstance;
390 }
391
392 function getNodeFromInstance(inst) {
393 return inst.stateNode;
394 }
395
396 function getFiberCurrentPropsFromNode(node) {
397 return node.testInstance.memoizedProps;
398 }
399
400 function putListener(instance, registrationName, handler) {
401 instance.memoizedProps[registrationName] = handler;
402 }
403
404 function deleteAllListeners(instance) {
405 instance.memoizedProps = {};
406 }
407
408 describe('ResponderEventPlugin', () => {
409 beforeEach(() => {
410 jest.resetModules();
411
412 EventBatching = require('react-native-renderer/src/legacy-events/EventBatching');
413 EventPluginUtils = require('react-native-renderer/src/legacy-events/EventPluginUtils');
414 ResponderEventPlugin =
415 require('react-native-renderer/src/legacy-events/ResponderEventPlugin').default;
416
417 deleteAllListeners(GRANDPARENT_INST);
418 deleteAllListeners(PARENT_INST);
419 deleteAllListeners(CHILD_INST);
420 deleteAllListeners(CHILD_INST2);
421
422 injectComponentTree({
423 getInstanceFromNode,
424 getNodeFromInstance,
425 getFiberCurrentPropsFromNode,
426 });
427 });
428
429 it('should do nothing when no one wants to respond', () => {
430 let config = oneEventLoopTestConfig(three);
431 config.startShouldSetResponder.captured.grandParent = {
432 order: 0,
433 returnVal: false,
434 };
435 config.startShouldSetResponder.captured.parent = {
436 order: 1,
437 returnVal: false,
438 };
439 config.startShouldSetResponder.captured.child = {
440 order: 2,
441 returnVal: false,
442 };
443 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
444 config.startShouldSetResponder.bubbled.parent = {
445 order: 4,
446 returnVal: false,
447 };
448 config.startShouldSetResponder.bubbled.grandParent = {
449 order: 5,
450 returnVal: false,
451 };
452 run(config, three, startConfig(three.child, [three.child], [0]));
453 expect(ResponderEventPlugin._getResponder()).toBe(null);
454
455 // Now no handlers should be called on `touchEnd`.
456 config = oneEventLoopTestConfig(three);
457 run(config, three, endConfig(three.child, [three.child], [0]));
458 expect(ResponderEventPlugin._getResponder()).toBe(null);
459 });
460
461 /**
462 * Simple Start Granting
463 * --------------------
464 */
465
466 it('should grant responder grandParent while capturing', () => {
467 let config = oneEventLoopTestConfig(three);
468 config.startShouldSetResponder.captured.grandParent = {
469 order: 0,
470 returnVal: true,
471 };
472 config.responderGrant.grandParent = {order: 1};
473 config.responderStart.grandParent = {order: 2};
474 run(config, three, startConfig(three.child, [three.child], [0]));
475 expect(ResponderEventPlugin._getResponder()).toBe(
476 getInstanceFromNode(three.grandParent),
477 );
478
479 config = oneEventLoopTestConfig(three);
480 config.responderEnd.grandParent = {order: 0};
481 config.responderRelease.grandParent = {order: 1};
482 run(config, three, endConfig(three.child, [three.child], [0]));
483 expect(ResponderEventPlugin._getResponder()).toBe(null);
484 });
485
486 it('should grant responder parent while capturing', () => {
487 let config = oneEventLoopTestConfig(three);
488 config.startShouldSetResponder.captured.grandParent = {
489 order: 0,
490 returnVal: false,
491 };
492 config.startShouldSetResponder.captured.parent = {
493 order: 1,
494 returnVal: true,
495 };
496 config.responderGrant.parent = {order: 2};
497 config.responderStart.parent = {order: 3};
498 run(config, three, startConfig(three.child, [three.child], [0]));
499 expect(ResponderEventPlugin._getResponder()).toBe(
500 getInstanceFromNode(three.parent),
501 );
502
503 config = oneEventLoopTestConfig(three);
504 config.responderEnd.parent = {order: 0};
505 config.responderRelease.parent = {order: 1};
506 run(config, three, endConfig(three.child, [three.child], [0]));
507 expect(ResponderEventPlugin._getResponder()).toBe(null);
508 });
509
510 it('should grant responder child while capturing', () => {
511 let config = oneEventLoopTestConfig(three);
512 config.startShouldSetResponder.captured.grandParent = {
513 order: 0,
514 returnVal: false,
515 };
516 config.startShouldSetResponder.captured.parent = {
517 order: 1,
518 returnVal: false,
519 };
520 config.startShouldSetResponder.captured.child = {order: 2, returnVal: true};
521 config.responderGrant.child = {order: 3};
522 config.responderStart.child = {order: 4};
523 run(config, three, startConfig(three.child, [three.child], [0]));
524 expect(ResponderEventPlugin._getResponder()).toBe(
525 getInstanceFromNode(three.child),
526 );
527
528 config = oneEventLoopTestConfig(three);
529 config.responderEnd.child = {order: 0};
530 config.responderRelease.child = {order: 1};
531 run(config, three, endConfig(three.child, [three.child], [0]));
532 expect(ResponderEventPlugin._getResponder()).toBe(null);
533 });
534
535 it('should grant responder child while bubbling', () => {
536 let config = oneEventLoopTestConfig(three);
537 config.startShouldSetResponder.captured.grandParent = {
538 order: 0,
539 returnVal: false,
540 };
541 config.startShouldSetResponder.captured.parent = {
542 order: 1,
543 returnVal: false,
544 };
545 config.startShouldSetResponder.captured.child = {
546 order: 2,
547 returnVal: false,
548 };
549 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
550 config.responderGrant.child = {order: 4};
551 config.responderStart.child = {order: 5};
552 run(config, three, startConfig(three.child, [three.child], [0]));
553 expect(ResponderEventPlugin._getResponder()).toBe(
554 getInstanceFromNode(three.child),
555 );
556
557 config = oneEventLoopTestConfig(three);
558 config.responderEnd.child = {order: 0};
559 config.responderRelease.child = {order: 1};
560 run(config, three, endConfig(three.child, [three.child], [0]));
561 expect(ResponderEventPlugin._getResponder()).toBe(null);
562 });
563
564 it('should grant responder parent while bubbling', () => {
565 let config = oneEventLoopTestConfig(three);
566 config.startShouldSetResponder.captured.grandParent = {
567 order: 0,
568 returnVal: false,
569 };
570 config.startShouldSetResponder.captured.parent = {
571 order: 1,
572 returnVal: false,
573 };
574 config.startShouldSetResponder.captured.child = {
575 order: 2,
576 returnVal: false,
577 };
578 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
579 config.startShouldSetResponder.bubbled.parent = {order: 4, returnVal: true};
580 config.responderGrant.parent = {order: 5};
581 config.responderStart.parent = {order: 6};
582 run(config, three, startConfig(three.child, [three.child], [0]));
583 expect(ResponderEventPlugin._getResponder()).toBe(
584 getInstanceFromNode(three.parent),
585 );
586
587 config = oneEventLoopTestConfig(three);
588 config.responderEnd.parent = {order: 0};
589 config.responderRelease.parent = {order: 1};
590 run(config, three, endConfig(three.child, [three.child], [0]));
591 expect(ResponderEventPlugin._getResponder()).toBe(null);
592 });
593
594 it('should grant responder grandParent while bubbling', () => {
595 let config = oneEventLoopTestConfig(three);
596 config.startShouldSetResponder.captured.grandParent = {
597 order: 0,
598 returnVal: false,
599 };
600 config.startShouldSetResponder.captured.parent = {
601 order: 1,
602 returnVal: false,
603 };
604 config.startShouldSetResponder.captured.child = {
605 order: 2,
606 returnVal: false,
607 };
608 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
609 config.startShouldSetResponder.bubbled.parent = {
610 order: 4,
611 returnVal: false,
612 };
613 config.startShouldSetResponder.bubbled.grandParent = {
614 order: 5,
615 returnVal: true,
616 };
617 config.responderGrant.grandParent = {order: 6};
618 config.responderStart.grandParent = {order: 7};
619 run(config, three, startConfig(three.child, [three.child], [0]));
620 expect(ResponderEventPlugin._getResponder()).toBe(
621 getInstanceFromNode(three.grandParent),
622 );
623
624 config = oneEventLoopTestConfig(three);
625 config.responderEnd.grandParent = {order: 0};
626 config.responderRelease.grandParent = {order: 1};
627 run(config, three, endConfig(three.child, [three.child], [0]));
628 expect(ResponderEventPlugin._getResponder()).toBe(null);
629 });
630
631 /**
632 * Simple Move Granting
633 * --------------------
634 */
635
636 it('should grant responder grandParent while capturing move', () => {
637 let config = oneEventLoopTestConfig(three);
638
639 config.startShouldSetResponder.captured.grandParent = {order: 0};
640 config.startShouldSetResponder.captured.parent = {order: 1};
641 config.startShouldSetResponder.captured.child = {order: 2};
642 config.startShouldSetResponder.bubbled.child = {order: 3};
643 config.startShouldSetResponder.bubbled.parent = {order: 4};
644 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
645 run(config, three, startConfig(three.child, [three.child], [0]));
646
647 config = oneEventLoopTestConfig(three);
648 config.moveShouldSetResponder.captured.grandParent = {
649 order: 0,
650 returnVal: true,
651 };
652 config.responderGrant.grandParent = {order: 1};
653 config.responderMove.grandParent = {order: 2};
654 run(config, three, moveConfig(three.child, [three.child], [0]));
655 expect(ResponderEventPlugin._getResponder()).toBe(
656 getInstanceFromNode(three.grandParent),
657 );
658
659 config = oneEventLoopTestConfig(three);
660 config.responderEnd.grandParent = {order: 0};
661 config.responderRelease.grandParent = {order: 1};
662 run(config, three, endConfig(three.child, [three.child], [0]));
663 expect(ResponderEventPlugin._getResponder()).toBe(null);
664 });
665
666 it('should grant responder parent while capturing move', () => {
667 let config = oneEventLoopTestConfig(three);
668
669 config.startShouldSetResponder.captured.grandParent = {order: 0};
670 config.startShouldSetResponder.captured.parent = {order: 1};
671 config.startShouldSetResponder.captured.child = {order: 2};
672 config.startShouldSetResponder.bubbled.child = {order: 3};
673 config.startShouldSetResponder.bubbled.parent = {order: 4};
674 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
675 run(config, three, startConfig(three.child, [three.child], [0]));
676
677 config = oneEventLoopTestConfig(three);
678 config.moveShouldSetResponder.captured.grandParent = {
679 order: 0,
680 returnVal: false,
681 };
682 config.moveShouldSetResponder.captured.parent = {order: 1, returnVal: true};
683 config.responderGrant.parent = {order: 2};
684 config.responderMove.parent = {order: 3};
685 run(config, three, moveConfig(three.child, [three.child], [0]));
686 expect(ResponderEventPlugin._getResponder()).toBe(
687 getInstanceFromNode(three.parent),
688 );
689
690 config = oneEventLoopTestConfig(three);
691 config.responderEnd.parent = {order: 0};
692 config.responderRelease.parent = {order: 1};
693 run(config, three, endConfig(three.child, [three.child], [0]));
694 expect(ResponderEventPlugin._getResponder()).toBe(null);
695 });
696
697 it('should grant responder child while capturing move', () => {
698 let config = oneEventLoopTestConfig(three);
699
700 config.startShouldSetResponder.captured.grandParent = {order: 0};
701 config.startShouldSetResponder.captured.parent = {order: 1};
702 config.startShouldSetResponder.captured.child = {order: 2};
703 config.startShouldSetResponder.bubbled.child = {order: 3};
704 config.startShouldSetResponder.bubbled.parent = {order: 4};
705 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
706 run(config, three, startConfig(three.child, [three.child], [0]));
707
708 config = oneEventLoopTestConfig(three);
709 config.moveShouldSetResponder.captured.grandParent = {
710 order: 0,
711 returnVal: false,
712 };
713 config.moveShouldSetResponder.captured.parent = {
714 order: 1,
715 returnVal: false,
716 };
717 config.moveShouldSetResponder.captured.child = {order: 2, returnVal: true};
718 config.responderGrant.child = {order: 3};
719 config.responderMove.child = {order: 4};
720 run(config, three, moveConfig(three.child, [three.child], [0]));
721 expect(ResponderEventPlugin._getResponder()).toBe(
722 getInstanceFromNode(three.child),
723 );
724
725 config = oneEventLoopTestConfig(three);
726 config.responderEnd.child = {order: 0};
727 config.responderRelease.child = {order: 1};
728 run(config, three, endConfig(three.child, [three.child], [0]));
729 expect(ResponderEventPlugin._getResponder()).toBe(null);
730 });
731
732 it('should grant responder child while bubbling move', () => {
733 let config = oneEventLoopTestConfig(three);
734
735 config.startShouldSetResponder.captured.grandParent = {order: 0};
736 config.startShouldSetResponder.captured.parent = {order: 1};
737 config.startShouldSetResponder.captured.child = {order: 2};
738 config.startShouldSetResponder.bubbled.child = {order: 3};
739 config.startShouldSetResponder.bubbled.parent = {order: 4};
740 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
741 run(config, three, startConfig(three.child, [three.child], [0]));
742
743 config = oneEventLoopTestConfig(three);
744 config.moveShouldSetResponder.captured.grandParent = {
745 order: 0,
746 returnVal: false,
747 };
748 config.moveShouldSetResponder.captured.parent = {
749 order: 1,
750 returnVal: false,
751 };
752 config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
753 config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
754 config.responderGrant.child = {order: 4};
755 config.responderMove.child = {order: 5};
756 run(config, three, moveConfig(three.child, [three.child], [0]));
757 expect(ResponderEventPlugin._getResponder()).toBe(
758 getInstanceFromNode(three.child),
759 );
760
761 config = oneEventLoopTestConfig(three);
762 config.responderEnd.child = {order: 0};
763 config.responderRelease.child = {order: 1};
764 run(config, three, endConfig(three.child, [three.child], [0]));
765 expect(ResponderEventPlugin._getResponder()).toBe(null);
766 });
767
768 it('should grant responder parent while bubbling move', () => {
769 let config = oneEventLoopTestConfig(three);
770
771 config.startShouldSetResponder.captured.grandParent = {order: 0};
772 config.startShouldSetResponder.captured.parent = {order: 1};
773 config.startShouldSetResponder.captured.child = {order: 2};
774 config.startShouldSetResponder.bubbled.child = {order: 3};
775 config.startShouldSetResponder.bubbled.parent = {order: 4};
776 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
777 run(config, three, startConfig(three.child, [three.child], [0]));
778
779 config = oneEventLoopTestConfig(three);
780 config.moveShouldSetResponder.captured.grandParent = {
781 order: 0,
782 returnVal: false,
783 };
784 config.moveShouldSetResponder.captured.parent = {
785 order: 1,
786 returnVal: false,
787 };
788 config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
789 config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
790 config.moveShouldSetResponder.bubbled.parent = {order: 4, returnVal: true};
791 config.responderGrant.parent = {order: 5};
792 config.responderMove.parent = {order: 6};
793 run(config, three, moveConfig(three.child, [three.child], [0]));
794 expect(ResponderEventPlugin._getResponder()).toBe(
795 getInstanceFromNode(three.parent),
796 );
797
798 config = oneEventLoopTestConfig(three);
799 config.responderEnd.parent = {order: 0};
800 config.responderRelease.parent = {order: 1};
801 run(config, three, endConfig(three.child, [three.child], [0]));
802 expect(ResponderEventPlugin._getResponder()).toBe(null);
803 });
804
805 it('should grant responder grandParent while bubbling move', () => {
806 let config = oneEventLoopTestConfig(three);
807
808 config.startShouldSetResponder.captured.grandParent = {order: 0};
809 config.startShouldSetResponder.captured.parent = {order: 1};
810 config.startShouldSetResponder.captured.child = {order: 2};
811 config.startShouldSetResponder.bubbled.child = {order: 3};
812 config.startShouldSetResponder.bubbled.parent = {order: 4};
813 config.startShouldSetResponder.bubbled.grandParent = {order: 5};
814 run(config, three, startConfig(three.child, [three.child], [0]));
815
816 config = oneEventLoopTestConfig(three);
817 config.moveShouldSetResponder.captured.grandParent = {
818 order: 0,
819 returnVal: false,
820 };
821 config.moveShouldSetResponder.captured.parent = {
822 order: 1,
823 returnVal: false,
824 };
825 config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
826 config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
827 config.moveShouldSetResponder.bubbled.parent = {order: 4, returnVal: false};
828 config.moveShouldSetResponder.bubbled.grandParent = {
829 order: 5,
830 returnVal: true,
831 };
832 config.responderGrant.grandParent = {order: 6};
833 config.responderMove.grandParent = {order: 7};
834 run(config, three, moveConfig(three.child, [three.child], [0]));
835 expect(ResponderEventPlugin._getResponder()).toBe(
836 getInstanceFromNode(three.grandParent),
837 );
838
839 config = oneEventLoopTestConfig(three);
840 config.responderEnd.grandParent = {order: 0};
841 config.responderRelease.grandParent = {order: 1};
842 run(config, three, endConfig(three.child, [three.child], [0]));
843 expect(ResponderEventPlugin._getResponder()).toBe(null);
844 });
845
846 /**
847 * Common ancestor tests
848 * ---------------------
849 */
850
851 it('should bubble negotiation to first common ancestor of responder', () => {
852 let config = oneEventLoopTestConfig(three);
853 config.startShouldSetResponder.captured.grandParent = {
854 order: 0,
855 returnVal: false,
856 };
857 config.startShouldSetResponder.captured.parent = {
858 order: 1,
859 returnVal: true,
860 };
861 config.responderGrant.parent = {order: 2};
862 config.responderStart.parent = {order: 3};
863 run(config, three, startConfig(three.child, [three.child], [0]));
864 expect(ResponderEventPlugin._getResponder()).toBe(
865 getInstanceFromNode(three.parent),
866 );
867
868 // While `parent` is still responder, we create new handlers that verify
869 // the ordering of propagation, restarting the count at `0`.
870 config = oneEventLoopTestConfig(three);
871 config.startShouldSetResponder.captured.grandParent = {
872 order: 0,
873 returnVal: false,
874 };
875
876 config.startShouldSetResponder.bubbled.grandParent = {
877 order: 1,
878 returnVal: false,
879 };
880 config.responderStart.parent = {order: 2};
881 run(config, three, startConfig(three.child, [three.child], [0]));
882 expect(ResponderEventPlugin._getResponder()).toBe(
883 getInstanceFromNode(three.parent),
884 );
885
886 config = oneEventLoopTestConfig(three);
887 config.responderEnd.parent = {order: 0};
888 config.responderRelease.parent = {order: 1};
889 run(config, three, endConfig(three.child, [three.child], [0]));
890 expect(ResponderEventPlugin._getResponder()).toBe(null);
891 });
892
893 it('should bubble negotiation to first common ancestor of responder then transfer', () => {
894 let config = oneEventLoopTestConfig(three);
895 config.startShouldSetResponder.captured.grandParent = {
896 order: 0,
897 returnVal: false,
898 };
899 config.startShouldSetResponder.captured.parent = {
900 order: 1,
901 returnVal: true,
902 };
903 config.responderGrant.parent = {order: 2};
904 config.responderStart.parent = {order: 3};
905 run(config, three, startConfig(three.child, [three.child], [0]));
906 expect(ResponderEventPlugin._getResponder()).toBe(
907 getInstanceFromNode(three.parent),
908 );
909
910 config = oneEventLoopTestConfig(three);
911
912 // Parent is responder, and responder is transferred by a second touch start
913 config.startShouldSetResponder.captured.grandParent = {
914 order: 0,
915 returnVal: true,
916 };
917 config.responderGrant.grandParent = {order: 1};
918 config.responderTerminationRequest.parent = {order: 2, returnVal: true};
919 config.responderTerminate.parent = {order: 3};
920 config.responderStart.grandParent = {order: 4};
921 run(
922 config,
923 three,
924 startConfig(three.child, [three.child, three.child], [1]),
925 );
926 expect(ResponderEventPlugin._getResponder()).toBe(
927 getInstanceFromNode(three.grandParent),
928 );
929
930 config = oneEventLoopTestConfig(three);
931 config.responderEnd.grandParent = {order: 0};
932 // one remains\ /one ended \
933 run(config, three, endConfig(three.child, [three.child, three.child], [1]));
934 expect(ResponderEventPlugin._getResponder()).toBe(
935 getInstanceFromNode(three.grandParent),
936 );
937
938 config = oneEventLoopTestConfig(three);
939 config.responderEnd.grandParent = {order: 0};
940 config.responderRelease.grandParent = {order: 1};
941 run(config, three, endConfig(three.child, [three.child], [0]));
942 expect(ResponderEventPlugin._getResponder()).toBe(null);
943 });
944
945 /**
946 * If nothing is responder, then the negotiation should propagate directly to
947 * the deepest target in the second touch.
948 */
949 it('should negotiate with deepest target on second touch if nothing is responder', () => {
950 // Initially nothing wants to become the responder
951 let config = oneEventLoopTestConfig(three);
952 config.startShouldSetResponder.captured.grandParent = {
953 order: 0,
954 returnVal: false,
955 };
956 config.startShouldSetResponder.captured.parent = {
957 order: 1,
958 returnVal: false,
959 };
960 config.startShouldSetResponder.bubbled.parent = {
961 order: 2,
962 returnVal: false,
963 };
964 config.startShouldSetResponder.bubbled.grandParent = {
965 order: 3,
966 returnVal: false,
967 };
968
969 run(config, three, startConfig(three.parent, [three.parent], [0]));
970 expect(ResponderEventPlugin._getResponder()).toBe(null);
971
972 config = oneEventLoopTestConfig(three);
973
974 // Now child wants to become responder. Negotiation should bubble as deep
975 // as the target is because we don't find first common ancestor (with
976 // current responder) because there is no current responder.
977 // (Even if this is the second active touch).
978 config.startShouldSetResponder.captured.grandParent = {
979 order: 0,
980 returnVal: false,
981 };
982 config.startShouldSetResponder.captured.parent = {
983 order: 1,
984 returnVal: false,
985 };
986 config.startShouldSetResponder.captured.child = {
987 order: 2,
988 returnVal: false,
989 };
990 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
991 config.responderGrant.child = {order: 4};
992 config.responderStart.child = {order: 5};
993 // / Two active touches \ /one of them new\
994 run(
995 config,
996 three,
997 startConfig(three.child, [three.parent, three.child], [1]),
998 );
999 expect(ResponderEventPlugin._getResponder()).toBe(
1000 getInstanceFromNode(three.child),
1001 );
1002
1003 // Now we remove the original first touch, keeping the second touch that
1004 // started within the current responder (child). Nothing changes because
1005 // there's still touches that started inside of the current responder.
1006 config = oneEventLoopTestConfig(three);
1007 config.responderEnd.child = {order: 0};
1008 // / one ended\ /one remains \
1009 run(
1010 config,
1011 three,
1012 endConfig(three.child, [three.parent, three.child], [0]),
1013 );
1014 expect(ResponderEventPlugin._getResponder()).toBe(
1015 getInstanceFromNode(three.child),
1016 );
1017
1018 // Okay, now let's add back that first touch (nothing should change) and
1019 // then we'll try peeling back the touches in the opposite order to make
1020 // sure that first removing the second touch instantly causes responder to
1021 // be released.
1022 config = oneEventLoopTestConfig(three);
1023 config.startShouldSetResponder.captured.grandParent = {
1024 order: 0,
1025 returnVal: false,
1026 };
1027 config.startShouldSetResponder.captured.parent = {
1028 order: 1,
1029 returnVal: false,
1030 };
1031 config.startShouldSetResponder.bubbled.parent = {
1032 order: 2,
1033 returnVal: false,
1034 };
1035 config.startShouldSetResponder.bubbled.grandParent = {
1036 order: 3,
1037 returnVal: false,
1038 };
1039 // Interesting: child still gets moves even though touch target is parent!
1040 // Current responder gets a `responderStart` for any touch while responder.
1041 config.responderStart.child = {order: 4};
1042 // / Two active touches \ /one of them new\
1043 run(
1044 config,
1045 three,
1046 startConfig(three.parent, [three.child, three.parent], [1]),
1047 );
1048 expect(ResponderEventPlugin._getResponder()).toBe(
1049 getInstanceFromNode(three.child),
1050 );
1051
1052 // Now, move that new touch that had no effect, and did not start within
1053 // the current responder.
1054 config = oneEventLoopTestConfig(three);
1055 config.moveShouldSetResponder.captured.grandParent = {
1056 order: 0,
1057 returnVal: false,
1058 };
1059 config.moveShouldSetResponder.captured.parent = {
1060 order: 1,
1061 returnVal: false,
1062 };
1063 config.moveShouldSetResponder.bubbled.parent = {order: 2, returnVal: false};
1064 config.moveShouldSetResponder.bubbled.grandParent = {
1065 order: 3,
1066 returnVal: false,
1067 };
1068 // Interesting: child still gets moves even though touch target is parent!
1069 // Current responder gets a `responderMove` for any touch while responder.
1070 config.responderMove.child = {order: 4};
1071 // / Two active touches \ /one of them moved\
1072 run(
1073 config,
1074 three,
1075 moveConfig(three.parent, [three.child, three.parent], [1]),
1076 );
1077 expect(ResponderEventPlugin._getResponder()).toBe(
1078 getInstanceFromNode(three.child),
1079 );
1080
1081 config = oneEventLoopTestConfig(three);
1082 config.responderEnd.child = {order: 0};
1083 config.responderRelease.child = {order: 1};
1084 // /child end \ /parent remain\
1085 run(
1086 config,
1087 three,
1088 endConfig(three.child, [three.child, three.parent], [0]),
1089 );
1090 expect(ResponderEventPlugin._getResponder()).toBe(null);
1091 });
1092
1093 /**
1094 * If nothing is responder, then the negotiation should propagate directly to
1095 * the deepest target in the second touch.
1096 */
1097 it('should negotiate until first common ancestor when there are siblings', () => {
1098 // Initially nothing wants to become the responder
1099 let config = oneEventLoopTestConfig(siblings);
1100 config.startShouldSetResponder.captured.parent = {
1101 order: 0,
1102 returnVal: false,
1103 };
1104 config.startShouldSetResponder.captured.childOne = {
1105 order: 1,
1106 returnVal: false,
1107 };
1108 config.startShouldSetResponder.bubbled.childOne = {
1109 order: 2,
1110 returnVal: true,
1111 };
1112 config.responderGrant.childOne = {order: 3};
1113 config.responderStart.childOne = {order: 4};
1114
1115 run(
1116 config,
1117 siblings,
1118 startConfig(siblings.childOne, [siblings.childOne], [0]),
1119 );
1120 expect(ResponderEventPlugin._getResponder()).toBe(
1121 getInstanceFromNode(siblings.childOne),
1122 );
1123
1124 // If the touch target is the sibling item, the negotiation should only
1125 // propagate to first common ancestor of current responder and sibling (so
1126 // the parent).
1127 config = oneEventLoopTestConfig(siblings);
1128 config.startShouldSetResponder.captured.parent = {
1129 order: 0,
1130 returnVal: false,
1131 };
1132 config.startShouldSetResponder.bubbled.parent = {
1133 order: 1,
1134 returnVal: false,
1135 };
1136 config.responderStart.childOne = {order: 2};
1137
1138 const touchConfig = startConfig(
1139 siblings.childTwo,
1140 [siblings.childOne, siblings.childTwo],
1141 [1],
1142 );
1143 run(config, siblings, touchConfig);
1144 expect(ResponderEventPlugin._getResponder()).toBe(
1145 getInstanceFromNode(siblings.childOne),
1146 );
1147
1148 // move childOne
1149 config = oneEventLoopTestConfig(siblings);
1150 config.moveShouldSetResponder.captured.parent = {
1151 order: 0,
1152 returnVal: false,
1153 };
1154 config.moveShouldSetResponder.bubbled.parent = {order: 1, returnVal: false};
1155 config.responderMove.childOne = {order: 2};
1156 run(
1157 config,
1158 siblings,
1159 moveConfig(
1160 siblings.childOne,
1161 [siblings.childOne, siblings.childTwo],
1162 [0],
1163 ),
1164 );
1165 expect(ResponderEventPlugin._getResponder()).toBe(
1166 getInstanceFromNode(siblings.childOne),
1167 );
1168
1169 // move childTwo: Only negotiates to `parent`.
1170 config = oneEventLoopTestConfig(siblings);
1171 config.moveShouldSetResponder.captured.parent = {
1172 order: 0,
1173 returnVal: false,
1174 };
1175 config.moveShouldSetResponder.bubbled.parent = {order: 1, returnVal: false};
1176 config.responderMove.childOne = {order: 2};
1177 run(
1178 config,
1179 siblings,
1180 moveConfig(
1181 siblings.childTwo,
1182 [siblings.childOne, siblings.childTwo],
1183 [1],
1184 ),
1185 );
1186 expect(ResponderEventPlugin._getResponder()).toBe(
1187 getInstanceFromNode(siblings.childOne),
1188 );
1189 });
1190
1191 it('should notify of being rejected. responderStart/Move happens on current responder', () => {
1192 // Initially nothing wants to become the responder
1193 let config = oneEventLoopTestConfig(three);
1194 config.startShouldSetResponder.captured.grandParent = {
1195 order: 0,
1196 returnVal: false,
1197 };
1198 config.startShouldSetResponder.captured.parent = {
1199 order: 1,
1200 returnVal: false,
1201 };
1202 config.startShouldSetResponder.captured.child = {
1203 order: 2,
1204 returnVal: false,
1205 };
1206 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
1207 config.responderGrant.child = {order: 4};
1208 config.responderStart.child = {order: 5};
1209
1210 run(config, three, startConfig(three.child, [three.child], [0]));
1211 expect(ResponderEventPlugin._getResponder()).toBe(
1212 getInstanceFromNode(three.child),
1213 );
1214
1215 // Suppose parent wants to become responder on move, and is rejected
1216 config = oneEventLoopTestConfig(three);
1217 config.moveShouldSetResponder.captured.grandParent = {
1218 order: 0,
1219 returnVal: false,
1220 };
1221 config.moveShouldSetResponder.captured.parent = {
1222 order: 1,
1223 returnVal: false,
1224 };
1225 config.moveShouldSetResponder.bubbled.parent = {order: 2, returnVal: true};
1226 config.responderGrant.parent = {order: 3};
1227 config.responderTerminationRequest.child = {order: 4, returnVal: false};
1228 config.responderReject.parent = {order: 5};
1229 // The start/move should occur on the original responder if new one is rejected
1230 config.responderMove.child = {order: 6};
1231
1232 let touchConfig = moveConfig(three.child, [three.child], [0]);
1233 run(config, three, touchConfig);
1234 expect(ResponderEventPlugin._getResponder()).toBe(
1235 getInstanceFromNode(three.child),
1236 );
1237
1238 config = oneEventLoopTestConfig(three);
1239 config.startShouldSetResponder.captured.grandParent = {
1240 order: 0,
1241 returnVal: false,
1242 };
1243 config.startShouldSetResponder.captured.parent = {
1244 order: 1,
1245 returnVal: false,
1246 };
1247 config.startShouldSetResponder.bubbled.parent = {order: 2, returnVal: true};
1248 config.responderGrant.parent = {order: 3};
1249 config.responderTerminationRequest.child = {order: 4, returnVal: false};
1250 config.responderReject.parent = {order: 5};
1251 // The start/move should occur on the original responder if new one is rejected
1252 config.responderStart.child = {order: 6};
1253
1254 touchConfig = startConfig(three.child, [three.child, three.child], [1]);
1255 run(config, three, touchConfig);
1256 expect(ResponderEventPlugin._getResponder()).toBe(
1257 getInstanceFromNode(three.child),
1258 );
1259 });
1260
1261 it('should negotiate scroll', () => {
1262 // Initially nothing wants to become the responder
1263 let config = oneEventLoopTestConfig(three);
1264 config.startShouldSetResponder.captured.grandParent = {
1265 order: 0,
1266 returnVal: false,
1267 };
1268 config.startShouldSetResponder.captured.parent = {
1269 order: 1,
1270 returnVal: false,
1271 };
1272 config.startShouldSetResponder.captured.child = {
1273 order: 2,
1274 returnVal: false,
1275 };
1276 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
1277 config.responderGrant.child = {order: 4};
1278 config.responderStart.child = {order: 5};
1279
1280 run(config, three, startConfig(three.child, [three.child], [0]));
1281 expect(ResponderEventPlugin._getResponder()).toBe(
1282 getInstanceFromNode(three.child),
1283 );
1284
1285 // If the touch target is the sibling item, the negotiation should only
1286 // propagate to first common ancestor of current responder and sibling (so
1287 // the parent).
1288 config = oneEventLoopTestConfig(three);
1289 config.scrollShouldSetResponder.captured.grandParent = {
1290 order: 0,
1291 returnVal: false,
1292 };
1293 config.scrollShouldSetResponder.captured.parent = {
1294 order: 1,
1295 returnVal: false,
1296 };
1297 config.scrollShouldSetResponder.bubbled.parent = {
1298 order: 2,
1299 returnVal: true,
1300 };
1301 config.responderGrant.parent = {order: 3};
1302 config.responderTerminationRequest.child = {order: 4, returnVal: false};
1303 config.responderReject.parent = {order: 5};
1304
1305 run(config, three, {
1306 topLevelType: 'topScroll',
1307 targetInst: getInstanceFromNode(three.parent),
1308 nativeEvent: {},
1309 });
1310 expect(ResponderEventPlugin._getResponder()).toBe(
1311 getInstanceFromNode(three.child),
1312 );
1313
1314 // Now lets let the scroll take control this time.
1315 config = oneEventLoopTestConfig(three);
1316 config.scrollShouldSetResponder.captured.grandParent = {
1317 order: 0,
1318 returnVal: false,
1319 };
1320 config.scrollShouldSetResponder.captured.parent = {
1321 order: 1,
1322 returnVal: false,
1323 };
1324 config.scrollShouldSetResponder.bubbled.parent = {
1325 order: 2,
1326 returnVal: true,
1327 };
1328 config.responderGrant.parent = {order: 3};
1329 config.responderTerminationRequest.child = {order: 4, returnVal: true};
1330 config.responderTerminate.child = {order: 5};
1331
1332 run(config, three, {
1333 topLevelType: 'topScroll',
1334 targetInst: getInstanceFromNode(three.parent),
1335 nativeEvent: {},
1336 });
1337 expect(ResponderEventPlugin._getResponder()).toBe(
1338 getInstanceFromNode(three.parent),
1339 );
1340 });
1341
1342 it('should cancel correctly', () => {
1343 // Initially our child becomes responder
1344 let config = oneEventLoopTestConfig(three);
1345 config.startShouldSetResponder.captured.grandParent = {
1346 order: 0,
1347 returnVal: false,
1348 };
1349 config.startShouldSetResponder.captured.parent = {
1350 order: 1,
1351 returnVal: false,
1352 };
1353 config.startShouldSetResponder.captured.child = {
1354 order: 2,
1355 returnVal: false,
1356 };
1357 config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
1358 config.responderGrant.child = {order: 4};
1359 config.responderStart.child = {order: 5};
1360
1361 run(config, three, startConfig(three.child, [three.child], [0]));
1362 expect(ResponderEventPlugin._getResponder()).toBe(
1363 getInstanceFromNode(three.child),
1364 );
1365
1366 config = oneEventLoopTestConfig(three);
1367 config.responderEnd.child = {order: 0};
1368 config.responderTerminate.child = {order: 1};
1369
1370 const nativeEvent = _touchConfig(
1371 'topTouchCancel',
1372 three.child,
1373 [three.child],
1374 [0],
1375 );
1376 run(config, three, nativeEvent);
1377 expect(ResponderEventPlugin._getResponder()).toBe(null);
1378 });
1379
1380 it(
1381 'should determine the first common ancestor correctly',
1382 async () => {
1383 // This test was moved here from the ReactTreeTraversal test since only the
1384 // ResponderEventPlugin uses `getLowestCommonAncestor`
1385 const React = require('react');
1386 const ReactDOMClient = require('react-dom/client');
1387 const act = require('internal-test-utils').act;
1388 const getLowestCommonAncestor =
1389 require('react-native-renderer/src/legacy-events/ResponderEventPlugin').getLowestCommonAncestor;
1390 // This works by accident and will likely break in the future.
1391 const ReactDOMComponentTree = require('react-dom-bindings/src/client/ReactDOMComponentTree');
1392
1393 class ChildComponent extends React.Component {
1394 divRef = React.createRef();
1395 div1Ref = React.createRef();
1396 div2Ref = React.createRef();
1397
1398 render() {
1399 return (
1400 <div ref={this.divRef} id={this.props.id + '__DIV'}>
1401 <div ref={this.div1Ref} id={this.props.id + '__DIV_1'} />
1402 <div ref={this.div2Ref} id={this.props.id + '__DIV_2'} />
1403 </div>
1404 );
1405 }
1406 }
1407
1408 class ParentComponent extends React.Component {
1409 pRef = React.createRef();
1410 p_P1Ref = React.createRef();
1411 p_P1_C1Ref = React.createRef();
1412 p_P1_C2Ref = React.createRef();
1413 p_OneOffRef = React.createRef();
1414
1415 render() {
1416 return (
1417 <div ref={this.pRef} id="P">
1418 <div ref={this.p_P1Ref} id="P_P1">
1419 <ChildComponent ref={this.p_P1_C1Ref} id="P_P1_C1" />
1420 <ChildComponent ref={this.p_P1_C2Ref} id="P_P1_C2" />
1421 </div>
1422 <div ref={this.p_OneOffRef} id="P_OneOff" />
1423 </div>
1424 );
1425 }
1426 }
1427
1428 const container = document.createElement('div');
1429 const root = ReactDOMClient.createRoot(container);
1430 let parent;
1431 await act(() => {
1432 root.render(<ParentComponent ref={current => (parent = current)} />);
1433 });
1434
1435 const ancestors = [
1436 // Common ancestor with self is self.
1437 {
1438 one: parent.p_P1_C1Ref.current.div1Ref.current,
1439 two: parent.p_P1_C1Ref.current.div1Ref.current,
1440 com: parent.p_P1_C1Ref.current.div1Ref.current,
1441 },
1442 // Common ancestor with self is self - even if topmost DOM.
1443 {
1444 one: parent.pRef.current,
1445 two: parent.pRef.current,
1446 com: parent.pRef.current,
1447 },
1448 // Siblings
1449 {
1450 one: parent.p_P1_C1Ref.current.div1Ref.current,
1451 two: parent.p_P1_C1Ref.current.div2Ref.current,
1452 com: parent.p_P1_C1Ref.current.divRef.current,
1453 },
1454 // Common ancestor with parent is the parent.
1455 {
1456 one: parent.p_P1_C1Ref.current.div1Ref.current,
1457 two: parent.p_P1_C1Ref.current.divRef.current,
1458 com: parent.p_P1_C1Ref.current.divRef.current,
1459 },
1460 // Common ancestor with grandparent is the grandparent.
1461 {
1462 one: parent.p_P1_C1Ref.current.div1Ref.current,
1463 two: parent.p_P1Ref.current,
1464 com: parent.p_P1Ref.current,
1465 },
1466 // Grandparent across subcomponent boundaries.
1467 {
1468 one: parent.p_P1_C1Ref.current.div1Ref.current,
1469 two: parent.p_P1_C2Ref.current.div1Ref.current,
1470 com: parent.p_P1Ref.current,
1471 },
1472 // Something deep with something one-off.
1473 {
1474 one: parent.p_P1_C1Ref.current.div1Ref.current,
1475 two: parent.p_OneOffRef.current,
1476 com: parent.pRef.current,
1477 },
1478 ];
1479 let i;
1480 for (i = 0; i < ancestors.length; i++) {
1481 const plan = ancestors[i];
1482 const firstCommon = getLowestCommonAncestor(
1483 ReactDOMComponentTree.getInstanceFromNode(plan.one),
1484 ReactDOMComponentTree.getInstanceFromNode(plan.two),
1485 );
1486 expect(firstCommon).toBe(
1487 ReactDOMComponentTree.getInstanceFromNode(plan.com),
1488 );
1489 }
1490 },
1491 // TODO: this is a long running test, we should speed it up.
1492 60 * 1000,
1493 );
1494 });