main
js 194 lines 5.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 import {
13 insertNodesAndExecuteScripts,
14 getVisibleChildren,
15 } from '../test-utils/FizzTestUtils';
16 import {patchMessageChannel} from '../../../../scripts/jest/patchMessageChannel';
17
18 // Polyfills for test environment
19 global.ReadableStream =
20 require('web-streams-polyfill/ponyfill/es6').ReadableStream;
21 global.TextEncoder = require('util').TextEncoder;
22
23 let act;
24 let serverAct;
25 let assertLog;
26 let waitForPaint;
27 let container;
28 let React;
29 let Scheduler;
30 let ReactDOMServer;
31 let ReactDOMClient;
32 let useDeferredValue;
33 let Suspense;
34
35 describe('ReactDOMFizzForm', () => {
36 beforeEach(() => {
37 jest.resetModules();
38 Scheduler = require('scheduler');
39 patchMessageChannel();
40 act = require('internal-test-utils').act;
41 serverAct = require('internal-test-utils').serverAct;
42 React = require('react');
43 ReactDOMServer = require('react-dom/server.browser');
44 ReactDOMClient = require('react-dom/client');
45 useDeferredValue = React.useDeferredValue;
46 Suspense = React.Suspense;
47 assertLog = require('internal-test-utils').assertLog;
48 waitForPaint = require('internal-test-utils').waitForPaint;
49 container = document.createElement('div');
50 document.body.appendChild(container);
51 });
52
53 afterEach(() => {
54 document.body.removeChild(container);
55 });
56
57 async function readIntoContainer(stream) {
58 const reader = stream.getReader();
59 let result = '';
60 while (true) {
61 const {done, value} = await reader.read();
62 if (done) {
63 break;
64 }
65 result += Buffer.from(value).toString('utf8');
66 }
67 const temp = document.createElement('div');
68 temp.innerHTML = result;
69 insertNodesAndExecuteScripts(temp, container, null);
70 }
71
72 function Text({text}) {
73 Scheduler.log(text);
74 return text;
75 }
76
77 it('returns initialValue argument, if provided', async () => {
78 function App() {
79 return useDeferredValue('Final', 'Initial');
80 }
81
82 const stream = await serverAct(() =>
83 ReactDOMServer.renderToReadableStream(<App />),
84 );
85 await readIntoContainer(stream);
86 expect(container.textContent).toEqual('Initial');
87
88 // After hydration, it's updated to the final value
89 await act(() => ReactDOMClient.hydrateRoot(container, <App />));
90 expect(container.textContent).toEqual('Final');
91 });
92
93 it(
94 'useDeferredValue during hydration has higher priority than remaining ' +
95 'incremental hydration',
96 async () => {
97 function B() {
98 const text = useDeferredValue('B [Final]', 'B [Initial]');
99 return <Text text={text} />;
100 }
101
102 function App() {
103 return (
104 <div>
105 <span>
106 <Text text="A" />
107 </span>
108 <Suspense fallback={<Text text="Loading..." />}>
109 <span>
110 <B />
111 </span>
112 <div>
113 <Suspense fallback={<Text text="Loading..." />}>
114 <span id="C" ref={cRef}>
115 <Text text="C" />
116 </span>
117 </Suspense>
118 </div>
119 </Suspense>
120 </div>
121 );
122 }
123
124 const cRef = React.createRef();
125
126 const stream = await serverAct(() =>
127 ReactDOMServer.renderToReadableStream(<App />),
128 );
129 await readIntoContainer(stream);
130 assertLog(['A', 'B [Initial]', 'C']);
131 expect(getVisibleChildren(container)).toEqual(
132 <div>
133 <span>A</span>
134 <span>B [Initial]</span>
135 <div>
136 <span id="C">C</span>
137 </div>
138 </div>,
139 );
140
141 const serverRenderedC = document.getElementById('C');
142
143 // On the client, we first hydrate the initial value, then upgrade
144 // to final.
145 await act(async () => {
146 ReactDOMClient.hydrateRoot(container, <App />);
147
148 // First the outermost Suspense boundary hydrates.
149 await waitForPaint(['A']);
150 expect(cRef.current).toBe(null);
151
152 // Then the next level hydrates. This level includes a useDeferredValue,
153 // so we should prioritize upgrading it before we proceed to hydrating
154 // additional levels.
155 await waitForPaint(['B [Initial]']);
156 expect(getVisibleChildren(container)).toEqual(
157 <div>
158 <span>A</span>
159 <span>B [Initial]</span>
160 <div>
161 <span id="C">C</span>
162 </div>
163 </div>,
164 );
165 expect(cRef.current).toBe(null);
166
167 // This paint should only update B. C should still be dehydrated.
168 await waitForPaint(['B [Final]']);
169 expect(getVisibleChildren(container)).toEqual(
170 <div>
171 <span>A</span>
172 <span>B [Final]</span>
173 <div>
174 <span id="C">C</span>
175 </div>
176 </div>,
177 );
178 expect(cRef.current).toBe(null);
179 });
180 // Finally we can hydrate C
181 assertLog(['C']);
182 expect(getVisibleChildren(container)).toEqual(
183 <div>
184 <span>A</span>
185 <span>B [Final]</span>
186 <div>
187 <span id="C">C</span>
188 </div>
189 </div>,
190 );
191 expect(cRef.current).toBe(serverRenderedC);
192 },
193 );
194 });