main
js 260 lines 6.54 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 * @jest-environment node
9 */
10 'use strict';
11
12 let React;
13 let ReactNoop;
14 let waitForAll;
15 let act;
16 let assertConsoleErrorDev;
17
18 describe('ReactSuspense', () => {
19 beforeEach(() => {
20 jest.resetModules();
21
22 React = require('react');
23 ReactNoop = require('react-noop-renderer');
24
25 const InternalTestUtils = require('internal-test-utils');
26 waitForAll = InternalTestUtils.waitForAll;
27 act = InternalTestUtils.act;
28 assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
29 });
30
31 function createThenable() {
32 let completed = false;
33 let resolve;
34 const promise = new Promise(res => {
35 resolve = () => {
36 completed = true;
37 res();
38 };
39 });
40 const PromiseComp = () => {
41 if (!completed) {
42 throw promise;
43 }
44 return 'Done';
45 };
46 return {promise, resolve, PromiseComp};
47 }
48
49 // Warning don't fire in production, so this test passes in prod even if
50 // the suspenseCallback feature is not enabled
51 // @gate enableSuspenseCallback || !__DEV__
52 it('check type', async () => {
53 const {PromiseComp} = createThenable();
54
55 const elementBadType = (
56 <React.Suspense suspenseCallback={1} fallback={'Waiting'}>
57 <PromiseComp />
58 </React.Suspense>
59 );
60
61 ReactNoop.render(elementBadType);
62 await waitForAll([]);
63 assertConsoleErrorDev(
64 [
65 'Unexpected type for suspenseCallback.',
66 ...(gate('alwaysThrottleRetries')
67 ? []
68 : ['Unexpected type for suspenseCallback.']),
69 ],
70 {
71 withoutStack: true,
72 },
73 );
74
75 const elementMissingCallback = (
76 <React.Suspense fallback={'Waiting'}>
77 <PromiseComp />
78 </React.Suspense>
79 );
80
81 ReactNoop.render(elementMissingCallback);
82 await waitForAll([]);
83 assertConsoleErrorDev([]);
84 });
85
86 // @gate enableSuspenseCallback
87 it('1 then 0 suspense callback', async () => {
88 const {promise, resolve, PromiseComp} = createThenable();
89
90 let ops = [];
91 const suspenseCallback = thenables => {
92 ops.push(thenables);
93 };
94
95 const element = (
96 <React.Suspense suspenseCallback={suspenseCallback} fallback={'Waiting'}>
97 <PromiseComp />
98 </React.Suspense>
99 );
100
101 ReactNoop.render(element);
102 await waitForAll([]);
103 expect(ReactNoop).toMatchRenderedOutput('Waiting');
104 expect(ops).toEqual([
105 new Set([promise]),
106 ...(gate('alwaysThrottleRetries') ? [] : new Set([promise])),
107 ]);
108 ops = [];
109
110 await act(() => resolve());
111 await waitForAll([]);
112 expect(ReactNoop).toMatchRenderedOutput('Done');
113 expect(ops).toEqual([]);
114 });
115
116 // @gate enableSuspenseCallback
117 it('2 then 1 then 0 suspense callback', async () => {
118 const {
119 promise: promise1,
120 resolve: resolve1,
121 PromiseComp: PromiseComp1,
122 } = createThenable();
123 const {
124 promise: promise2,
125 resolve: resolve2,
126 PromiseComp: PromiseComp2,
127 } = createThenable();
128
129 let ops = [];
130 const suspenseCallback1 = thenables => {
131 ops.push(thenables);
132 };
133
134 const element = (
135 <React.Suspense
136 suspenseCallback={suspenseCallback1}
137 fallback={'Waiting Tier 1'}>
138 <PromiseComp1 />
139 <PromiseComp2 />
140 </React.Suspense>
141 );
142
143 ReactNoop.render(element);
144 await waitForAll([]);
145 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
146 expect(ops).toEqual([
147 new Set([promise1]),
148 ...(gate('alwaysThrottleRetries') ? [] : new Set([promise1, promise2])),
149 ]);
150 ops = [];
151
152 await act(() => resolve1());
153 ReactNoop.render(element);
154 await waitForAll([]);
155 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
156 expect(ops).toEqual([
157 new Set([promise2]),
158 // pre-warming
159 new Set([promise2]),
160 ]);
161 ops = [];
162
163 await act(() => resolve2());
164 ReactNoop.render(element);
165 await waitForAll([]);
166 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
167 expect(ops).toEqual([]);
168 });
169
170 // @gate enableSuspenseCallback
171 it('nested suspense promises are reported only for their tier', async () => {
172 const {promise, PromiseComp} = createThenable();
173
174 const ops1 = [];
175 const suspenseCallback1 = thenables => {
176 ops1.push(thenables);
177 };
178 const ops2 = [];
179 const suspenseCallback2 = thenables => {
180 ops2.push(thenables);
181 };
182
183 const element = (
184 <React.Suspense
185 suspenseCallback={suspenseCallback1}
186 fallback={'Waiting Tier 1'}>
187 <React.Suspense
188 suspenseCallback={suspenseCallback2}
189 fallback={'Waiting Tier 2'}>
190 <PromiseComp />
191 </React.Suspense>
192 </React.Suspense>
193 );
194
195 ReactNoop.render(element);
196 await waitForAll([]);
197 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
198 expect(ops1).toEqual([]);
199 expect(ops2).toEqual([
200 new Set([promise]),
201 ...(gate('alwaysThrottleRetries') ? [] : [new Set([promise])]),
202 ]);
203 });
204
205 // @gate enableSuspenseCallback
206 it('competing suspense promises', async () => {
207 const {
208 promise: promise1,
209 resolve: resolve1,
210 PromiseComp: PromiseComp1,
211 } = createThenable();
212 const {
213 promise: promise2,
214 resolve: resolve2,
215 PromiseComp: PromiseComp2,
216 } = createThenable();
217
218 let ops1 = [];
219 const suspenseCallback1 = thenables => {
220 ops1.push(thenables);
221 };
222 let ops2 = [];
223 const suspenseCallback2 = thenables => {
224 ops2.push(thenables);
225 };
226
227 const element = (
228 <React.Suspense
229 suspenseCallback={suspenseCallback1}
230 fallback={'Waiting Tier 1'}>
231 <React.Suspense
232 suspenseCallback={suspenseCallback2}
233 fallback={'Waiting Tier 2'}>
234 <PromiseComp2 />
235 </React.Suspense>
236 <PromiseComp1 />
237 </React.Suspense>
238 );
239
240 ReactNoop.render(element);
241 await waitForAll([]);
242 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
243 expect(ops1).toEqual([new Set([promise1])]);
244 expect(ops2).toEqual([]);
245 ops1 = [];
246 ops2 = [];
247
248 await act(() => resolve1());
249 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done');
250 expect(ops1).toEqual([]);
251 expect(ops2).toEqual([new Set([promise2]), new Set([promise2])]);
252 ops1 = [];
253 ops2 = [];
254
255 await act(() => resolve2());
256 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
257 expect(ops1).toEqual([]);
258 expect(ops2).toEqual([]);
259 });
260 });