main
js 184 lines 5.63 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 // Polyfills for test environment
13 global.ReadableStream =
14 require('web-streams-polyfill/ponyfill/es6').ReadableStream;
15 global.TextEncoder = require('util').TextEncoder;
16 global.AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
17
18 let React;
19 let ReactDOM;
20 let ReactDOMFizzServer;
21 let Suspense;
22
23 describe('ReactDOMFizzServerEdge', () => {
24 beforeEach(() => {
25 jest.resetModules();
26 jest.useRealTimers();
27 React = require('react');
28 Suspense = React.Suspense;
29 ReactDOM = require('react-dom');
30 ReactDOMFizzServer = require('react-dom/server.edge');
31 });
32
33 async function readResult(stream) {
34 const reader = stream.getReader();
35 let result = '';
36 while (true) {
37 const {done, value} = await reader.read();
38 if (done) {
39 return result;
40 }
41 result += Buffer.from(value).toString('utf8');
42 }
43 }
44
45 // https://github.com/facebook/react/issues/27540
46 it('does not try to write to the stream after it has been closed', async () => {
47 async function preloadLate() {
48 await 1;
49 await 1;
50 // need to wait a few microtasks to get the stream to close before this is called
51 ReactDOM.preconnect('foo');
52 }
53
54 function Preload() {
55 preloadLate();
56 return null;
57 }
58
59 function App() {
60 return (
61 <html>
62 <body>
63 <main>hello</main>
64 <Preload />
65 </body>
66 </html>
67 );
68 }
69 const stream = await ReactDOMFizzServer.renderToReadableStream(<App />);
70 const result = await readResult(stream);
71 // need to wait a macrotask to let the scheduled work from the preconnect to execute
72 await new Promise(resolve => {
73 setTimeout(resolve, 1);
74 });
75
76 if (gate(flags => flags.enableFizzBlockingRender)) {
77 expect(result).toMatchInlineSnapshot(
78 `"<!DOCTYPE html><html><head><link rel="expect" href="#_R_" blocking="render"/></head><body><main>hello</main><template id="_R_"></template></body></html>"`,
79 );
80 } else {
81 expect(result).toMatchInlineSnapshot(
82 `"<!DOCTYPE html><html><head></head><body><main>hello</main></body></html>"`,
83 );
84 }
85 });
86
87 it('recoverably errors and does not add rel="expect" for large shells', async () => {
88 function Paragraph() {
89 return (
90 <p>
91 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
92 porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.
93 Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,
94 aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra.
95 Cras facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse
96 aliquet tempus tortor et ultricies. Aliquam libero velit, posuere
97 tempus ante sed, pellentesque tincidunt lorem. Nullam iaculis, eros a
98 varius aliquet, tortor felis tempor metus, nec cursus felis eros
99 aliquam nulla. Vivamus ut orci sed mauris congue lacinia. Cras eget
100 blandit neque. Pellentesque a massa in turpis ullamcorper volutpat vel
101 at massa. Sed ante est, auctor non diam non, vulputate ultrices metus.
102 Maecenas dictum fermentum quam id aliquam. Donec porta risus vitae
103 pretium posuere. Fusce facilisis eros in lacus tincidunt congue.
104 </p>
105 );
106 }
107
108 function App({suspense}) {
109 const paragraphs = [];
110 for (let i = 0; i < 600; i++) {
111 paragraphs.push(<Paragraph key={i} />);
112 }
113 return (
114 <html>
115 <body>
116 {suspense ? (
117 // This is ok
118 <Suspense fallback="Loading">{paragraphs}</Suspense>
119 ) : (
120 // This is not
121 paragraphs
122 )}
123 </body>
124 </html>
125 );
126 }
127 const errors = [];
128 const stream = await ReactDOMFizzServer.renderToReadableStream(
129 <App suspense={false} />,
130 {
131 onError(error) {
132 errors.push(error);
133 },
134 },
135 );
136 const result = await readResult(stream);
137 expect(result).not.toContain('rel="expect"');
138 if (gate(flags => flags.enableFizzBlockingRender)) {
139 expect(errors.length).toBe(1);
140 expect(errors[0].message).toContain(
141 'This rendered a large document (>512 kB) without any Suspense boundaries around most of it.',
142 );
143 } else {
144 expect(errors.length).toBe(0);
145 }
146
147 // If we wrap in a Suspense boundary though, then it should be ok.
148 const errors2 = [];
149 const stream2 = await ReactDOMFizzServer.renderToReadableStream(
150 <App suspense={true} />,
151 {
152 onError(error) {
153 errors2.push(error);
154 },
155 },
156 );
157 const result2 = await readResult(stream2);
158 if (gate(flags => flags.enableFizzBlockingRender)) {
159 expect(result2).toContain('rel="expect"');
160 } else {
161 expect(result2).not.toContain('rel="expect"');
162 }
163 expect(errors2.length).toBe(0);
164
165 // Or if we increase the progressiveChunkSize.
166 const errors3 = [];
167 const stream3 = await ReactDOMFizzServer.renderToReadableStream(
168 <App suspense={false} />,
169 {
170 progressiveChunkSize: 100000,
171 onError(error) {
172 errors3.push(error);
173 },
174 },
175 );
176 const result3 = await readResult(stream3);
177 if (gate(flags => flags.enableFizzBlockingRender)) {
178 expect(result3).toContain('rel="expect"');
179 } else {
180 expect(result3).not.toContain('rel="expect"');
181 }
182 expect(errors3.length).toBe(0);
183 });
184 });