main
js 539 lines 17.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 let React;
13 let ReactFeatureFlags;
14 let ReactDOMServer;
15 let act;
16
17 describe('ReactScope', () => {
18 beforeEach(() => {
19 jest.resetModules();
20 ReactFeatureFlags = require('shared/ReactFeatureFlags');
21 ReactFeatureFlags.enableScopeAPI = true;
22 React = require('react');
23
24 const InternalTestUtils = require('internal-test-utils');
25 act = InternalTestUtils.act;
26 });
27
28 describe('ReactDOM', () => {
29 let ReactDOMClient;
30 let container;
31
32 beforeEach(() => {
33 ReactDOMClient = require('react-dom/client');
34 ReactDOMServer = require('react-dom/server');
35 container = document.createElement('div');
36 document.body.appendChild(container);
37 });
38
39 afterEach(() => {
40 document.body.removeChild(container);
41 container = null;
42 });
43
44 // @gate enableScopeAPI
45 it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
46 const testScopeQuery = (type, props) => true;
47 const TestScope = React.unstable_Scope;
48 const scopeRef = React.createRef();
49 const divRef = React.createRef();
50 const spanRef = React.createRef();
51 const aRef = React.createRef();
52
53 function Test({toggle}) {
54 return toggle ? (
55 <TestScope ref={scopeRef}>
56 <div ref={divRef}>DIV</div>
57 <span ref={spanRef}>SPAN</span>
58 <a ref={aRef}>A</a>
59 </TestScope>
60 ) : (
61 <TestScope ref={scopeRef}>
62 <a ref={aRef}>A</a>
63 <div ref={divRef}>DIV</div>
64 <span ref={spanRef}>SPAN</span>
65 </TestScope>
66 );
67 }
68
69 const root = ReactDOMClient.createRoot(container);
70 await act(() => {
71 root.render(<Test toggle={true} />);
72 });
73
74 let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
75 expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
76 await act(() => {
77 root.render(<Test toggle={false} />);
78 });
79
80 nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
81 expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
82 await act(() => {
83 root.render(null);
84 });
85
86 expect(scopeRef.current).toBe(null);
87 });
88
89 // @gate enableScopeAPI
90 it('DO_NOT_USE_queryAllNodes() provides the correct host instance', async () => {
91 const testScopeQuery = (type, props) => type === 'div';
92 const TestScope = React.unstable_Scope;
93 const scopeRef = React.createRef();
94 const divRef = React.createRef();
95 const spanRef = React.createRef();
96 const aRef = React.createRef();
97
98 function Test({toggle}) {
99 return toggle ? (
100 <TestScope ref={scopeRef}>
101 <div ref={divRef}>DIV</div>
102 <span ref={spanRef}>SPAN</span>
103 <a ref={aRef}>A</a>
104 </TestScope>
105 ) : (
106 <TestScope ref={scopeRef}>
107 <a ref={aRef}>A</a>
108 <div ref={divRef}>DIV</div>
109 <span ref={spanRef}>SPAN</span>
110 </TestScope>
111 );
112 }
113
114 const root = ReactDOMClient.createRoot(container);
115 await act(() => {
116 root.render(<Test toggle={true} />);
117 });
118
119 let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
120 expect(nodes).toEqual([divRef.current]);
121 let filterQuery = (type, props, instance) =>
122 instance === spanRef.current || testScopeQuery(type, props);
123 nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
124 expect(nodes).toEqual([divRef.current, spanRef.current]);
125 filterQuery = (type, props, instance) =>
126 [spanRef.current, aRef.current].includes(instance) ||
127 testScopeQuery(type, props);
128 nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
129 expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
130 await act(() => {
131 root.render(<Test toggle={false} />);
132 });
133
134 filterQuery = (type, props, instance) =>
135 [spanRef.current, aRef.current].includes(instance) ||
136 testScopeQuery(type, props);
137 nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
138 expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
139 await act(() => {
140 root.render(null);
141 });
142
143 expect(scopeRef.current).toBe(null);
144 });
145
146 // @gate enableScopeAPI
147 it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
148 const testScopeQuery = (type, props) => true;
149 const TestScope = React.unstable_Scope;
150 const scopeRef = React.createRef();
151 const divRef = React.createRef();
152 const spanRef = React.createRef();
153 const aRef = React.createRef();
154
155 function Test({toggle}) {
156 return toggle ? (
157 <TestScope ref={scopeRef}>
158 <div ref={divRef}>DIV</div>
159 <span ref={spanRef}>SPAN</span>
160 <a ref={aRef}>A</a>
161 </TestScope>
162 ) : (
163 <TestScope ref={scopeRef}>
164 <a ref={aRef}>A</a>
165 <div ref={divRef}>DIV</div>
166 <span ref={spanRef}>SPAN</span>
167 </TestScope>
168 );
169 }
170
171 const root = ReactDOMClient.createRoot(container);
172 await act(() => {
173 root.render(<Test toggle={true} />);
174 });
175
176 let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
177 expect(node).toEqual(divRef.current);
178 await act(() => {
179 root.render(<Test toggle={false} />);
180 });
181
182 node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
183 expect(node).toEqual(aRef.current);
184 await act(() => {
185 root.render(null);
186 });
187
188 expect(scopeRef.current).toBe(null);
189 });
190
191 // @gate enableScopeAPI
192 it('containsNode() works as intended', async () => {
193 const TestScope = React.unstable_Scope;
194 const scopeRef = React.createRef();
195 const divRef = React.createRef();
196 const spanRef = React.createRef();
197 const aRef = React.createRef();
198 const outerSpan = React.createRef();
199 const emRef = React.createRef();
200
201 function Test({toggle}) {
202 return toggle ? (
203 <div>
204 <span ref={outerSpan}>SPAN</span>
205 <TestScope ref={scopeRef}>
206 <div ref={divRef}>DIV</div>
207 <span ref={spanRef}>SPAN</span>
208 <a ref={aRef}>A</a>
209 </TestScope>
210 <em ref={emRef}>EM</em>
211 </div>
212 ) : (
213 <div>
214 <TestScope ref={scopeRef}>
215 <a ref={aRef}>A</a>
216 <div ref={divRef}>DIV</div>
217 <span ref={spanRef}>SPAN</span>
218 <em ref={emRef}>EM</em>
219 </TestScope>
220 <span ref={outerSpan}>SPAN</span>
221 </div>
222 );
223 }
224
225 const root = ReactDOMClient.createRoot(container);
226 await act(() => {
227 root.render(<Test toggle={true} />);
228 });
229
230 expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
231 expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
232 expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
233 expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
234 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
235 await act(() => {
236 root.render(<Test toggle={false} />);
237 });
238
239 expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
240 expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
241 expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
242 expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
243 expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
244 await act(() => {
245 root.render(<Test toggle={true} />);
246 });
247
248 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
249 });
250
251 // @gate enableScopeAPI
252 it('scopes support server-side rendering and hydration', async () => {
253 const TestScope = React.unstable_Scope;
254 const scopeRef = React.createRef();
255 const divRef = React.createRef();
256 const spanRef = React.createRef();
257 const aRef = React.createRef();
258
259 function Test({toggle}) {
260 return (
261 <div>
262 <TestScope ref={scopeRef}>
263 <div ref={divRef}>DIV</div>
264 <span ref={spanRef}>SPAN</span>
265 <a ref={aRef}>A</a>
266 </TestScope>
267 <div>Outside content!</div>
268 </div>
269 );
270 }
271 const html = ReactDOMServer.renderToString(<Test />);
272 expect(html).toBe(
273 '<div><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
274 );
275 container.innerHTML = html;
276 await act(() => {
277 ReactDOMClient.hydrateRoot(container, <Test />);
278 });
279 const testScopeQuery = (type, props) => true;
280 const nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
281 expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
282 });
283
284 // @gate enableScopeAPI
285 it('getChildContextValues() works as intended', async () => {
286 const TestContext = React.createContext();
287 const TestScope = React.unstable_Scope;
288 const scopeRef = React.createRef();
289
290 function Test({toggle}) {
291 return toggle ? (
292 <TestScope ref={scopeRef}>
293 <TestContext.Provider value={1} />
294 </TestScope>
295 ) : (
296 <TestScope ref={scopeRef}>
297 <TestContext.Provider value={1} />
298 <TestContext.Provider value={2} />
299 </TestScope>
300 );
301 }
302
303 const root = ReactDOMClient.createRoot(container);
304 await act(() => {
305 root.render(<Test toggle={true} />);
306 });
307
308 let nodes = scopeRef.current.getChildContextValues(TestContext);
309 expect(nodes).toEqual([1]);
310 await act(() => {
311 root.render(<Test toggle={false} />);
312 });
313
314 nodes = scopeRef.current.getChildContextValues(TestContext);
315 expect(nodes).toEqual([1, 2]);
316 await act(() => {
317 root.render(null);
318 });
319
320 expect(scopeRef.current).toBe(null);
321 });
322
323 // @gate enableScopeAPI
324 it('correctly works with suspended boundaries that are hydrated', async () => {
325 let suspend = false;
326 let resolve;
327 const promise = new Promise(resolvePromise => (resolve = resolvePromise));
328 const ref = React.createRef();
329 const TestScope = React.unstable_Scope;
330 const scopeRef = React.createRef();
331 const testScopeQuery = (type, props) => true;
332
333 function Child() {
334 if (suspend) {
335 throw promise;
336 } else {
337 return 'Hello';
338 }
339 }
340
341 function App() {
342 return (
343 <div>
344 <TestScope ref={scopeRef}>
345 <React.Suspense fallback="Loading...">
346 <span ref={ref}>
347 <Child />
348 </span>
349 </React.Suspense>
350 </TestScope>
351 </div>
352 );
353 }
354
355 // First we render the final HTML. With the streaming renderer
356 // this may have suspense points on the server but here we want
357 // to test the completed HTML. Don't suspend on the server.
358 suspend = false;
359 const finalHTML = ReactDOMServer.renderToString(<App />);
360
361 const container2 = document.createElement('div');
362 container2.innerHTML = finalHTML;
363
364 const span = container2.getElementsByTagName('span')[0];
365
366 // On the client we don't have all data yet but we want to start
367 // hydrating anyway.
368 suspend = true;
369 await act(() => ReactDOMClient.hydrateRoot(container2, <App />));
370
371 // This should not cause a runtime exception, see:
372 // https://github.com/facebook/react/pull/18184
373 scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
374 expect(ref.current).toBe(null);
375
376 // Resolving the promise should continue hydration
377 suspend = false;
378 await act(async () => {
379 resolve();
380 await promise;
381 });
382
383 // We should now have hydrated with a ref on the existing span.
384 expect(ref.current).toBe(span);
385 });
386 });
387
388 describe('ReactTestRenderer', () => {
389 let ReactTestRenderer;
390
391 beforeEach(() => {
392 ReactTestRenderer = require('react-test-renderer');
393 });
394
395 // @gate enableScopeAPI
396 it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
397 const testScopeQuery = (type, props) => true;
398 const TestScope = React.unstable_Scope;
399 const scopeRef = React.createRef();
400 const divRef = React.createRef();
401 const spanRef = React.createRef();
402 const aRef = React.createRef();
403
404 function Test({toggle}) {
405 return toggle ? (
406 <TestScope ref={scopeRef}>
407 <div ref={divRef}>DIV</div>
408 <span ref={spanRef}>SPAN</span>
409 <a ref={aRef}>A</a>
410 </TestScope>
411 ) : (
412 <TestScope ref={scopeRef}>
413 <a ref={aRef}>A</a>
414 <div ref={divRef}>DIV</div>
415 <span ref={spanRef}>SPAN</span>
416 </TestScope>
417 );
418 }
419
420 let renderer;
421 await act(
422 () =>
423 (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
424 createNodeMock: element => {
425 return element;
426 },
427 unstable_isConcurrent: true,
428 })),
429 );
430 let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
431 expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
432 await act(() => renderer.update(<Test toggle={false} />));
433 nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
434 expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
435 });
436
437 // @gate enableScopeAPI
438 it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
439 const testScopeQuery = (type, props) => true;
440 const TestScope = React.unstable_Scope;
441 const scopeRef = React.createRef();
442 const divRef = React.createRef();
443 const spanRef = React.createRef();
444 const aRef = React.createRef();
445
446 function Test({toggle}) {
447 return toggle ? (
448 <TestScope ref={scopeRef}>
449 <div ref={divRef}>DIV</div>
450 <span ref={spanRef}>SPAN</span>
451 <a ref={aRef}>A</a>
452 </TestScope>
453 ) : (
454 <TestScope ref={scopeRef}>
455 <a ref={aRef}>A</a>
456 <div ref={divRef}>DIV</div>
457 <span ref={spanRef}>SPAN</span>
458 </TestScope>
459 );
460 }
461
462 let renderer;
463 await act(
464 () =>
465 (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
466 createNodeMock: element => {
467 return element;
468 },
469 unstable_isConcurrent: true,
470 })),
471 );
472 let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
473 expect(node).toEqual(divRef.current);
474 await act(() => renderer.update(<Test toggle={false} />));
475
476 node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
477 expect(node).toEqual(aRef.current);
478 });
479
480 // @gate enableScopeAPI
481 it('containsNode() works as intended', async () => {
482 const TestScope = React.unstable_Scope;
483 const scopeRef = React.createRef();
484 const divRef = React.createRef();
485 const spanRef = React.createRef();
486 const aRef = React.createRef();
487 const outerSpan = React.createRef();
488 const emRef = React.createRef();
489
490 function Test({toggle}) {
491 return toggle ? (
492 <div>
493 <span ref={outerSpan}>SPAN</span>
494 <TestScope ref={scopeRef}>
495 <div ref={divRef}>DIV</div>
496 <span ref={spanRef}>SPAN</span>
497 <a ref={aRef}>A</a>
498 </TestScope>
499 <em ref={emRef}>EM</em>
500 </div>
501 ) : (
502 <div>
503 <TestScope ref={scopeRef}>
504 <a ref={aRef}>A</a>
505 <div ref={divRef}>DIV</div>
506 <span ref={spanRef}>SPAN</span>
507 <em ref={emRef}>EM</em>
508 </TestScope>
509 <span ref={outerSpan}>SPAN</span>
510 </div>
511 );
512 }
513
514 let renderer;
515 await act(
516 () =>
517 (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
518 createNodeMock: element => {
519 return element;
520 },
521 unstable_isConcurrent: true,
522 })),
523 );
524 expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
525 expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
526 expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
527 expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
528 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
529 await act(() => renderer.update(<Test toggle={false} />));
530 expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
531 expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
532 expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
533 expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
534 expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
535 await act(() => renderer.update(<Test toggle={true} />));
536 expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
537 });
538 });
539 });