main
js 356 lines 8.01 KB
Raw
1 let React;
2 let Activity;
3 let ReactNoop;
4 let act;
5 let log;
6
7 describe('Activity StrictMode', () => {
8 beforeEach(() => {
9 jest.resetModules();
10 log = [];
11
12 React = require('react');
13 Activity = React.Activity;
14 ReactNoop = require('react-noop-renderer');
15 act = require('internal-test-utils').act;
16 });
17
18 function Component({label}) {
19 React.useEffect(() => {
20 log.push(`${label}: useEffect mount`);
21 return () => log.push(`${label}: useEffect unmount`);
22 });
23
24 React.useLayoutEffect(() => {
25 log.push(`${label}: useLayoutEffect mount`);
26 return () => log.push(`${label}: useLayoutEffect unmount`);
27 });
28
29 log.push(`${label}: render`);
30
31 return <span>label</span>;
32 }
33
34 // @gate __DEV__
35 it('should trigger strict effects when offscreen is visible', async () => {
36 await act(() => {
37 ReactNoop.render(
38 <React.StrictMode>
39 <Activity mode="visible">
40 <Component label="A" />
41 </Activity>
42 </React.StrictMode>,
43 );
44 });
45
46 expect(log).toEqual([
47 'A: render',
48 'A: render',
49 'A: useLayoutEffect mount',
50 'A: useEffect mount',
51 'A: useLayoutEffect unmount',
52 'A: useEffect unmount',
53 'A: useLayoutEffect mount',
54 'A: useEffect mount',
55 ]);
56 });
57
58 // @gate __DEV__
59 it('should not trigger strict effects when offscreen is hidden', async () => {
60 await act(() => {
61 ReactNoop.render(
62 <React.StrictMode>
63 <Activity mode="hidden">
64 <Component label="A" />
65 </Activity>
66 </React.StrictMode>,
67 );
68 });
69
70 expect(log).toEqual(['A: render', 'A: render']);
71
72 log = [];
73
74 await act(() => {
75 ReactNoop.render(
76 <React.StrictMode>
77 <Activity mode="hidden">
78 <Component label="A" />
79 <Component label="B" />
80 </Activity>
81 </React.StrictMode>,
82 );
83 });
84
85 expect(log).toEqual(['A: render', 'A: render', 'B: render', 'B: render']);
86
87 log = [];
88
89 await act(() => {
90 ReactNoop.render(
91 <React.StrictMode>
92 <Activity mode="visible">
93 <Component label="A" />
94 </Activity>
95 </React.StrictMode>,
96 );
97 });
98
99 expect(log).toEqual([
100 'A: render',
101 'A: render',
102 'A: useLayoutEffect mount',
103 'A: useEffect mount',
104 'A: useLayoutEffect unmount',
105 'A: useEffect unmount',
106 'A: useLayoutEffect mount',
107 'A: useEffect mount',
108 ]);
109
110 log = [];
111
112 await act(() => {
113 ReactNoop.render(
114 <React.StrictMode>
115 <Activity mode="hidden">
116 <Component label="A" />
117 </Activity>
118 </React.StrictMode>,
119 );
120 });
121
122 expect(log).toEqual([
123 'A: useLayoutEffect unmount',
124 'A: useEffect unmount',
125 'A: render',
126 'A: render',
127 ]);
128 });
129
130 it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', async () => {
131 // This is a regression test, see https://github.com/facebook/react/pull/25179 for more details.
132 function App() {
133 const [state, setState] = React.useState(false);
134
135 React.useLayoutEffect(() => {
136 setState(true);
137 }, []);
138
139 React.useEffect(() => {
140 // Empty useEffect with empty dependency array is needed to trigger infinite render loop.
141 }, []);
142
143 return state;
144 }
145
146 await act(() => {
147 ReactNoop.render(
148 <React.StrictMode>
149 <React.Suspense>
150 <App />
151 </React.Suspense>
152 </React.StrictMode>,
153 );
154 });
155 });
156
157 // @gate __DEV__
158 it('should double invoke effects on unsuspended child', async () => {
159 let shouldSuspend = true;
160 let resolve;
161 const suspensePromise = new Promise(_resolve => {
162 resolve = _resolve;
163 });
164
165 function Parent() {
166 log.push('Parent rendered');
167 React.useEffect(() => {
168 log.push('Parent mount');
169 return () => {
170 log.push('Parent unmount');
171 };
172 });
173
174 return (
175 <React.Suspense fallback="fallback">
176 <Child />
177 </React.Suspense>
178 );
179 }
180
181 function Child() {
182 log.push('Child rendered');
183 React.useEffect(() => {
184 log.push('Child mount');
185 return () => {
186 log.push('Child unmount');
187 };
188 });
189 if (shouldSuspend) {
190 log.push('Child suspended');
191 throw suspensePromise;
192 }
193 return null;
194 }
195
196 await act(() => {
197 ReactNoop.render(
198 <React.StrictMode>
199 <Activity mode="visible">
200 <Parent />
201 </Activity>
202 </React.StrictMode>,
203 );
204 });
205
206 log.push('------------------------------');
207
208 await act(() => {
209 resolve();
210 shouldSuspend = false;
211 });
212
213 expect(log).toEqual([
214 'Parent rendered',
215 'Parent rendered',
216 'Child rendered',
217 'Child suspended',
218 'Parent mount',
219 'Parent unmount',
220 'Parent mount',
221 // pre-warming
222 'Child rendered',
223 'Child suspended',
224 // end pre-warming
225 '------------------------------',
226 'Child rendered',
227 'Child rendered',
228 'Child mount',
229 'Child unmount',
230 'Child mount',
231 ]);
232 });
233
234 // @gate __DEV__
235 it('should double invoke effects on newly inserted children while Activity becomes visible', async () => {
236 function Parent({children}) {
237 log.push('Parent rendered');
238 React.useEffect(() => {
239 log.push('Parent mount');
240 return () => {
241 log.push('Parent unmount');
242 };
243 });
244
245 return <div>{children}</div>;
246 }
247
248 function Child({name}) {
249 log.push(`Child ${name} rendered`);
250 React.useEffect(() => {
251 log.push(`Child ${name} mount`);
252 return () => {
253 log.push(`Child ${name} unmount`);
254 };
255 });
256
257 return null;
258 }
259
260 await act(() => {
261 ReactNoop.render(
262 <React.StrictMode>
263 <Activity mode="hidden">
264 <Parent />
265 </Activity>
266 </React.StrictMode>,
267 );
268 });
269
270 expect(log).toEqual(['Parent rendered', 'Parent rendered']);
271
272 log.length = 0;
273 await act(() => {
274 ReactNoop.render(
275 <React.StrictMode>
276 <Activity mode="visible">
277 <Parent>
278 <Child name="one" />
279 </Parent>
280 </Activity>
281 </React.StrictMode>,
282 );
283 });
284
285 expect(log).toEqual([
286 'Parent rendered',
287 'Parent rendered',
288 'Child one rendered',
289 'Child one rendered',
290 'Child one mount',
291 'Parent mount',
292 // StrictMode double invocation
293 'Parent unmount',
294 'Child one unmount',
295 'Child one mount',
296 'Parent mount',
297 ]);
298
299 log.length = 0;
300 await act(() => {
301 ReactNoop.render(
302 <React.StrictMode>
303 <Activity mode="visible">
304 <Parent>
305 <Child name="one" />
306 </Parent>
307 </Activity>
308 </React.StrictMode>,
309 );
310 });
311
312 expect(log).toEqual([
313 'Parent rendered',
314 'Parent rendered',
315 'Child one rendered',
316 'Child one rendered',
317 // single Effect invocation. No double invocation on update.
318 'Child one unmount',
319 'Parent unmount',
320 'Child one mount',
321 'Parent mount',
322 ]);
323
324 log.length = 0;
325 await act(() => {
326 ReactNoop.render(
327 <React.StrictMode>
328 <Activity mode="visible">
329 <Parent>
330 <Child name="one" />
331 <Child name="two" />
332 </Parent>
333 </Activity>
334 </React.StrictMode>,
335 );
336 });
337
338 expect(log).toEqual([
339 'Parent rendered',
340 'Parent rendered',
341 'Child one rendered',
342 'Child one rendered',
343 'Child two rendered',
344 'Child two rendered',
345 // single Effect invocation for existing Components.
346 'Child one unmount',
347 'Parent unmount',
348 'Child one mount',
349 'Child two mount',
350 'Parent mount',
351 // Double Effect invocation for new Component "two"
352 'Child two unmount',
353 'Child two mount',
354 ]);
355 });
356 });