main
js 228 lines 5.57 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 describe('forwardRef', () => {
13 let React;
14 let ReactNoop;
15 let Scheduler;
16 let waitForAll;
17
18 beforeEach(() => {
19 jest.resetModules();
20
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
24
25 const InternalTestUtils = require('internal-test-utils');
26 waitForAll = InternalTestUtils.waitForAll;
27 });
28
29 it('should work without a ref to be forwarded', async () => {
30 class Child extends React.Component {
31 render() {
32 Scheduler.log(this.props.value);
33 return null;
34 }
35 }
36
37 function Wrapper(props) {
38 return <Child {...props} ref={props.forwardedRef} />;
39 }
40
41 const RefForwardingComponent = React.forwardRef((props, ref) => (
42 <Wrapper {...props} forwardedRef={ref} />
43 ));
44
45 ReactNoop.render(<RefForwardingComponent value={123} />);
46 await waitForAll([123]);
47 });
48
49 it('should forward a ref for a single child', async () => {
50 class Child extends React.Component {
51 render() {
52 Scheduler.log(this.props.value);
53 return null;
54 }
55 }
56
57 function Wrapper(props) {
58 return <Child {...props} ref={props.forwardedRef} />;
59 }
60
61 const RefForwardingComponent = React.forwardRef((props, ref) => (
62 <Wrapper {...props} forwardedRef={ref} />
63 ));
64
65 const ref = React.createRef();
66
67 ReactNoop.render(<RefForwardingComponent ref={ref} value={123} />);
68 await waitForAll([123]);
69 expect(ref.current instanceof Child).toBe(true);
70 });
71
72 it('should forward a ref for multiple children', async () => {
73 class Child extends React.Component {
74 render() {
75 Scheduler.log(this.props.value);
76 return null;
77 }
78 }
79
80 function Wrapper(props) {
81 return <Child {...props} ref={props.forwardedRef} />;
82 }
83
84 const RefForwardingComponent = React.forwardRef((props, ref) => (
85 <Wrapper {...props} forwardedRef={ref} />
86 ));
87
88 const ref = React.createRef();
89
90 ReactNoop.render(
91 <div>
92 <div />
93 <RefForwardingComponent ref={ref} value={123} />
94 <div />
95 </div>,
96 );
97 await waitForAll([123]);
98 expect(ref.current instanceof Child).toBe(true);
99 });
100
101 it('should maintain child instance and ref through updates', async () => {
102 class Child extends React.Component {
103 constructor(props) {
104 super(props);
105 }
106 render() {
107 Scheduler.log(this.props.value);
108 return null;
109 }
110 }
111
112 function Wrapper(props) {
113 return <Child {...props} ref={props.forwardedRef} />;
114 }
115
116 const RefForwardingComponent = React.forwardRef((props, ref) => (
117 <Wrapper {...props} forwardedRef={ref} />
118 ));
119
120 let setRefCount = 0;
121 let ref;
122
123 const setRef = r => {
124 setRefCount++;
125 ref = r;
126 };
127
128 ReactNoop.render(<RefForwardingComponent ref={setRef} value={123} />);
129 await waitForAll([123]);
130 expect(ref instanceof Child).toBe(true);
131 expect(setRefCount).toBe(1);
132 ReactNoop.render(<RefForwardingComponent ref={setRef} value={456} />);
133 await waitForAll([456]);
134 expect(ref instanceof Child).toBe(true);
135 expect(setRefCount).toBe(1);
136 });
137
138 it('should not break lifecycle error handling', async () => {
139 class ErrorBoundary extends React.Component {
140 state = {error: null};
141 componentDidCatch(error) {
142 Scheduler.log('ErrorBoundary.componentDidCatch');
143 this.setState({error});
144 }
145 render() {
146 if (this.state.error) {
147 Scheduler.log('ErrorBoundary.render: catch');
148 return null;
149 }
150 Scheduler.log('ErrorBoundary.render: try');
151 return this.props.children;
152 }
153 }
154
155 class BadRender extends React.Component {
156 render() {
157 Scheduler.log('BadRender throw');
158 throw new Error('oops!');
159 }
160 }
161
162 function Wrapper(props) {
163 const forwardedRef = props.forwardedRef;
164 Scheduler.log('Wrapper');
165 return <BadRender {...props} ref={forwardedRef} />;
166 }
167
168 const RefForwardingComponent = React.forwardRef((props, ref) => (
169 <Wrapper {...props} forwardedRef={ref} />
170 ));
171
172 const ref = React.createRef();
173
174 ReactNoop.render(
175 <ErrorBoundary>
176 <RefForwardingComponent ref={ref} />
177 </ErrorBoundary>,
178 );
179 await waitForAll([
180 'ErrorBoundary.render: try',
181 'Wrapper',
182 'BadRender throw',
183
184 // React retries one more time
185 'ErrorBoundary.render: try',
186 'Wrapper',
187 'BadRender throw',
188
189 // Errored again on retry. Now handle it.
190 'ErrorBoundary.componentDidCatch',
191 'ErrorBoundary.render: catch',
192 ]);
193 expect(ref.current).toBe(null);
194 });
195
196 it('should not re-run the render callback on a deep setState', async () => {
197 let inst;
198
199 class Inner extends React.Component {
200 render() {
201 Scheduler.log('Inner');
202 inst = this;
203 return <div ref={this.props.forwardedRef} />;
204 }
205 }
206
207 function Middle(props) {
208 Scheduler.log('Middle');
209 return <Inner {...props} />;
210 }
211
212 const Forward = React.forwardRef((props, ref) => {
213 Scheduler.log('Forward');
214 return <Middle {...props} forwardedRef={ref} />;
215 });
216
217 function App() {
218 Scheduler.log('App');
219 return <Forward />;
220 }
221
222 ReactNoop.render(<App />);
223 await waitForAll(['App', 'Forward', 'Middle', 'Inner']);
224
225 inst.setState({});
226 await waitForAll(['Inner']);
227 });
228 });