main
js 290 lines 6.64 KB
Raw
1 /* eslint-disable react/jsx-boolean-value */
2
3 let React;
4 let ReactNoop;
5 let Scheduler;
6 let act;
7 let Suspense;
8 let useState;
9 let textCache;
10
11 let readText;
12 let resolveText;
13 // let rejectText;
14
15 let assertLog;
16 let waitForPaint;
17
18 describe('ReactSuspenseWithNoopRenderer', () => {
19 beforeEach(() => {
20 jest.resetModules();
21
22 React = require('react');
23 ReactNoop = require('react-noop-renderer');
24 Scheduler = require('scheduler');
25 act = require('internal-test-utils').act;
26 Suspense = React.Suspense;
27 useState = React.useState;
28
29 const InternalTestUtils = require('internal-test-utils');
30 assertLog = InternalTestUtils.assertLog;
31 waitForPaint = InternalTestUtils.waitForPaint;
32
33 textCache = new Map();
34
35 readText = text => {
36 const record = textCache.get(text);
37 if (record !== undefined) {
38 switch (record.status) {
39 case 'pending':
40 throw record.promise;
41 case 'rejected':
42 throw Error('Failed to load: ' + text);
43 case 'resolved':
44 return text;
45 }
46 } else {
47 let ping;
48 const promise = new Promise(resolve => (ping = resolve));
49 const newRecord = {
50 status: 'pending',
51 ping: ping,
52 promise,
53 };
54 textCache.set(text, newRecord);
55 throw promise;
56 }
57 };
58
59 resolveText = text => {
60 const record = textCache.get(text);
61 if (record !== undefined) {
62 if (record.status === 'pending') {
63 record.ping();
64 record.ping = null;
65 record.status = 'resolved';
66 record.promise = null;
67 }
68 } else {
69 const newRecord = {
70 ping: null,
71 status: 'resolved',
72 promise: null,
73 };
74 textCache.set(text, newRecord);
75 }
76 };
77
78 // rejectText = text => {
79 // const record = textCache.get(text);
80 // if (record !== undefined) {
81 // if (record.status === 'pending') {
82 // Scheduler.log(`Promise rejected [${text}]`);
83 // record.ping();
84 // record.status = 'rejected';
85 // clearTimeout(record.promise._timer);
86 // record.promise = null;
87 // }
88 // } else {
89 // const newRecord = {
90 // ping: null,
91 // status: 'rejected',
92 // promise: null,
93 // };
94 // textCache.set(text, newRecord);
95 // }
96 // };
97 });
98
99 function Text(props) {
100 Scheduler.log(props.text);
101 return props.text;
102 }
103
104 function AsyncText(props) {
105 const text = props.text;
106 try {
107 readText(text);
108 Scheduler.log(text);
109 return text;
110 } catch (promise) {
111 if (typeof promise.then === 'function') {
112 Scheduler.log(`Suspend! [${text}]`);
113 } else {
114 Scheduler.log(`Error! [${text}]`);
115 }
116 throw promise;
117 }
118 }
119
120 // @gate enableCPUSuspense
121 it('skips CPU-bound trees on initial mount', async () => {
122 function App() {
123 return (
124 <>
125 <Text text="Outer" />
126 <div>
127 <Suspense defer fallback={<Text text="Loading..." />}>
128 <Text text="Inner" />
129 </Suspense>
130 </div>
131 </>
132 );
133 }
134
135 const root = ReactNoop.createRoot();
136 await act(async () => {
137 root.render(<App />);
138 await waitForPaint(['Outer', 'Loading...']);
139 expect(root).toMatchRenderedOutput(
140 <>
141 Outer
142 <div>Loading...</div>
143 </>,
144 );
145 });
146 // Inner contents finish in separate commit from outer
147 assertLog(['Inner']);
148 expect(root).toMatchRenderedOutput(
149 <>
150 Outer
151 <div>Inner</div>
152 </>,
153 );
154 });
155
156 // @gate enableCPUSuspense
157 it('does not skip CPU-bound trees during updates', async () => {
158 let setCount;
159
160 function App() {
161 const [count, _setCount] = useState(0);
162 setCount = _setCount;
163 return (
164 <>
165 <Text text="Outer" />
166 <div>
167 <Suspense defer fallback={<Text text="Loading..." />}>
168 <Text text={`Inner [${count}]`} />
169 </Suspense>
170 </div>
171 </>
172 );
173 }
174
175 // Initial mount
176 const root = ReactNoop.createRoot();
177 await act(() => {
178 root.render(<App />);
179 });
180 // Inner contents finish in separate commit from outer
181 assertLog(['Outer', 'Loading...', 'Inner [0]']);
182 expect(root).toMatchRenderedOutput(
183 <>
184 Outer
185 <div>Inner [0]</div>
186 </>,
187 );
188
189 // Update
190 await act(() => {
191 setCount(1);
192 });
193 // Entire update finishes in a single commit
194 assertLog(['Outer', 'Inner [1]']);
195 expect(root).toMatchRenderedOutput(
196 <>
197 Outer
198 <div>Inner [1]</div>
199 </>,
200 );
201 });
202
203 // @gate enableCPUSuspense
204 it('suspend inside CPU-bound tree', async () => {
205 function App() {
206 return (
207 <>
208 <Text text="Outer" />
209 <div>
210 <Suspense defer fallback={<Text text="Loading..." />}>
211 <AsyncText text="Inner" />
212 </Suspense>
213 </div>
214 </>
215 );
216 }
217
218 const root = ReactNoop.createRoot();
219 await act(async () => {
220 root.render(<App />);
221 await waitForPaint(['Outer', 'Loading...']);
222 expect(root).toMatchRenderedOutput(
223 <>
224 Outer
225 <div>Loading...</div>
226 </>,
227 );
228 });
229 // Inner contents suspended, so we continue showing a fallback.
230 assertLog([
231 'Suspend! [Inner]',
232 // pre-warming
233 'Suspend! [Inner]',
234 ]);
235 expect(root).toMatchRenderedOutput(
236 <>
237 Outer
238 <div>Loading...</div>
239 </>,
240 );
241
242 // Resolve the data and finish rendering
243 await act(async () => {
244 await resolveText('Inner');
245 });
246 assertLog(['Inner']);
247 expect(root).toMatchRenderedOutput(
248 <>
249 Outer
250 <div>Inner</div>
251 </>,
252 );
253 });
254
255 // @gate enableCPUSuspense
256 it('nested CPU-bound trees', async () => {
257 function App() {
258 return (
259 <>
260 <Text text="A" />
261 <div>
262 <Suspense defer fallback={<Text text="Loading B..." />}>
263 <Text text="B" />
264 <div>
265 <Suspense defer fallback={<Text text="Loading C..." />}>
266 <Text text="C" />
267 </Suspense>
268 </div>
269 </Suspense>
270 </div>
271 </>
272 );
273 }
274
275 const root = ReactNoop.createRoot();
276 await act(() => {
277 root.render(<App />);
278 });
279 // Each level commits separately
280 assertLog(['A', 'Loading B...', 'B', 'Loading C...', 'C']);
281 expect(root).toMatchRenderedOutput(
282 <>
283 A
284 <div>
285 B<div>C</div>
286 </div>
287 </>,
288 );
289 });
290 });