main
js 1,663 lines 46.8 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 * @jest-environment node
9 */
10
11 'use strict';
12
13 let React;
14 let ReactFabric;
15 let ReactNativePrivateInterface;
16 let createReactNativeComponentClass;
17 let StrictMode;
18 let act;
19 let assertConsoleErrorDev;
20
21 const DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT =
22 "dispatchCommand was called with a ref that isn't a " +
23 'native component. Use React.forwardRef to get access to the underlying native component';
24
25 const SEND_ACCESSIBILITY_EVENT_REQUIRES_HOST_COMPONENT =
26 "sendAccessibilityEvent was called with a ref that isn't a " +
27 'native component. Use React.forwardRef to get access to the underlying native component';
28
29 describe('ReactFabric', () => {
30 beforeEach(() => {
31 jest.resetModules();
32
33 require('react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager');
34
35 React = require('react');
36 StrictMode = React.StrictMode;
37 ReactFabric = require('react-native-renderer/fabric');
38 ReactNativePrivateInterface = require('react-native/react-private-interface');
39 createReactNativeComponentClass =
40 require('react-native/react-private-interface')
41 .ReactNativeViewConfigRegistry.register;
42 ({act, assertConsoleErrorDev} = require('internal-test-utils'));
43 });
44
45 it('should be able to create and render a native component', async () => {
46 const View = createReactNativeComponentClass('RCTView', () => ({
47 validAttributes: {foo: true},
48 uiViewClassName: 'RCTView',
49 }));
50
51 await act(() => {
52 ReactFabric.render(<View foo="test" />, 1, null, true);
53 });
54 expect(nativeFabricUIManager.createNode).toHaveBeenCalled();
55 expect(nativeFabricUIManager.appendChild).not.toHaveBeenCalled();
56 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
57 });
58
59 it('should be able to create and update a native component', async () => {
60 const View = createReactNativeComponentClass('RCTView', () => ({
61 validAttributes: {foo: true},
62 uiViewClassName: 'RCTView',
63 }));
64
65 const firstNode = {};
66
67 nativeFabricUIManager.createNode.mockReturnValue(firstNode);
68
69 await act(() => {
70 ReactFabric.render(<View foo="foo" />, 11, null, true);
71 });
72
73 expect(nativeFabricUIManager.createNode).toHaveBeenCalledTimes(1);
74
75 await act(() => {
76 ReactFabric.render(<View foo="bar" />, 11, null, true);
77 });
78
79 expect(nativeFabricUIManager.createNode).toHaveBeenCalledTimes(1);
80 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
81 1,
82 );
83 expect(nativeFabricUIManager.cloneNodeWithNewProps.mock.calls[0][0]).toBe(
84 firstNode,
85 );
86 expect(
87 nativeFabricUIManager.cloneNodeWithNewProps.mock.calls[0][1],
88 ).toEqual({
89 foo: 'bar',
90 });
91 });
92
93 it('should not call FabricUIManager.cloneNode after render for properties that have not changed', async () => {
94 const Text = createReactNativeComponentClass('RCTText', () => ({
95 validAttributes: {foo: true},
96 uiViewClassName: 'RCTText',
97 }));
98
99 await act(() => {
100 ReactFabric.render(<Text foo="a">1</Text>, 11, null, true);
101 });
102 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
103 expect(
104 nativeFabricUIManager.cloneNodeWithNewChildren,
105 ).not.toHaveBeenCalled();
106 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
107 expect(
108 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
109 ).not.toHaveBeenCalled();
110
111 // If no properties have changed, we shouldn't call cloneNode.
112 await act(() => {
113 ReactFabric.render(<Text foo="a">1</Text>, 11, null, true);
114 });
115 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
116 expect(
117 nativeFabricUIManager.cloneNodeWithNewChildren,
118 ).not.toHaveBeenCalled();
119 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
120 expect(
121 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
122 ).not.toHaveBeenCalled();
123
124 // Only call cloneNode for the changed property (and not for text).
125 await act(() => {
126 ReactFabric.render(<Text foo="b">1</Text>, 11, null, true);
127 });
128 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
129 expect(
130 nativeFabricUIManager.cloneNodeWithNewChildren,
131 ).not.toHaveBeenCalled();
132 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
133 1,
134 );
135 expect(
136 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
137 ).not.toHaveBeenCalled();
138
139 // Only call cloneNode for the changed text (and no other properties).
140 await act(() => {
141 ReactFabric.render(<Text foo="b">2</Text>, 11, null, true);
142 });
143 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
144 expect(
145 nativeFabricUIManager.cloneNodeWithNewChildren,
146 ).toHaveBeenCalledTimes(1);
147 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
148 1,
149 );
150 expect(
151 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
152 ).not.toHaveBeenCalled();
153
154 // Call cloneNode for both changed text and properties.
155 await act(() => {
156 ReactFabric.render(<Text foo="c">3</Text>, 11, null, true);
157 });
158 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
159 expect(
160 nativeFabricUIManager.cloneNodeWithNewChildren,
161 ).toHaveBeenCalledTimes(1);
162 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
163 1,
164 );
165 expect(
166 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
167 ).toHaveBeenCalledTimes(1);
168 });
169
170 it('should only pass props diffs to FabricUIManager.cloneNode', async () => {
171 const Text = createReactNativeComponentClass('RCTText', () => ({
172 validAttributes: {foo: true, bar: true},
173 uiViewClassName: 'RCTText',
174 }));
175
176 await act(() => {
177 ReactFabric.render(
178 <Text foo="a" bar="a">
179 1
180 </Text>,
181 11,
182 null,
183 true,
184 );
185 });
186 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
187 expect(
188 nativeFabricUIManager.cloneNodeWithNewChildren,
189 ).not.toHaveBeenCalled();
190 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
191 expect(
192 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
193 ).not.toHaveBeenCalled();
194
195 jest
196 .spyOn(ReactNativePrivateInterface, 'diffAttributePayloads')
197 .mockReturnValue({bar: 'b'});
198
199 await act(() => {
200 ReactFabric.render(
201 <Text foo="a" bar="b">
202 1
203 </Text>,
204 11,
205 null,
206 true,
207 );
208 });
209 expect(
210 nativeFabricUIManager.cloneNodeWithNewProps.mock.calls[0][1],
211 ).toEqual({
212 bar: 'b',
213 });
214 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(`11
215 RCTText {"foo":"a","bar":"b"}
216 RCTRawText {"text":"1"}`);
217
218 jest
219 .spyOn(ReactNativePrivateInterface, 'diffAttributePayloads')
220 .mockReturnValue({foo: 'b'});
221 await act(() => {
222 ReactFabric.render(
223 <Text foo="b" bar="b">
224 2
225 </Text>,
226 11,
227 null,
228 true,
229 );
230 });
231 const argIndex = gate(flags => flags.passChildrenWhenCloningPersistedNodes)
232 ? 2
233 : 1;
234 expect(
235 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps.mock.calls[0][
236 argIndex
237 ],
238 ).toEqual({
239 foo: 'b',
240 });
241 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(`11
242 RCTText {"foo":"b","bar":"b"}
243 RCTRawText {"text":"2"}`);
244 });
245
246 it('should not clone nodes without children when updating props', async () => {
247 const View = createReactNativeComponentClass('RCTView', () => ({
248 validAttributes: {foo: true},
249 uiViewClassName: 'RCTView',
250 }));
251
252 const Component = ({foo}) => (
253 <View>
254 <View foo={foo} />
255 </View>
256 );
257
258 await act(() =>
259 ReactFabric.render(<Component foo={true} />, 11, null, true),
260 );
261 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
262 jest.clearAllMocks();
263
264 await act(() =>
265 ReactFabric.render(<Component foo={false} />, 11, null, true),
266 );
267 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
268 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
269 1,
270 );
271 expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledWith(
272 expect.anything(),
273 {foo: false},
274 );
275
276 expect(
277 nativeFabricUIManager.cloneNodeWithNewChildren,
278 ).toHaveBeenCalledTimes(1);
279 if (gate(flags => flags.passChildrenWhenCloningPersistedNodes)) {
280 expect(
281 nativeFabricUIManager.cloneNodeWithNewChildren,
282 ).toHaveBeenCalledWith(expect.anything(), [
283 expect.objectContaining({props: {foo: false}}),
284 ]);
285 expect(nativeFabricUIManager.appendChild).not.toHaveBeenCalled();
286 } else {
287 expect(
288 nativeFabricUIManager.cloneNodeWithNewChildren,
289 ).toHaveBeenCalledWith(expect.anything());
290 expect(nativeFabricUIManager.appendChild).toHaveBeenCalledTimes(1);
291 }
292 expect(
293 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
294 ).not.toHaveBeenCalled();
295 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
296 });
297
298 it('should not clone nodes when layout effects are used', async () => {
299 const View = createReactNativeComponentClass('RCTView', () => ({
300 validAttributes: {foo: true},
301 uiViewClassName: 'RCTView',
302 }));
303
304 const ComponentWithEffect = () => {
305 React.useLayoutEffect(() => {});
306 return null;
307 };
308
309 await act(() =>
310 ReactFabric.render(
311 <View>
312 <ComponentWithEffect />
313 </View>,
314 11,
315 null,
316 true,
317 ),
318 );
319 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
320 jest.clearAllMocks();
321
322 await act(() =>
323 ReactFabric.render(
324 <View>
325 <ComponentWithEffect />
326 </View>,
327 11,
328 null,
329 true,
330 ),
331 );
332 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
333 expect(
334 nativeFabricUIManager.cloneNodeWithNewChildren,
335 ).not.toHaveBeenCalled();
336 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
337 expect(
338 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
339 ).not.toHaveBeenCalled();
340 expect(nativeFabricUIManager.completeRoot).not.toHaveBeenCalled();
341 });
342
343 it('should not clone nodes when insertion effects are used', async () => {
344 const View = createReactNativeComponentClass('RCTView', () => ({
345 validAttributes: {foo: true},
346 uiViewClassName: 'RCTView',
347 }));
348
349 const ComponentWithRef = () => {
350 React.useInsertionEffect(() => {});
351 return null;
352 };
353
354 await act(() =>
355 ReactFabric.render(
356 <View>
357 <ComponentWithRef />
358 </View>,
359 11,
360 null,
361 true,
362 ),
363 );
364 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
365 jest.clearAllMocks();
366
367 await act(() =>
368 ReactFabric.render(
369 <View>
370 <ComponentWithRef />
371 </View>,
372 11,
373 null,
374 true,
375 ),
376 );
377 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
378 expect(
379 nativeFabricUIManager.cloneNodeWithNewChildren,
380 ).not.toHaveBeenCalled();
381 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
382 expect(
383 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
384 ).not.toHaveBeenCalled();
385 expect(nativeFabricUIManager.completeRoot).not.toHaveBeenCalled();
386 });
387
388 it('should not clone nodes when useImperativeHandle is used', async () => {
389 const View = createReactNativeComponentClass('RCTView', () => ({
390 validAttributes: {foo: true},
391 uiViewClassName: 'RCTView',
392 }));
393
394 const ComponentWithImperativeHandle = props => {
395 React.useImperativeHandle(props.ref, () => ({greet: () => 'hello'}));
396 return null;
397 };
398
399 const ref = React.createRef();
400
401 await act(() =>
402 ReactFabric.render(
403 <View>
404 <ComponentWithImperativeHandle ref={ref} />
405 </View>,
406 11,
407 null,
408 true,
409 ),
410 );
411 expect(nativeFabricUIManager.completeRoot).toHaveBeenCalled();
412 expect(ref.current.greet()).toBe('hello');
413 jest.clearAllMocks();
414
415 await act(() =>
416 ReactFabric.render(
417 <View>
418 <ComponentWithImperativeHandle ref={ref} />
419 </View>,
420 11,
421 null,
422 true,
423 ),
424 );
425 expect(nativeFabricUIManager.cloneNode).not.toHaveBeenCalled();
426 expect(
427 nativeFabricUIManager.cloneNodeWithNewChildren,
428 ).not.toHaveBeenCalled();
429 expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toHaveBeenCalled();
430 expect(
431 nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
432 ).not.toHaveBeenCalled();
433 expect(nativeFabricUIManager.completeRoot).not.toHaveBeenCalled();
434 expect(ref.current.greet()).toBe('hello');
435 });
436
437 it('should call dispatchCommand for native refs', async () => {
438 const View = createReactNativeComponentClass('RCTView', () => ({
439 validAttributes: {foo: true},
440 uiViewClassName: 'RCTView',
441 }));
442
443 nativeFabricUIManager.dispatchCommand.mockClear();
444
445 let viewRef;
446 await act(() => {
447 ReactFabric.render(
448 <View
449 ref={ref => {
450 viewRef = ref;
451 }}
452 />,
453 11,
454 null,
455 true,
456 );
457 });
458
459 expect(nativeFabricUIManager.dispatchCommand).not.toHaveBeenCalled();
460 ReactFabric.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
461 expect(nativeFabricUIManager.dispatchCommand).toHaveBeenCalledTimes(1);
462 expect(nativeFabricUIManager.dispatchCommand).toHaveBeenCalledWith(
463 expect.any(Object),
464 'updateCommand',
465 [10, 20],
466 );
467 });
468
469 it('should warn and no-op if calling dispatchCommand on non native refs', async () => {
470 class BasicClass extends React.Component {
471 render() {
472 return <React.Fragment />;
473 }
474 }
475
476 nativeFabricUIManager.dispatchCommand.mockReset();
477
478 let viewRef;
479 await act(() => {
480 ReactFabric.render(
481 <BasicClass
482 ref={ref => {
483 viewRef = ref;
484 }}
485 />,
486 11,
487 null,
488 true,
489 );
490 });
491
492 expect(nativeFabricUIManager.dispatchCommand).not.toHaveBeenCalled();
493 ReactFabric.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
494 assertConsoleErrorDev([DISPATCH_COMMAND_REQUIRES_HOST_COMPONENT]);
495
496 expect(nativeFabricUIManager.dispatchCommand).not.toHaveBeenCalled();
497 });
498
499 it('should call sendAccessibilityEvent for native refs', async () => {
500 const View = createReactNativeComponentClass('RCTView', () => ({
501 validAttributes: {foo: true},
502 uiViewClassName: 'RCTView',
503 }));
504
505 nativeFabricUIManager.sendAccessibilityEvent.mockClear();
506
507 let viewRef;
508 await act(() => {
509 ReactFabric.render(
510 <View
511 ref={ref => {
512 viewRef = ref;
513 }}
514 />,
515 11,
516 null,
517 true,
518 );
519 });
520
521 expect(nativeFabricUIManager.sendAccessibilityEvent).not.toHaveBeenCalled();
522 ReactFabric.sendAccessibilityEvent(viewRef, 'focus');
523 expect(nativeFabricUIManager.sendAccessibilityEvent).toHaveBeenCalledTimes(
524 1,
525 );
526 expect(nativeFabricUIManager.sendAccessibilityEvent).toHaveBeenCalledWith(
527 expect.any(Object),
528 'focus',
529 );
530 });
531
532 it('should warn and no-op if calling sendAccessibilityEvent on non native refs', async () => {
533 class BasicClass extends React.Component {
534 render() {
535 return <React.Fragment />;
536 }
537 }
538
539 nativeFabricUIManager.sendAccessibilityEvent.mockReset();
540
541 let viewRef;
542 await act(() => {
543 ReactFabric.render(
544 <BasicClass
545 ref={ref => {
546 viewRef = ref;
547 }}
548 />,
549 11,
550 null,
551 true,
552 );
553 });
554
555 expect(nativeFabricUIManager.sendAccessibilityEvent).not.toHaveBeenCalled();
556 ReactFabric.sendAccessibilityEvent(viewRef, 'eventTypeName');
557 assertConsoleErrorDev([SEND_ACCESSIBILITY_EVENT_REQUIRES_HOST_COMPONENT]);
558
559 expect(nativeFabricUIManager.sendAccessibilityEvent).not.toHaveBeenCalled();
560 });
561
562 it('calls the callback with the correct instance and returns null', async () => {
563 const View = createReactNativeComponentClass('RCTView', () => ({
564 validAttributes: {foo: true},
565 uiViewClassName: 'RCTView',
566 }));
567
568 let a;
569 let b;
570 let c;
571 await act(() => {
572 c = ReactFabric.render(
573 <View foo="foo" ref={v => (a = v)} />,
574 11,
575 function () {
576 b = this;
577 },
578 true,
579 );
580 });
581
582 expect(a).toBeTruthy();
583 expect(a).toBe(b);
584 expect(c).toBe(null);
585 });
586
587 // @gate !disableLegacyMode
588 it('returns the instance in legacy mode and calls the callback with it', () => {
589 const View = createReactNativeComponentClass('RCTView', () => ({
590 validAttributes: {foo: true},
591 uiViewClassName: 'RCTView',
592 }));
593
594 let a;
595 let b;
596 const c = ReactFabric.render(
597 <View
598 foo="foo"
599 ref={v => {
600 a = v;
601 }}
602 />,
603 11,
604 function () {
605 b = this;
606 },
607 );
608
609 expect(a).toBeTruthy();
610 expect(a).toBe(b);
611 expect(a).toBe(c);
612 });
613
614 it('renders and reorders children', async () => {
615 const View = createReactNativeComponentClass('RCTView', () => ({
616 validAttributes: {title: true},
617 uiViewClassName: 'RCTView',
618 }));
619
620 class Component extends React.Component {
621 render() {
622 const chars = this.props.chars.split('');
623 return (
624 <View>
625 {chars.map(text => (
626 <View key={text} title={text} />
627 ))}
628 </View>
629 );
630 }
631 }
632
633 // Mini multi-child stress test: lots of reorders, some adds, some removes.
634 const before = 'abcdefghijklmnopqrst';
635 const after = 'mxhpgwfralkeoivcstzy';
636
637 await act(() => {
638 ReactFabric.render(<Component chars={before} />, 11, null, true);
639 });
640 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(`11
641 RCTView {}
642 RCTView {"title":"a"}
643 RCTView {"title":"b"}
644 RCTView {"title":"c"}
645 RCTView {"title":"d"}
646 RCTView {"title":"e"}
647 RCTView {"title":"f"}
648 RCTView {"title":"g"}
649 RCTView {"title":"h"}
650 RCTView {"title":"i"}
651 RCTView {"title":"j"}
652 RCTView {"title":"k"}
653 RCTView {"title":"l"}
654 RCTView {"title":"m"}
655 RCTView {"title":"n"}
656 RCTView {"title":"o"}
657 RCTView {"title":"p"}
658 RCTView {"title":"q"}
659 RCTView {"title":"r"}
660 RCTView {"title":"s"}
661 RCTView {"title":"t"}`);
662
663 await act(() => {
664 ReactFabric.render(<Component chars={after} />, 11, null, true);
665 });
666 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(`11
667 RCTView {}
668 RCTView {"title":"m"}
669 RCTView {"title":"x"}
670 RCTView {"title":"h"}
671 RCTView {"title":"p"}
672 RCTView {"title":"g"}
673 RCTView {"title":"w"}
674 RCTView {"title":"f"}
675 RCTView {"title":"r"}
676 RCTView {"title":"a"}
677 RCTView {"title":"l"}
678 RCTView {"title":"k"}
679 RCTView {"title":"e"}
680 RCTView {"title":"o"}
681 RCTView {"title":"i"}
682 RCTView {"title":"v"}
683 RCTView {"title":"c"}
684 RCTView {"title":"s"}
685 RCTView {"title":"t"}
686 RCTView {"title":"z"}
687 RCTView {"title":"y"}`);
688 });
689
690 it('recreates host parents even if only children changed', async () => {
691 const View = createReactNativeComponentClass('RCTView', () => ({
692 validAttributes: {title: true},
693 uiViewClassName: 'RCTView',
694 }));
695
696 const before = 'abcdefghijklmnopqrst';
697 const after = 'mxhpgwfralkeoivcstzy';
698
699 class Component extends React.Component {
700 state = {
701 chars: before,
702 };
703 render() {
704 const chars = this.state.chars.split('');
705 return (
706 <View>
707 {chars.map(text => (
708 <View key={text} title={text} />
709 ))}
710 </View>
711 );
712 }
713 }
714
715 const ref = React.createRef();
716 // Wrap in a host node.
717 await act(() => {
718 ReactFabric.render(
719 <View>
720 <Component ref={ref} />
721 </View>,
722 11,
723 null,
724 true,
725 );
726 });
727 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(
728 `11
729 RCTView {}
730 RCTView {}
731 RCTView {"title":"a"}
732 RCTView {"title":"b"}
733 RCTView {"title":"c"}
734 RCTView {"title":"d"}
735 RCTView {"title":"e"}
736 RCTView {"title":"f"}
737 RCTView {"title":"g"}
738 RCTView {"title":"h"}
739 RCTView {"title":"i"}
740 RCTView {"title":"j"}
741 RCTView {"title":"k"}
742 RCTView {"title":"l"}
743 RCTView {"title":"m"}
744 RCTView {"title":"n"}
745 RCTView {"title":"o"}
746 RCTView {"title":"p"}
747 RCTView {"title":"q"}
748 RCTView {"title":"r"}
749 RCTView {"title":"s"}
750 RCTView {"title":"t"}`,
751 );
752
753 // Call setState() so that we skip over the top-level host node.
754 // It should still get recreated despite a bailout.
755 await act(() => {
756 ref.current.setState({
757 chars: after,
758 });
759 });
760 expect(nativeFabricUIManager.__dumpHierarchyForJestTestsOnly()).toBe(`11
761 RCTView {}
762 RCTView {}
763 RCTView {"title":"m"}
764 RCTView {"title":"x"}
765 RCTView {"title":"h"}
766 RCTView {"title":"p"}
767 RCTView {"title":"g"}
768 RCTView {"title":"w"}
769 RCTView {"title":"f"}
770 RCTView {"title":"r"}
771 RCTView {"title":"a"}
772 RCTView {"title":"l"}
773 RCTView {"title":"k"}
774 RCTView {"title":"e"}
775 RCTView {"title":"o"}
776 RCTView {"title":"i"}
777 RCTView {"title":"v"}
778 RCTView {"title":"c"}
779 RCTView {"title":"s"}
780 RCTView {"title":"t"}
781 RCTView {"title":"z"}
782 RCTView {"title":"y"}`);
783 });
784
785 it('calls setState with no arguments', async () => {
786 let mockArgs;
787 class Component extends React.Component {
788 componentDidMount() {
789 this.setState({}, (...args) => (mockArgs = args));
790 }
791 render() {
792 return false;
793 }
794 }
795
796 await act(() => {
797 ReactFabric.render(<Component />, 11, null, true);
798 });
799 expect(mockArgs.length).toEqual(0);
800 });
801
802 it('should call complete after inserting children', async () => {
803 const View = createReactNativeComponentClass('RCTView', () => ({
804 validAttributes: {foo: true},
805 uiViewClassName: 'RCTView',
806 }));
807
808 const snapshots = [];
809 nativeFabricUIManager.completeRoot.mockImplementation(
810 function (rootTag, newChildSet) {
811 snapshots.push(
812 nativeFabricUIManager.__dumpChildSetForJestTestsOnly(newChildSet),
813 );
814 },
815 );
816
817 await act(() => {
818 ReactFabric.render(
819 <View foo="a">
820 <View foo="b" />
821 </View>,
822 22,
823 null,
824 true,
825 );
826 });
827 expect(snapshots).toEqual([
828 `RCTView {"foo":"a"}
829 RCTView {"foo":"b"}`,
830 ]);
831 });
832
833 it('should not throw when <View> is used inside of a <Text> ancestor', async () => {
834 const Image = createReactNativeComponentClass('RCTImage', () => ({
835 validAttributes: {},
836 uiViewClassName: 'RCTImage',
837 }));
838 const Text = createReactNativeComponentClass('RCTText', () => ({
839 validAttributes: {},
840 uiViewClassName: 'RCTText',
841 }));
842 const View = createReactNativeComponentClass('RCTView', () => ({
843 validAttributes: {},
844 uiViewClassName: 'RCTView',
845 }));
846
847 await act(() => {
848 ReactFabric.render(
849 <Text>
850 <View />
851 </Text>,
852 11,
853 null,
854 true,
855 );
856 });
857
858 await act(() => {
859 ReactFabric.render(
860 <Text>
861 <Image />
862 </Text>,
863 11,
864 null,
865 true,
866 );
867 });
868 });
869
870 it('should console error for text not inside of a <Text> ancestor', async () => {
871 const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
872 validAttributes: {},
873 uiViewClassName: 'RCTScrollView',
874 }));
875 const Text = createReactNativeComponentClass('RCTText', () => ({
876 validAttributes: {},
877 uiViewClassName: 'RCTText',
878 }));
879 const View = createReactNativeComponentClass('RCTView', () => ({
880 validAttributes: {},
881 uiViewClassName: 'RCTView',
882 }));
883
884 await act(() => {
885 ReactFabric.render(<View>this should warn</View>, 11, null, true);
886 });
887 assertConsoleErrorDev([
888 'Text strings must be rendered within a <Text> component.\n' +
889 ' in RCTView (at **)',
890 ]);
891
892 await act(() => {
893 ReactFabric.render(
894 <Text>
895 <ScrollView>hi hello hi</ScrollView>
896 </Text>,
897 11,
898 null,
899 true,
900 );
901 });
902 assertConsoleErrorDev([
903 'Text strings must be rendered within a <Text> component.\n' +
904 ' in RCTScrollView (at **)',
905 ]);
906 });
907
908 it('should not throw for text inside of an indirect <Text> ancestor', async () => {
909 const Text = createReactNativeComponentClass('RCTText', () => ({
910 validAttributes: {},
911 uiViewClassName: 'RCTText',
912 }));
913
914 const Indirection = () => 'Hi';
915
916 await act(() => {
917 ReactFabric.render(
918 <Text>
919 <Indirection />
920 </Text>,
921 11,
922 null,
923 true,
924 );
925 });
926 });
927
928 it('dispatches events to the last committed props', async () => {
929 const View = createReactNativeComponentClass('RCTView', () => ({
930 validAttributes: {},
931 uiViewClassName: 'RCTView',
932 directEventTypes: {
933 topTouchStart: {
934 registrationName: 'onTouchStart',
935 },
936 },
937 }));
938
939 const touchStart = jest.fn();
940 const touchStart2 = jest.fn();
941
942 await act(() => {
943 ReactFabric.render(<View onTouchStart={touchStart} />, 11, null, true);
944 });
945
946 expect(nativeFabricUIManager.createNode.mock.calls.length).toBe(1);
947 expect(nativeFabricUIManager.registerEventHandler.mock.calls.length).toBe(
948 1,
949 );
950
951 const [, , , , instanceHandle] =
952 nativeFabricUIManager.createNode.mock.calls[0];
953 const [dispatchEvent] =
954 nativeFabricUIManager.registerEventHandler.mock.calls[0];
955
956 const touchEvent = {
957 touches: [],
958 changedTouches: [],
959 };
960
961 expect(touchStart).not.toHaveBeenCalled();
962
963 dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
964
965 expect(touchStart).toHaveBeenCalled();
966 expect(touchStart2).not.toHaveBeenCalled();
967
968 await act(() => {
969 ReactFabric.render(<View onTouchStart={touchStart2} />, 11, null, true);
970 });
971
972 // Intentionally dispatch to the same instanceHandle again.
973 dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
974
975 // The current semantics dictate that we always dispatch to the last committed
976 // props even though the actual scheduling of the event could have happened earlier.
977 // This could change in the future.
978 expect(touchStart2).toHaveBeenCalled();
979 });
980
981 describe('skipBubbling', () => {
982 it('should skip bubbling to ancestor if specified', async () => {
983 const View = createReactNativeComponentClass('RCTView', () => ({
984 validAttributes: {},
985 uiViewClassName: 'RCTView',
986 bubblingEventTypes: {
987 topDefaultBubblingEvent: {
988 phasedRegistrationNames: {
989 captured: 'onDefaultBubblingEventCapture',
990 bubbled: 'onDefaultBubblingEvent',
991 },
992 },
993 topBubblingEvent: {
994 phasedRegistrationNames: {
995 captured: 'onBubblingEventCapture',
996 bubbled: 'onBubblingEvent',
997 skipBubbling: false,
998 },
999 },
1000 topSkipBubblingEvent: {
1001 phasedRegistrationNames: {
1002 captured: 'onSkippedBubblingEventCapture',
1003 bubbled: 'onSkippedBubblingEvent',
1004 skipBubbling: true,
1005 },
1006 },
1007 },
1008 }));
1009 const ancestorBubble = jest.fn();
1010 const ancestorCapture = jest.fn();
1011 const targetBubble = jest.fn();
1012 const targetCapture = jest.fn();
1013
1014 const event = {};
1015
1016 await act(() => {
1017 ReactFabric.render(
1018 <View
1019 onSkippedBubblingEventCapture={ancestorCapture}
1020 onDefaultBubblingEventCapture={ancestorCapture}
1021 onBubblingEventCapture={ancestorCapture}
1022 onSkippedBubblingEvent={ancestorBubble}
1023 onDefaultBubblingEvent={ancestorBubble}
1024 onBubblingEvent={ancestorBubble}>
1025 <View
1026 onSkippedBubblingEventCapture={targetCapture}
1027 onDefaultBubblingEventCapture={targetCapture}
1028 onBubblingEventCapture={targetCapture}
1029 onSkippedBubblingEvent={targetBubble}
1030 onDefaultBubblingEvent={targetBubble}
1031 onBubblingEvent={targetBubble}
1032 />
1033 </View>,
1034 11,
1035 null,
1036 true,
1037 );
1038 });
1039
1040 expect(nativeFabricUIManager.createNode.mock.calls.length).toBe(2);
1041 expect(nativeFabricUIManager.registerEventHandler.mock.calls.length).toBe(
1042 1,
1043 );
1044 const [, , , , childInstance] =
1045 nativeFabricUIManager.createNode.mock.calls[0];
1046 const [dispatchEvent] =
1047 nativeFabricUIManager.registerEventHandler.mock.calls[0];
1048
1049 dispatchEvent(childInstance, 'topDefaultBubblingEvent', event);
1050 expect(targetBubble).toHaveBeenCalledTimes(1);
1051 expect(targetCapture).toHaveBeenCalledTimes(1);
1052 expect(ancestorCapture).toHaveBeenCalledTimes(1);
1053 expect(ancestorBubble).toHaveBeenCalledTimes(1);
1054 ancestorBubble.mockReset();
1055 ancestorCapture.mockReset();
1056 targetBubble.mockReset();
1057 targetCapture.mockReset();
1058
1059 dispatchEvent(childInstance, 'topBubblingEvent', event);
1060 expect(targetBubble).toHaveBeenCalledTimes(1);
1061 expect(targetCapture).toHaveBeenCalledTimes(1);
1062 expect(ancestorCapture).toHaveBeenCalledTimes(1);
1063 expect(ancestorBubble).toHaveBeenCalledTimes(1);
1064 ancestorBubble.mockReset();
1065 ancestorCapture.mockReset();
1066 targetBubble.mockReset();
1067 targetCapture.mockReset();
1068
1069 dispatchEvent(childInstance, 'topSkipBubblingEvent', event);
1070 expect(targetBubble).toHaveBeenCalledTimes(1);
1071 expect(targetCapture).toHaveBeenCalledTimes(1);
1072 expect(ancestorCapture).toHaveBeenCalledTimes(1);
1073 expect(ancestorBubble).not.toHaveBeenCalled();
1074 });
1075 });
1076
1077 it('dispatches event with target as instance', async () => {
1078 const View = createReactNativeComponentClass('RCTView', () => ({
1079 validAttributes: {
1080 id: true,
1081 },
1082 uiViewClassName: 'RCTView',
1083 directEventTypes: {
1084 topTouchStart: {
1085 registrationName: 'onTouchStart',
1086 },
1087 topTouchEnd: {
1088 registrationName: 'onTouchEnd',
1089 },
1090 },
1091 }));
1092
1093 function getViewById(id) {
1094 const [reactTag, , , , instanceHandle] =
1095 nativeFabricUIManager.createNode.mock.calls.find(
1096 args => args[3] && args[3].id === id,
1097 );
1098
1099 return {reactTag, instanceHandle};
1100 }
1101
1102 const ref1 = React.createRef();
1103 const ref2 = React.createRef();
1104
1105 await act(() => {
1106 ReactFabric.render(
1107 <View id="parent">
1108 <View
1109 ref={ref1}
1110 id="one"
1111 onResponderStart={event => {
1112 expect(ref1.current).not.toBeNull();
1113 // Check for referential equality
1114 expect(ref1.current).toBe(event.target);
1115 expect(ref1.current).toBe(event.currentTarget);
1116
1117 expect(global.event).toBe(event);
1118 }}
1119 onStartShouldSetResponder={() => true}
1120 />
1121 <View
1122 ref={ref2}
1123 id="two"
1124 onResponderStart={event => {
1125 expect(ref2.current).not.toBeNull();
1126 // Check for referential equality
1127 expect(ref2.current).toBe(event.target);
1128 expect(ref2.current).toBe(event.currentTarget);
1129
1130 expect(global.event).toBe(event);
1131 }}
1132 onStartShouldSetResponder={() => true}
1133 />
1134 </View>,
1135 1,
1136 null,
1137 true,
1138 );
1139 });
1140
1141 const [dispatchEvent] =
1142 nativeFabricUIManager.registerEventHandler.mock.calls[0];
1143
1144 const preexistingEvent = {};
1145 global.event = preexistingEvent;
1146
1147 dispatchEvent(getViewById('one').instanceHandle, 'topTouchStart', {
1148 target: getViewById('one').reactTag,
1149 identifier: 17,
1150 touches: [],
1151 changedTouches: [],
1152 });
1153 dispatchEvent(getViewById('one').instanceHandle, 'topTouchEnd', {
1154 target: getViewById('one').reactTag,
1155 identifier: 17,
1156 touches: [],
1157 changedTouches: [],
1158 });
1159
1160 dispatchEvent(getViewById('two').instanceHandle, 'topTouchStart', {
1161 target: getViewById('two').reactTag,
1162 identifier: 17,
1163 touches: [],
1164 changedTouches: [],
1165 });
1166
1167 dispatchEvent(getViewById('two').instanceHandle, 'topTouchEnd', {
1168 target: getViewById('two').reactTag,
1169 identifier: 17,
1170 touches: [],
1171 changedTouches: [],
1172 });
1173
1174 expect(global.event).toBe(preexistingEvent);
1175
1176 expect.assertions(9);
1177 });
1178
1179 it('propagates timeStamps from native events and sets defaults', async () => {
1180 const View = createReactNativeComponentClass('RCTView', () => ({
1181 validAttributes: {
1182 id: true,
1183 },
1184 uiViewClassName: 'RCTView',
1185 directEventTypes: {
1186 topTouchStart: {
1187 registrationName: 'onTouchStart',
1188 },
1189 topTouchEnd: {
1190 registrationName: 'onTouchEnd',
1191 },
1192 },
1193 }));
1194
1195 function getViewById(id) {
1196 const [reactTag, , , , instanceHandle] =
1197 nativeFabricUIManager.createNode.mock.calls.find(
1198 args => args[3] && args[3].id === id,
1199 );
1200
1201 return {reactTag, instanceHandle};
1202 }
1203
1204 const ref1 = React.createRef();
1205 const ref2 = React.createRef();
1206
1207 const explicitTimeStampCamelCase = 'explicit-timestamp-camelcase';
1208 const explicitTimeStampLowerCase = 'explicit-timestamp-lowercase';
1209 const performanceNowValue = 'performance-now-timestamp';
1210
1211 jest.spyOn(performance, 'now').mockReturnValue(performanceNowValue);
1212
1213 await act(() => {
1214 ReactFabric.render(
1215 <>
1216 <View
1217 ref={ref1}
1218 id="default"
1219 onTouchEnd={event => {
1220 expect(event.timeStamp).toBe(performanceNowValue);
1221 }}
1222 />
1223 <View
1224 ref={ref2}
1225 id="explicitTimeStampCamelCase"
1226 onTouchEnd={event => {
1227 expect(event.timeStamp).toBe(explicitTimeStampCamelCase);
1228 }}
1229 />
1230 <View
1231 ref={ref2}
1232 id="explicitTimeStampLowerCase"
1233 onTouchEnd={event => {
1234 expect(event.timeStamp).toBe(explicitTimeStampLowerCase);
1235 }}
1236 />
1237 </>,
1238 1,
1239 null,
1240 true,
1241 );
1242 });
1243
1244 const [dispatchEvent] =
1245 nativeFabricUIManager.registerEventHandler.mock.calls[0];
1246
1247 dispatchEvent(getViewById('default').instanceHandle, 'topTouchStart', {
1248 target: getViewById('default').reactTag,
1249 identifier: 17,
1250 touches: [],
1251 changedTouches: [],
1252 });
1253 dispatchEvent(getViewById('default').instanceHandle, 'topTouchEnd', {
1254 target: getViewById('default').reactTag,
1255 identifier: 17,
1256 touches: [],
1257 changedTouches: [],
1258 // No timeStamp property
1259 });
1260
1261 dispatchEvent(
1262 getViewById('explicitTimeStampCamelCase').instanceHandle,
1263 'topTouchStart',
1264 {
1265 target: getViewById('explicitTimeStampCamelCase').reactTag,
1266 identifier: 17,
1267 touches: [],
1268 changedTouches: [],
1269 },
1270 );
1271
1272 dispatchEvent(
1273 getViewById('explicitTimeStampCamelCase').instanceHandle,
1274 'topTouchEnd',
1275 {
1276 target: getViewById('explicitTimeStampCamelCase').reactTag,
1277 identifier: 17,
1278 touches: [],
1279 changedTouches: [],
1280 timeStamp: explicitTimeStampCamelCase,
1281 },
1282 );
1283
1284 dispatchEvent(
1285 getViewById('explicitTimeStampLowerCase').instanceHandle,
1286 'topTouchStart',
1287 {
1288 target: getViewById('explicitTimeStampLowerCase').reactTag,
1289 identifier: 17,
1290 touches: [],
1291 changedTouches: [],
1292 },
1293 );
1294
1295 dispatchEvent(
1296 getViewById('explicitTimeStampLowerCase').instanceHandle,
1297 'topTouchEnd',
1298 {
1299 target: getViewById('explicitTimeStampLowerCase').reactTag,
1300 identifier: 17,
1301 touches: [],
1302 changedTouches: [],
1303 timestamp: explicitTimeStampLowerCase,
1304 },
1305 );
1306
1307 expect.assertions(3);
1308 });
1309
1310 it('findHostInstance_DEPRECATED should warn if used to find a host component inside StrictMode', async () => {
1311 const View = createReactNativeComponentClass('RCTView', () => ({
1312 validAttributes: {foo: true},
1313 uiViewClassName: 'RCTView',
1314 }));
1315
1316 let parent = undefined;
1317 let child = undefined;
1318
1319 class ContainsStrictModeChild extends React.Component {
1320 render() {
1321 return (
1322 <StrictMode>
1323 <View ref={n => (child = n)} />
1324 </StrictMode>
1325 );
1326 }
1327 }
1328
1329 await act(() => {
1330 ReactFabric.render(
1331 <ContainsStrictModeChild ref={n => (parent = n)} />,
1332 11,
1333 null,
1334 true,
1335 );
1336 });
1337
1338 const match = ReactFabric.findHostInstance_DEPRECATED(parent);
1339 assertConsoleErrorDev([
1340 'findHostInstance_DEPRECATED is deprecated in StrictMode. ' +
1341 'findHostInstance_DEPRECATED was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
1342 'Instead, add a ref directly to the element you want to reference. ' +
1343 'Learn more about using refs safely here: ' +
1344 'https://react.dev/link/strict-mode-find-node' +
1345 '\n in RCTView (at **)' +
1346 '\n in ContainsStrictModeChild (at **)',
1347 ]);
1348 expect(match).toBe(child);
1349 });
1350
1351 it('findHostInstance_DEPRECATED should warn if passed a component that is inside StrictMode', async () => {
1352 const View = createReactNativeComponentClass('RCTView', () => ({
1353 validAttributes: {foo: true},
1354 uiViewClassName: 'RCTView',
1355 }));
1356
1357 let parent = undefined;
1358 let child = undefined;
1359
1360 class IsInStrictMode extends React.Component {
1361 render() {
1362 return <View ref={n => (child = n)} />;
1363 }
1364 }
1365
1366 await act(() => {
1367 ReactFabric.render(
1368 <StrictMode>
1369 <IsInStrictMode ref={n => (parent = n)} />
1370 </StrictMode>,
1371 11,
1372 null,
1373 true,
1374 );
1375 });
1376
1377 const match = ReactFabric.findHostInstance_DEPRECATED(parent);
1378 assertConsoleErrorDev([
1379 'findHostInstance_DEPRECATED is deprecated in StrictMode. ' +
1380 'findHostInstance_DEPRECATED was passed an instance of IsInStrictMode which is inside StrictMode. ' +
1381 'Instead, add a ref directly to the element you want to reference. ' +
1382 'Learn more about using refs safely here: ' +
1383 'https://react.dev/link/strict-mode-find-node' +
1384 '\n in RCTView (at **)' +
1385 '\n in IsInStrictMode (at **)',
1386 ]);
1387 expect(match).toBe(child);
1388 });
1389
1390 it('findNodeHandle should warn if used to find a host component inside StrictMode', async () => {
1391 const View = createReactNativeComponentClass('RCTView', () => ({
1392 validAttributes: {foo: true},
1393 uiViewClassName: 'RCTView',
1394 }));
1395
1396 let parent = undefined;
1397 let child = undefined;
1398
1399 class ContainsStrictModeChild extends React.Component {
1400 render() {
1401 return (
1402 <StrictMode>
1403 <View ref={n => (child = n)} />
1404 </StrictMode>
1405 );
1406 }
1407 }
1408
1409 await act(() => {
1410 ReactFabric.render(
1411 <ContainsStrictModeChild ref={n => (parent = n)} />,
1412 11,
1413 null,
1414 true,
1415 );
1416 });
1417
1418 const match = ReactFabric.findNodeHandle(parent);
1419 assertConsoleErrorDev([
1420 'findNodeHandle is deprecated in StrictMode. ' +
1421 'findNodeHandle was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
1422 'Instead, add a ref directly to the element you want to reference. ' +
1423 'Learn more about using refs safely here: ' +
1424 'https://react.dev/link/strict-mode-find-node' +
1425 '\n in RCTView (at **)' +
1426 '\n in ContainsStrictModeChild (at **)',
1427 ]);
1428 expect(match).toBe(
1429 ReactNativePrivateInterface.getNativeTagFromPublicInstance(child),
1430 );
1431 });
1432
1433 it('findNodeHandle should warn if passed a component that is inside StrictMode', async () => {
1434 const View = createReactNativeComponentClass('RCTView', () => ({
1435 validAttributes: {foo: true},
1436 uiViewClassName: 'RCTView',
1437 }));
1438
1439 let parent = undefined;
1440 let child = undefined;
1441
1442 class IsInStrictMode extends React.Component {
1443 render() {
1444 return <View ref={n => (child = n)} />;
1445 }
1446 }
1447
1448 await act(() => {
1449 ReactFabric.render(
1450 <StrictMode>
1451 <IsInStrictMode ref={n => (parent = n)} />
1452 </StrictMode>,
1453 11,
1454 null,
1455 true,
1456 );
1457 });
1458
1459 const match = ReactFabric.findNodeHandle(parent);
1460 assertConsoleErrorDev([
1461 'findNodeHandle is deprecated in StrictMode. ' +
1462 'findNodeHandle was passed an instance of IsInStrictMode which is inside StrictMode. ' +
1463 'Instead, add a ref directly to the element you want to reference. ' +
1464 'Learn more about using refs safely here: ' +
1465 'https://react.dev/link/strict-mode-find-node' +
1466 '\n in RCTView (at **)' +
1467 '\n in IsInStrictMode (at **)',
1468 ]);
1469 expect(match).toBe(
1470 ReactNativePrivateInterface.getNativeTagFromPublicInstance(child),
1471 );
1472 });
1473
1474 it('findNodeHandle errors when called from render', async () => {
1475 class TestComponent extends React.Component {
1476 render() {
1477 ReactFabric.findNodeHandle(this);
1478 return null;
1479 }
1480 }
1481 await act(() => {
1482 ReactFabric.render(<TestComponent />, 11, null, true);
1483 });
1484 assertConsoleErrorDev([
1485 'TestComponent is accessing findNodeHandle inside its render(). ' +
1486 'render() should be a pure function of props and state. It should ' +
1487 'never access something that requires stale data from the previous ' +
1488 'render, such as refs. Move this logic to componentDidMount and ' +
1489 'componentDidUpdate instead.\n' +
1490 ' in TestComponent (at **)',
1491 ]);
1492 });
1493
1494 it("findNodeHandle doesn't error when called outside render", async () => {
1495 class TestComponent extends React.Component {
1496 render() {
1497 return null;
1498 }
1499 componentDidMount() {
1500 ReactFabric.findNodeHandle(this);
1501 }
1502 }
1503 await act(() => {
1504 ReactFabric.render(<TestComponent />, 11, null, true);
1505 });
1506 });
1507
1508 it('should no-op if calling sendAccessibilityEvent on unmounted refs', async () => {
1509 const View = createReactNativeComponentClass('RCTView', () => ({
1510 validAttributes: {foo: true},
1511 uiViewClassName: 'RCTView',
1512 }));
1513
1514 nativeFabricUIManager.sendAccessibilityEvent.mockReset();
1515
1516 let viewRef;
1517 await act(() => {
1518 ReactFabric.render(
1519 <View
1520 ref={ref => {
1521 viewRef = ref;
1522 }}
1523 />,
1524 11,
1525 null,
1526 true,
1527 );
1528 });
1529 const dangerouslyRetainedViewRef = viewRef;
1530 await act(() => {
1531 ReactFabric.stopSurface(11);
1532 });
1533
1534 ReactFabric.sendAccessibilityEvent(
1535 dangerouslyRetainedViewRef,
1536 'eventTypeName',
1537 );
1538 assertConsoleErrorDev([
1539 "sendAccessibilityEvent was called with a ref that isn't a " +
1540 'native component. Use React.forwardRef to get access to the underlying native component',
1541 ]);
1542
1543 expect(nativeFabricUIManager.sendAccessibilityEvent).not.toHaveBeenCalled();
1544 });
1545
1546 it('getNodeFromInternalInstanceHandle should return the correct shadow node', async () => {
1547 const View = createReactNativeComponentClass('RCTView', () => ({
1548 validAttributes: {foo: true},
1549 uiViewClassName: 'RCTView',
1550 }));
1551
1552 await act(() => {
1553 ReactFabric.render(<View foo="test" />, 1, null, true);
1554 });
1555
1556 const internalInstanceHandle =
1557 nativeFabricUIManager.createNode.mock.calls[0][4];
1558 expect(internalInstanceHandle).toEqual(expect.any(Object));
1559
1560 const expectedShadowNode =
1561 nativeFabricUIManager.createNode.mock.results[0].value;
1562 expect(expectedShadowNode).toEqual(expect.any(Object));
1563
1564 const node = ReactFabric.getNodeFromInternalInstanceHandle(
1565 internalInstanceHandle,
1566 );
1567 expect(node).toBe(expectedShadowNode);
1568 });
1569
1570 it('getPublicInstanceFromInternalInstanceHandle should provide public instances for HostComponent', async () => {
1571 const View = createReactNativeComponentClass('RCTView', () => ({
1572 validAttributes: {foo: true},
1573 uiViewClassName: 'RCTView',
1574 }));
1575
1576 let viewRef;
1577 await act(() => {
1578 ReactFabric.render(
1579 <View
1580 foo="test"
1581 ref={ref => {
1582 viewRef = ref;
1583 }}
1584 />,
1585 1,
1586 null,
1587 true,
1588 );
1589 });
1590
1591 const internalInstanceHandle =
1592 nativeFabricUIManager.createNode.mock.calls[0][4];
1593 expect(internalInstanceHandle).toEqual(expect.any(Object));
1594
1595 const publicInstance =
1596 ReactFabric.getPublicInstanceFromInternalInstanceHandle(
1597 internalInstanceHandle,
1598 );
1599 expect(publicInstance).toBe(viewRef);
1600
1601 await act(() => {
1602 ReactFabric.render(null, 1, null, true);
1603 });
1604
1605 const publicInstanceAfterUnmount =
1606 ReactFabric.getPublicInstanceFromInternalInstanceHandle(
1607 internalInstanceHandle,
1608 );
1609 expect(publicInstanceAfterUnmount).toBe(null);
1610 });
1611
1612 it('getPublicInstanceFromInternalInstanceHandle should provide public instances for HostText', async () => {
1613 jest.spyOn(ReactNativePrivateInterface, 'createPublicTextInstance');
1614
1615 const RCTText = createReactNativeComponentClass('RCTText', () => ({
1616 validAttributes: {},
1617 uiViewClassName: 'RCTText',
1618 }));
1619
1620 await act(() => {
1621 ReactFabric.render(<RCTText>Text content</RCTText>, 1, null, true);
1622 });
1623
1624 // Access the internal instance handle used to create the text node.
1625 const internalInstanceHandle =
1626 nativeFabricUIManager.createNode.mock.calls[0][4];
1627 expect(internalInstanceHandle).toEqual(expect.any(Object));
1628
1629 // Text public instances should be created lazily.
1630 expect(
1631 ReactNativePrivateInterface.createPublicTextInstance,
1632 ).not.toHaveBeenCalled();
1633
1634 const publicInstance =
1635 ReactFabric.getPublicInstanceFromInternalInstanceHandle(
1636 internalInstanceHandle,
1637 );
1638
1639 // We just requested the text public instance, so it should have been created at this point.
1640 expect(
1641 ReactNativePrivateInterface.createPublicTextInstance,
1642 ).toHaveBeenCalledTimes(1);
1643 expect(
1644 ReactNativePrivateInterface.createPublicTextInstance,
1645 ).toHaveBeenCalledWith(internalInstanceHandle);
1646
1647 const expectedPublicInstance =
1648 ReactNativePrivateInterface.createPublicTextInstance.mock.results[0]
1649 .value;
1650 expect(publicInstance).toBe(expectedPublicInstance);
1651
1652 await act(() => {
1653 ReactFabric.render(null, 1, null, true);
1654 });
1655
1656 const publicInstanceAfterUnmount =
1657 ReactFabric.getPublicInstanceFromInternalInstanceHandle(
1658 internalInstanceHandle,
1659 );
1660
1661 expect(publicInstanceAfterUnmount).toBe(null);
1662 });
1663 });