@samitouri / QOS-React-1 / commits / 90172d12e8

[Flight] Cleanup turbopack tests (#27552)

Renames turbopack flight test files. removes tests which are primarily testing internal implemenation behavior of Flight Client itself

Josh Story committed Oct 20, 2023 at 23:21 UTC 90172d12e8f06936ade78ed916397c16a2bec4f8
10 files changed +388 -2266
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOM-test.js deleted
-1572
@@ -1,1572 +0,0 @@
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.TextDecoder = require('util').TextDecoder;
17 -
18 -// Don't wait before processing work on the server.
19 -// TODO: we can replace this with FlightServer.act().
20 -global.setImmediate = cb => cb();
21 -
22 -let act;
23 -let use;
24 -let clientExports;
25 -let clientModuleError;
26 -let turbopackMap;
27 -let Stream;
28 -let FlightReact;
29 -let React;
30 -let FlightReactDOM;
31 -let ReactDOMClient;
32 -let ReactServerDOMServer;
33 -let ReactServerDOMClient;
34 -let ReactDOMFizzServer;
35 -let Suspense;
36 -let ErrorBoundary;
37 -let JSDOM;
38 -
39 -describe('ReactFlightDOM', () => {
40 - beforeEach(() => {
41 - // For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
42 - // This can be thought of as essentially being the React Server Components scope with react-server
43 - // condition
44 - jest.resetModules();
45 -
46 - JSDOM = require('jsdom').JSDOM;
47 -
48 - // Simulate the condition resolution
49 - jest.mock('react-server-dom-turbopack/server', () =>
50 - require('react-server-dom-turbopack/server.node.unbundled'),
51 - );
52 - jest.mock('react', () => require('react/react.shared-subset'));
53 -
54 - const TurbopackMock = require('./utils/TurbopackMock');
55 - clientExports = TurbopackMock.clientExports;
56 - clientModuleError = TurbopackMock.clientModuleError;
57 - turbopackMap = TurbopackMock.turbopackMap;
58 -
59 - ReactServerDOMServer = require('react-server-dom-turbopack/server');
60 - FlightReact = require('react');
61 - FlightReactDOM = require('react-dom');
62 -
63 - // This reset is to load modules for the SSR/Browser scope.
64 - jest.resetModules();
65 - __unmockReact();
66 - act = require('internal-test-utils').act;
67 - Stream = require('stream');
68 - React = require('react');
69 - use = React.use;
70 - Suspense = React.Suspense;
71 - ReactDOMClient = require('react-dom/client');
72 - ReactDOMFizzServer = require('react-dom/server.node');
73 - ReactServerDOMClient = require('react-server-dom-turbopack/client');
74 -
75 - ErrorBoundary = class extends React.Component {
76 - state = {hasError: false, error: null};
77 - static getDerivedStateFromError(error) {
78 - return {
79 - hasError: true,
80 - error,
81 - };
82 - }
83 - render() {
84 - if (this.state.hasError) {
85 - return this.props.fallback(this.state.error);
86 - }
87 - return this.props.children;
88 - }
89 - };
90 - });
91 -
92 - function getTestStream() {
93 - const writable = new Stream.PassThrough();
94 - const readable = new ReadableStream({
95 - start(controller) {
96 - writable.on('data', chunk => {
97 - controller.enqueue(chunk);
98 - });
99 - writable.on('end', () => {
100 - controller.close();
101 - });
102 - },
103 - });
104 - return {
105 - readable,
106 - writable,
107 - };
108 - }
109 -
110 - const theInfinitePromise = new Promise(() => {});
111 - function InfiniteSuspend() {
112 - throw theInfinitePromise;
113 - }
114 -
115 - function getMeaningfulChildren(element) {
116 - const children = [];
117 - let node = element.firstChild;
118 - while (node) {
119 - if (node.nodeType === 1) {
120 - if (
121 - // some tags are ambiguous and might be hidden because they look like non-meaningful children
122 - // so we have a global override where if this data attribute is included we also include the node
123 - node.hasAttribute('data-meaningful') ||
124 - (node.tagName === 'SCRIPT' &&
125 - node.hasAttribute('src') &&
126 - node.hasAttribute('async')) ||
127 - (node.tagName !== 'SCRIPT' &&
128 - node.tagName !== 'TEMPLATE' &&
129 - node.tagName !== 'template' &&
130 - !node.hasAttribute('hidden') &&
131 - !node.hasAttribute('aria-hidden'))
132 - ) {
133 - const props = {};
134 - const attributes = node.attributes;
135 - for (let i = 0; i < attributes.length; i++) {
136 - if (
137 - attributes[i].name === 'id' &&
138 - attributes[i].value.includes(':')
139 - ) {
140 - // We assume this is a React added ID that's a non-visual implementation detail.
141 - continue;
142 - }
143 - props[attributes[i].name] = attributes[i].value;
144 - }
145 - props.children = getMeaningfulChildren(node);
146 - children.push(React.createElement(node.tagName.toLowerCase(), props));
147 - }
148 - } else if (node.nodeType === 3) {
149 - children.push(node.data);
150 - }
151 - node = node.nextSibling;
152 - }
153 - return children.length === 0
154 - ? undefined
155 - : children.length === 1
156 - ? children[0]
157 - : children;
158 - }
159 -
160 - it('should resolve HTML using Node streams', async () => {
161 - function Text({children}) {
162 - return <span>{children}</span>;
163 - }
164 - function HTML() {
165 - return (
166 - <div>
167 - <Text>hello</Text>
168 - <Text>world</Text>
169 - </div>
170 - );
171 - }
172 -
173 - function App() {
174 - const model = {
175 - html: <HTML />,
176 - };
177 - return model;
178 - }
179 -
180 - const {writable, readable} = getTestStream();
181 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
182 - <App />,
183 - turbopackMap,
184 - );
185 - pipe(writable);
186 - const response = ReactServerDOMClient.createFromReadableStream(readable);
187 - const model = await response;
188 - expect(model).toEqual({
189 - html: (
190 - <div>
191 - <span>hello</span>
192 - <span>world</span>
193 - </div>
194 - ),
195 - });
196 - });
197 -
198 - it('should resolve the root', async () => {
199 - // Model
200 - function Text({children}) {
201 - return <span>{children}</span>;
202 - }
203 - function HTML() {
204 - return (
205 - <div>
206 - <Text>hello</Text>
207 - <Text>world</Text>
208 - </div>
209 - );
210 - }
211 - function RootModel() {
212 - return {
213 - html: <HTML />,
214 - };
215 - }
216 -
217 - // View
218 - function Message({response}) {
219 - return <section>{use(response).html}</section>;
220 - }
221 - function App({response}) {
222 - return (
223 - <Suspense fallback={<h1>Loading...</h1>}>
224 - <Message response={response} />
225 - </Suspense>
226 - );
227 - }
228 -
229 - const {writable, readable} = getTestStream();
230 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
231 - <RootModel />,
232 - turbopackMap,
233 - );
234 - pipe(writable);
235 - const response = ReactServerDOMClient.createFromReadableStream(readable);
236 -
237 - const container = document.createElement('div');
238 - const root = ReactDOMClient.createRoot(container);
239 - await act(() => {
240 - root.render(<App response={response} />);
241 - });
242 - expect(container.innerHTML).toBe(
243 - '<section><div><span>hello</span><span>world</span></div></section>',
244 - );
245 - });
246 -
247 - it('should not get confused by $', async () => {
248 - // Model
249 - function RootModel() {
250 - return {text: '$1'};
251 - }
252 -
253 - // View
254 - function Message({response}) {
255 - return <p>{use(response).text}</p>;
256 - }
257 - function App({response}) {
258 - return (
259 - <Suspense fallback={<h1>Loading...</h1>}>
260 - <Message response={response} />
261 - </Suspense>
262 - );
263 - }
264 -
265 - const {writable, readable} = getTestStream();
266 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
267 - <RootModel />,
268 - turbopackMap,
269 - );
270 - pipe(writable);
271 - const response = ReactServerDOMClient.createFromReadableStream(readable);
272 -
273 - const container = document.createElement('div');
274 - const root = ReactDOMClient.createRoot(container);
275 - await act(() => {
276 - root.render(<App response={response} />);
277 - });
278 - expect(container.innerHTML).toBe('<p>$1</p>');
279 - });
280 -
281 - it('should not get confused by @', async () => {
282 - // Model
283 - function RootModel() {
284 - return {text: '@div'};
285 - }
286 -
287 - // View
288 - function Message({response}) {
289 - return <p>{use(response).text}</p>;
290 - }
291 - function App({response}) {
292 - return (
293 - <Suspense fallback={<h1>Loading...</h1>}>
294 - <Message response={response} />
295 - </Suspense>
296 - );
297 - }
298 -
299 - const {writable, readable} = getTestStream();
300 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
301 - <RootModel />,
302 - turbopackMap,
303 - );
304 - pipe(writable);
305 - const response = ReactServerDOMClient.createFromReadableStream(readable);
306 -
307 - const container = document.createElement('div');
308 - const root = ReactDOMClient.createRoot(container);
309 - await act(() => {
310 - root.render(<App response={response} />);
311 - });
312 - expect(container.innerHTML).toBe('<p>@div</p>');
313 - });
314 -
315 - it('should be able to esm compat test module references', async () => {
316 - const ESMCompatModule = {
317 - __esModule: true,
318 - default: function ({greeting}) {
319 - return greeting + ' World';
320 - },
321 - hi: 'Hello',
322 - };
323 -
324 - function Print({response}) {
325 - return <p>{use(response)}</p>;
326 - }
327 -
328 - function App({response}) {
329 - return (
330 - <Suspense fallback={<h1>Loading...</h1>}>
331 - <Print response={response} />
332 - </Suspense>
333 - );
334 - }
335 -
336 - function interopWebpack(obj) {
337 - // Basically what Webpack's ESM interop feature testing does.
338 - if (typeof obj === 'object' && obj.__esModule) {
339 - return obj;
340 - }
341 - return Object.assign({default: obj}, obj);
342 - }
343 -
344 - const {default: Component, hi} = interopWebpack(
345 - clientExports(ESMCompatModule),
346 - );
347 -
348 - const {writable, readable} = getTestStream();
349 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
350 - <Component greeting={hi} />,
351 - turbopackMap,
352 - );
353 - pipe(writable);
354 - const response = ReactServerDOMClient.createFromReadableStream(readable);
355 -
356 - const container = document.createElement('div');
357 - const root = ReactDOMClient.createRoot(container);
358 - await act(() => {
359 - root.render(<App response={response} />);
360 - });
361 - expect(container.innerHTML).toBe('<p>Hello World</p>');
362 - });
363 -
364 - it('should be able to render a named component export', async () => {
365 - const Module = {
366 - Component: function ({greeting}) {
367 - return greeting + ' World';
368 - },
369 - };
370 -
371 - function Print({response}) {
372 - return <p>{use(response)}</p>;
373 - }
374 -
375 - function App({response}) {
376 - return (
377 - <Suspense fallback={<h1>Loading...</h1>}>
378 - <Print response={response} />
379 - </Suspense>
380 - );
381 - }
382 -
383 - const {Component} = clientExports(Module);
384 -
385 - const {writable, readable} = getTestStream();
386 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
387 - <Component greeting={'Hello'} />,
388 - turbopackMap,
389 - );
390 - pipe(writable);
391 - const response = ReactServerDOMClient.createFromReadableStream(readable);
392 -
393 - const container = document.createElement('div');
394 - const root = ReactDOMClient.createRoot(container);
395 - await act(() => {
396 - root.render(<App response={response} />);
397 - });
398 - expect(container.innerHTML).toBe('<p>Hello World</p>');
399 - });
400 -
401 - it('should be able to render a module split named component export', async () => {
402 - const Module = {
403 - // This gets split into a separate module from the original one.
404 - split: function ({greeting}) {
405 - return greeting + ' World';
406 - },
407 - };
408 -
409 - function Print({response}) {
410 - return <p>{use(response)}</p>;
411 - }
412 -
413 - function App({response}) {
414 - return (
415 - <Suspense fallback={<h1>Loading...</h1>}>
416 - <Print response={response} />
417 - </Suspense>
418 - );
419 - }
420 -
421 - const {split: Component} = clientExports(Module);
422 -
423 - const {writable, readable} = getTestStream();
424 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
425 - <Component greeting={'Hello'} />,
426 - turbopackMap,
427 - );
428 - pipe(writable);
429 - const response = ReactServerDOMClient.createFromReadableStream(readable);
430 -
431 - const container = document.createElement('div');
432 - const root = ReactDOMClient.createRoot(container);
433 - await act(() => {
434 - root.render(<App response={response} />);
435 - });
436 - expect(container.innerHTML).toBe('<p>Hello World</p>');
437 - });
438 -
439 - it('should unwrap async module references', async () => {
440 - const AsyncModule = Promise.resolve(function AsyncModule({text}) {
441 - return 'Async: ' + text;
442 - });
443 -
444 - const AsyncModule2 = Promise.resolve({
445 - exportName: 'Module',
446 - });
447 -
448 - function Print({response}) {
449 - return <p>{use(response)}</p>;
450 - }
451 -
452 - function App({response}) {
453 - return (
454 - <Suspense fallback={<h1>Loading...</h1>}>
455 - <Print response={response} />
456 - </Suspense>
457 - );
458 - }
459 -
460 - const AsyncModuleRef = await clientExports(AsyncModule);
461 - const AsyncModuleRef2 = await clientExports(AsyncModule2);
462 -
463 - const {writable, readable} = getTestStream();
464 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
465 - <AsyncModuleRef text={AsyncModuleRef2.exportName} />,
466 - turbopackMap,
467 - );
468 - pipe(writable);
469 - const response = ReactServerDOMClient.createFromReadableStream(readable);
470 -
471 - const container = document.createElement('div');
472 - const root = ReactDOMClient.createRoot(container);
473 - await act(() => {
474 - root.render(<App response={response} />);
475 - });
476 - expect(container.innerHTML).toBe('<p>Async: Module</p>');
477 - });
478 -
479 - it('should unwrap async module references using use', async () => {
480 - const AsyncModule = Promise.resolve('Async Text');
481 -
482 - function Print({response}) {
483 - return use(response);
484 - }
485 -
486 - function App({response}) {
487 - return (
488 - <Suspense fallback={<h1>Loading...</h1>}>
489 - <Print response={response} />
490 - </Suspense>
491 - );
492 - }
493 -
494 - const AsyncModuleRef = clientExports(AsyncModule);
495 -
496 - function ServerComponent() {
497 - const text = FlightReact.use(AsyncModuleRef);
498 - return <p>{text}</p>;
499 - }
500 -
501 - const {writable, readable} = getTestStream();
502 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
503 - <ServerComponent />,
504 - turbopackMap,
505 - );
506 - pipe(writable);
507 - const response = ReactServerDOMClient.createFromReadableStream(readable);
508 -
509 - const container = document.createElement('div');
510 - const root = ReactDOMClient.createRoot(container);
511 - await act(() => {
512 - root.render(<App response={response} />);
513 - });
514 - expect(container.innerHTML).toBe('<p>Async Text</p>');
515 - });
516 -
517 - it('should be able to import a name called "then"', async () => {
518 - const thenExports = {
519 - then: function then() {
520 - return 'and then';
521 - },
522 - };
523 -
524 - function Print({response}) {
525 - return <p>{use(response)}</p>;
526 - }
527 -
528 - function App({response}) {
529 - return (
530 - <Suspense fallback={<h1>Loading...</h1>}>
531 - <Print response={response} />
532 - </Suspense>
533 - );
534 - }
535 -
536 - const ThenRef = clientExports(thenExports).then;
537 -
538 - const {writable, readable} = getTestStream();
539 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
540 - <ThenRef />,
541 - turbopackMap,
542 - );
543 - pipe(writable);
544 - const response = ReactServerDOMClient.createFromReadableStream(readable);
545 -
546 - const container = document.createElement('div');
547 - const root = ReactDOMClient.createRoot(container);
548 - await act(() => {
549 - root.render(<App response={response} />);
550 - });
551 - expect(container.innerHTML).toBe('<p>and then</p>');
552 - });
553 -
554 - it('throws when accessing a member below the client exports', () => {
555 - const ClientModule = clientExports({
556 - Component: {deep: 'thing'},
557 - });
558 - function dotting() {
559 - return ClientModule.Component.deep;
560 - }
561 - expect(dotting).toThrowError(
562 - 'Cannot access Component.deep on the server. ' +
563 - 'You cannot dot into a client module from a server component. ' +
564 - 'You can only pass the imported name through.',
565 - );
566 - });
567 -
568 - it('does not throw when React inspects any deep props', () => {
569 - const ClientModule = clientExports({
570 - Component: function () {},
571 - });
572 - <ClientModule.Component key="this adds instrumentation" />;
573 - });
574 -
575 - it('throws when accessing a Context.Provider below the client exports', () => {
576 - const Context = React.createContext();
577 - const ClientModule = clientExports({
578 - Context,
579 - });
580 - function dotting() {
581 - return ClientModule.Context.Provider;
582 - }
583 - expect(dotting).toThrowError(
584 - `Cannot render a Client Context Provider on the Server. ` +
585 - `Instead, you can export a Client Component wrapper ` +
586 - `that itself renders a Client Context Provider.`,
587 - );
588 - });
589 -
590 - it('should progressively reveal server components', async () => {
591 - let reportedErrors = [];
592 -
593 - // Client Components
594 -
595 - function MyErrorBoundary({children}) {
596 - return (
597 - <ErrorBoundary
598 - fallback={e => (
599 - <p>
600 - {__DEV__ ? e.message + ' + ' : null}
601 - {e.digest}
602 - </p>
603 - )}>
604 - {children}
605 - </ErrorBoundary>
606 - );
607 - }
608 -
609 - // Model
610 - function Text({children}) {
611 - return children;
612 - }
613 -
614 - function makeDelayedText() {
615 - let _resolve, _reject;
616 - let promise = new Promise((resolve, reject) => {
617 - _resolve = () => {
618 - promise = null;
619 - resolve();
620 - };
621 - _reject = e => {
622 - promise = null;
623 - reject(e);
624 - };
625 - });
626 - async function DelayedText({children}) {
627 - await promise;
628 - return <Text>{children}</Text>;
629 - }
630 - return [DelayedText, _resolve, _reject];
631 - }
632 -
633 - const [Friends, resolveFriends] = makeDelayedText();
634 - const [Name, resolveName] = makeDelayedText();
635 - const [Posts, resolvePosts] = makeDelayedText();
636 - const [Photos, resolvePhotos] = makeDelayedText();
637 - const [Games, , rejectGames] = makeDelayedText();
638 -
639 - // View
640 - function ProfileDetails({avatar}) {
641 - return (
642 - <div>
643 - <Name>:name:</Name>
644 - {avatar}
645 - </div>
646 - );
647 - }
648 - function ProfileSidebar({friends}) {
649 - return (
650 - <div>
651 - <Photos>:photos:</Photos>
652 - {friends}
653 - </div>
654 - );
655 - }
656 - function ProfilePosts({posts}) {
657 - return <div>{posts}</div>;
658 - }
659 - function ProfileGames({games}) {
660 - return <div>{games}</div>;
661 - }
662 -
663 - const MyErrorBoundaryClient = clientExports(MyErrorBoundary);
664 -
665 - function ProfileContent() {
666 - return (
667 - <>
668 - <ProfileDetails avatar={<Text>:avatar:</Text>} />
669 - <Suspense fallback={<p>(loading sidebar)</p>}>
670 - <ProfileSidebar friends={<Friends>:friends:</Friends>} />
671 - </Suspense>
672 - <Suspense fallback={<p>(loading posts)</p>}>
673 - <ProfilePosts posts={<Posts>:posts:</Posts>} />
674 - </Suspense>
675 - <MyErrorBoundaryClient>
676 - <Suspense fallback={<p>(loading games)</p>}>
677 - <ProfileGames games={<Games>:games:</Games>} />
678 - </Suspense>
679 - </MyErrorBoundaryClient>
680 - </>
681 - );
682 - }
683 -
684 - const model = {
685 - rootContent: <ProfileContent />,
686 - };
687 -
688 - function ProfilePage({response}) {
689 - return use(response).rootContent;
690 - }
691 -
692 - const {writable, readable} = getTestStream();
693 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
694 - model,
695 - turbopackMap,
696 - {
697 - onError(x) {
698 - reportedErrors.push(x);
699 - return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
700 - },
701 - },
702 - );
703 - pipe(writable);
704 - const response = ReactServerDOMClient.createFromReadableStream(readable);
705 -
706 - const container = document.createElement('div');
707 - const root = ReactDOMClient.createRoot(container);
708 - await act(() => {
709 - root.render(
710 - <Suspense fallback={<p>(loading)</p>}>
711 - <ProfilePage response={response} />
712 - </Suspense>,
713 - );
714 - });
715 - expect(container.innerHTML).toBe('<p>(loading)</p>');
716 -
717 - // This isn't enough to show anything.
718 - await act(() => {
719 - resolveFriends();
720 - });
721 - expect(container.innerHTML).toBe('<p>(loading)</p>');
722 -
723 - // We can now show the details. Sidebar and posts are still loading.
724 - await act(() => {
725 - resolveName();
726 - });
727 - // Advance time enough to trigger a nested fallback.
728 - await act(() => {
729 - jest.advanceTimersByTime(500);
730 - });
731 - expect(container.innerHTML).toBe(
732 - '<div>:name::avatar:</div>' +
733 - '<p>(loading sidebar)</p>' +
734 - '<p>(loading posts)</p>' +
735 - '<p>(loading games)</p>',
736 - );
737 -
738 - expect(reportedErrors).toEqual([]);
739 -
740 - const theError = new Error('Game over');
741 - // Let's *fail* loading games.
742 - await act(async () => {
743 - await rejectGames(theError);
744 - await 'the inner async function';
745 - });
746 - const expectedGamesValue = __DEV__
747 - ? '<p>Game over + a dev digest</p>'
748 - : '<p>digest("Game over")</p>';
749 - expect(container.innerHTML).toBe(
750 - '<div>:name::avatar:</div>' +
751 - '<p>(loading sidebar)</p>' +
752 - '<p>(loading posts)</p>' +
753 - expectedGamesValue,
754 - );
755 -
756 - expect(reportedErrors).toEqual([theError]);
757 - reportedErrors = [];
758 -
759 - // We can now show the sidebar.
760 - await act(async () => {
761 - await resolvePhotos();
762 - await 'the inner async function';
763 - });
764 - expect(container.innerHTML).toBe(
765 - '<div>:name::avatar:</div>' +
766 - '<div>:photos::friends:</div>' +
767 - '<p>(loading posts)</p>' +
768 - expectedGamesValue,
769 - );
770 -
771 - // Show everything.
772 - await act(async () => {
773 - await resolvePosts();
774 - await 'the inner async function';
775 - });
776 - expect(container.innerHTML).toBe(
777 - '<div>:name::avatar:</div>' +
778 - '<div>:photos::friends:</div>' +
779 - '<div>:posts:</div>' +
780 - expectedGamesValue,
781 - );
782 -
783 - expect(reportedErrors).toEqual([]);
784 - });
785 -
786 - it('should preserve state of client components on refetch', async () => {
787 - // Client
788 -
789 - function Page({response}) {
790 - return use(response);
791 - }
792 -
793 - function Input() {
794 - return <input />;
795 - }
796 -
797 - const InputClient = clientExports(Input);
798 -
799 - // Server
800 -
801 - function App({color}) {
802 - // Verify both DOM and Client children.
803 - return (
804 - <div style={{color}}>
805 - <input />
806 - <InputClient />
807 - </div>
808 - );
809 - }
810 -
811 - const container = document.createElement('div');
812 - const root = ReactDOMClient.createRoot(container);
813 -
814 - const stream1 = getTestStream();
815 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
816 - <App color="red" />,
817 - turbopackMap,
818 - );
819 - pipe(stream1.writable);
820 - const response1 = ReactServerDOMClient.createFromReadableStream(
821 - stream1.readable,
822 - );
823 - await act(() => {
824 - root.render(
825 - <Suspense fallback={<p>(loading)</p>}>
826 - <Page response={response1} />
827 - </Suspense>,
828 - );
829 - });
830 - expect(container.children.length).toBe(1);
831 - expect(container.children[0].tagName).toBe('DIV');
832 - expect(container.children[0].style.color).toBe('red');
833 -
834 - // Change the DOM state for both inputs.
835 - const inputA = container.children[0].children[0];
836 - expect(inputA.tagName).toBe('INPUT');
837 - inputA.value = 'hello';
838 - const inputB = container.children[0].children[1];
839 - expect(inputB.tagName).toBe('INPUT');
840 - inputB.value = 'goodbye';
841 -
842 - const stream2 = getTestStream();
843 - const {pipe: pipe2} = ReactServerDOMServer.renderToPipeableStream(
844 - <App color="blue" />,
845 - turbopackMap,
846 - );
847 - pipe2(stream2.writable);
848 - const response2 = ReactServerDOMClient.createFromReadableStream(
849 - stream2.readable,
850 - );
851 - await act(() => {
852 - root.render(
853 - <Suspense fallback={<p>(loading)</p>}>
854 - <Page response={response2} />
855 - </Suspense>,
856 - );
857 - });
858 - expect(container.children.length).toBe(1);
859 - expect(container.children[0].tagName).toBe('DIV');
860 - expect(container.children[0].style.color).toBe('blue');
861 -
862 - // Verify we didn't destroy the DOM for either input.
863 - expect(inputA === container.children[0].children[0]).toBe(true);
864 - expect(inputA.tagName).toBe('INPUT');
865 - expect(inputA.value).toBe('hello');
866 - expect(inputB === container.children[0].children[1]).toBe(true);
867 - expect(inputB.tagName).toBe('INPUT');
868 - expect(inputB.value).toBe('goodbye');
869 - });
870 -
871 - it('should be able to complete after aborting and throw the reason client-side', async () => {
872 - const reportedErrors = [];
873 -
874 - const {writable, readable} = getTestStream();
875 - const {pipe, abort} = ReactServerDOMServer.renderToPipeableStream(
876 - <div>
877 - <InfiniteSuspend />
878 - </div>,
879 - turbopackMap,
880 - {
881 - onError(x) {
882 - reportedErrors.push(x);
883 - const message = typeof x === 'string' ? x : x.message;
884 - return __DEV__ ? 'a dev digest' : `digest("${message}")`;
885 - },
886 - },
887 - );
888 - pipe(writable);
889 - const response = ReactServerDOMClient.createFromReadableStream(readable);
890 -
891 - const container = document.createElement('div');
892 - const root = ReactDOMClient.createRoot(container);
893 -
894 - function App({res}) {
895 - return use(res);
896 - }
897 -
898 - await act(() => {
899 - root.render(
900 - <ErrorBoundary
901 - fallback={e => (
902 - <p>
903 - {__DEV__ ? e.message + ' + ' : null}
904 - {e.digest}
905 - </p>
906 - )}>
907 - <Suspense fallback={<p>(loading)</p>}>
908 - <App res={response} />
909 - </Suspense>
910 - </ErrorBoundary>,
911 - );
912 - });
913 - expect(container.innerHTML).toBe('<p>(loading)</p>');
914 -
915 - await act(() => {
916 - abort('for reasons');
917 - });
918 - if (__DEV__) {
919 - expect(container.innerHTML).toBe(
920 - '<p>Error: for reasons + a dev digest</p>',
921 - );
922 - } else {
923 - expect(container.innerHTML).toBe('<p>digest("for reasons")</p>');
924 - }
925 -
926 - expect(reportedErrors).toEqual(['for reasons']);
927 - });
928 -
929 - it('should be able to recover from a direct reference erroring client-side', async () => {
930 - const reportedErrors = [];
931 -
932 - const ClientComponent = clientExports(function ({prop}) {
933 - return 'This should never render';
934 - });
935 -
936 - const ClientReference = clientModuleError(new Error('module init error'));
937 -
938 - const {writable, readable} = getTestStream();
939 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
940 - <div>
941 - <ClientComponent prop={ClientReference} />
942 - </div>,
943 - turbopackMap,
944 - {
945 - onError(x) {
946 - reportedErrors.push(x);
947 - },
948 - },
949 - );
950 - pipe(writable);
951 - const response = ReactServerDOMClient.createFromReadableStream(readable);
952 -
953 - const container = document.createElement('div');
954 - const root = ReactDOMClient.createRoot(container);
955 -
956 - function App({res}) {
957 - return use(res);
958 - }
959 -
960 - await act(() => {
961 - root.render(
962 - <ErrorBoundary fallback={e => <p>{e.message}</p>}>
963 - <Suspense fallback={<p>(loading)</p>}>
964 - <App res={response} />
965 - </Suspense>
966 - </ErrorBoundary>,
967 - );
968 - });
969 - expect(container.innerHTML).toBe('<p>module init error</p>');
970 -
971 - expect(reportedErrors).toEqual([]);
972 - });
973 -
974 - it('should be able to recover from a direct reference erroring client-side async', async () => {
975 - const reportedErrors = [];
976 -
977 - const ClientComponent = clientExports(function ({prop}) {
978 - return 'This should never render';
979 - });
980 -
981 - let rejectPromise;
982 - const ClientReference = await clientExports(
983 - new Promise((resolve, reject) => {
984 - rejectPromise = reject;
985 - }),
986 - );
987 -
988 - const {writable, readable} = getTestStream();
989 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
990 - <div>
991 - <ClientComponent prop={ClientReference} />
992 - </div>,
993 - turbopackMap,
994 - {
995 - onError(x) {
996 - reportedErrors.push(x);
997 - },
998 - },
999 - );
1000 - pipe(writable);
1001 - const response = ReactServerDOMClient.createFromReadableStream(readable);
1002 -
1003 - const container = document.createElement('div');
1004 - const root = ReactDOMClient.createRoot(container);
1005 -
1006 - function App({res}) {
1007 - return use(res);
1008 - }
1009 -
1010 - await act(() => {
1011 - root.render(
1012 - <ErrorBoundary fallback={e => <p>{e.message}</p>}>
1013 - <Suspense fallback={<p>(loading)</p>}>
1014 - <App res={response} />
1015 - </Suspense>
1016 - </ErrorBoundary>,
1017 - );
1018 - });
1019 -
1020 - expect(container.innerHTML).toBe('<p>(loading)</p>');
1021 -
1022 - await act(() => {
1023 - rejectPromise(new Error('async module init error'));
1024 - });
1025 -
1026 - expect(container.innerHTML).toBe('<p>async module init error</p>');
1027 -
1028 - expect(reportedErrors).toEqual([]);
1029 - });
1030 -
1031 - it('should be able to recover from a direct reference erroring server-side', async () => {
1032 - const reportedErrors = [];
1033 -
1034 - const ClientComponent = clientExports(function ({prop}) {
1035 - return 'This should never render';
1036 - });
1037 -
1038 - // We simulate a bug in the Webpack bundler which causes an error on the server.
1039 - for (const id in turbopackMap) {
1040 - Object.defineProperty(turbopackMap, id, {
1041 - get: () => {
1042 - throw new Error('bug in the bundler');
1043 - },
1044 - });
1045 - }
1046 -
1047 - const {writable, readable} = getTestStream();
1048 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1049 - <div>
1050 - <ClientComponent />
1051 - </div>,
1052 - turbopackMap,
1053 - {
1054 - onError(x) {
1055 - reportedErrors.push(x.message);
1056 - return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
1057 - },
1058 - },
1059 - );
1060 - pipe(writable);
1061 -
1062 - const response = ReactServerDOMClient.createFromReadableStream(readable);
1063 -
1064 - const container = document.createElement('div');
1065 - const root = ReactDOMClient.createRoot(container);
1066 -
1067 - function App({res}) {
1068 - return use(res);
1069 - }
1070 -
1071 - await act(() => {
1072 - root.render(
1073 - <ErrorBoundary
1074 - fallback={e => (
1075 - <p>
1076 - {__DEV__ ? e.message + ' + ' : null}
1077 - {e.digest}
1078 - </p>
1079 - )}>
1080 - <Suspense fallback={<p>(loading)</p>}>
1081 - <App res={response} />
1082 - </Suspense>
1083 - </ErrorBoundary>,
1084 - );
1085 - });
1086 - if (__DEV__) {
1087 - expect(container.innerHTML).toBe(
1088 - '<p>bug in the bundler + a dev digest</p>',
1089 - );
1090 - } else {
1091 - expect(container.innerHTML).toBe('<p>digest("bug in the bundler")</p>');
1092 - }
1093 -
1094 - expect(reportedErrors).toEqual(['bug in the bundler']);
1095 - });
1096 -
1097 - it('should pass a Promise through props and be able use() it on the client', async () => {
1098 - async function getData() {
1099 - return 'async hello';
1100 - }
1101 -
1102 - function Component({data}) {
1103 - const text = use(data);
1104 - return <p>{text}</p>;
1105 - }
1106 -
1107 - const ClientComponent = clientExports(Component);
1108 -
1109 - function ServerComponent() {
1110 - const data = getData(); // no await here
1111 - return <ClientComponent data={data} />;
1112 - }
1113 -
1114 - function Print({response}) {
1115 - return use(response);
1116 - }
1117 -
1118 - function App({response}) {
1119 - return (
1120 - <Suspense fallback={<h1>Loading...</h1>}>
1121 - <Print response={response} />
1122 - </Suspense>
1123 - );
1124 - }
1125 -
1126 - const {writable, readable} = getTestStream();
1127 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1128 - <ServerComponent />,
1129 - turbopackMap,
1130 - );
1131 - pipe(writable);
1132 - const response = ReactServerDOMClient.createFromReadableStream(readable);
1133 -
1134 - const container = document.createElement('div');
1135 - const root = ReactDOMClient.createRoot(container);
1136 - await act(() => {
1137 - root.render(<App response={response} />);
1138 - });
1139 - expect(container.innerHTML).toBe('<p>async hello</p>');
1140 - });
1141 -
1142 - it('should throw on the client if a passed promise eventually rejects', async () => {
1143 - const reportedErrors = [];
1144 - const theError = new Error('Server throw');
1145 -
1146 - async function getData() {
1147 - throw theError;
1148 - }
1149 -
1150 - function Component({data}) {
1151 - const text = use(data);
1152 - return <p>{text}</p>;
1153 - }
1154 -
1155 - const ClientComponent = clientExports(Component);
1156 -
1157 - function ServerComponent() {
1158 - const data = getData(); // no await here
1159 - return <ClientComponent data={data} />;
1160 - }
1161 -
1162 - function Await({response}) {
1163 - return use(response);
1164 - }
1165 -
1166 - function App({response}) {
1167 - return (
1168 - <Suspense fallback={<h1>Loading...</h1>}>
1169 - <ErrorBoundary
1170 - fallback={e => (
1171 - <p>
1172 - {__DEV__ ? e.message + ' + ' : null}
1173 - {e.digest}
1174 - </p>
1175 - )}>
1176 - <Await response={response} />
1177 - </ErrorBoundary>
1178 - </Suspense>
1179 - );
1180 - }
1181 -
1182 - const {writable, readable} = getTestStream();
1183 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1184 - <ServerComponent />,
1185 - turbopackMap,
1186 - {
1187 - onError(x) {
1188 - reportedErrors.push(x);
1189 - return __DEV__ ? 'a dev digest' : `digest("${x.message}")`;
1190 - },
1191 - },
1192 - );
1193 - pipe(writable);
1194 - const response = ReactServerDOMClient.createFromReadableStream(readable);
1195 -
1196 - const container = document.createElement('div');
1197 - const root = ReactDOMClient.createRoot(container);
1198 - await act(() => {
1199 - root.render(<App response={response} />);
1200 - });
1201 - expect(container.innerHTML).toBe(
1202 - __DEV__
1203 - ? '<p>Server throw + a dev digest</p>'
1204 - : '<p>digest("Server throw")</p>',
1205 - );
1206 - expect(reportedErrors).toEqual([theError]);
1207 - });
1208 -
1209 - it('should support float methods when rendering in Fiber', async () => {
1210 - function Component() {
1211 - return <p>hello world</p>;
1212 - }
1213 -
1214 - const ClientComponent = clientExports(Component);
1215 -
1216 - async function ServerComponent() {
1217 - FlightReactDOM.prefetchDNS('d before');
1218 - FlightReactDOM.preconnect('c before');
1219 - FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1220 - FlightReactDOM.preload('l before', {as: 'style'});
1221 - FlightReactDOM.preloadModule('lm before');
1222 - FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1223 - FlightReactDOM.preinit('i before', {as: 'script'});
1224 - FlightReactDOM.preinitModule('m before');
1225 - FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1226 - await 1;
1227 - FlightReactDOM.prefetchDNS('d after');
1228 - FlightReactDOM.preconnect('c after');
1229 - FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1230 - FlightReactDOM.preload('l after', {as: 'style'});
1231 - FlightReactDOM.preloadModule('lm after');
1232 - FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1233 - FlightReactDOM.preinit('i after', {as: 'script'});
1234 - FlightReactDOM.preinitModule('m after');
1235 - FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1236 - return <ClientComponent />;
1237 - }
1238 -
1239 - const {writable, readable} = getTestStream();
1240 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1241 - <ServerComponent />,
1242 - turbopackMap,
1243 - );
1244 - pipe(writable);
1245 -
1246 - let response = null;
1247 - function getResponse() {
1248 - if (response === null) {
1249 - response = ReactServerDOMClient.createFromReadableStream(readable);
1250 - }
1251 - return response;
1252 - }
1253 -
1254 - function App() {
1255 - return getResponse();
1256 - }
1257 -
1258 - // We pause to allow the float call after the await point to process before the
1259 - // HostDispatcher gets set for Fiber by createRoot. This is only needed in testing
1260 - // because the module graphs are not different and the HostDispatcher is shared.
1261 - // In a real environment the Fiber and Flight code would each have their own independent
1262 - // dispatcher.
1263 - // @TODO consider what happens when Server-Components-On-The-Client exist. we probably
1264 - // want to use the Fiber HostDispatcher there too since it is more about the host than the runtime
1265 - // but we need to make sure that actually makes sense
1266 - await 1;
1267 -
1268 - const container = document.createElement('div');
1269 - const root = ReactDOMClient.createRoot(container);
1270 - await act(() => {
1271 - root.render(<App />);
1272 - });
1273 -
1274 - expect(getMeaningfulChildren(document)).toEqual(
1275 - <html>
1276 - <head>
1277 - <link rel="dns-prefetch" href="d before" />
1278 - <link rel="preconnect" href="c before" />
1279 - <link rel="preconnect" href="c2 before" crossorigin="" />
1280 - <link rel="preload" as="style" href="l before" />
1281 - <link rel="modulepreload" href="lm before" />
1282 - <link rel="modulepreload" href="lm2 before" crossorigin="" />
1283 - <script async="" src="i before" />
1284 - <script type="module" async="" src="m before" />
1285 - <script type="module" async="" src="m2 before" crossorigin="" />
1286 - <link rel="dns-prefetch" href="d after" />
1287 - <link rel="preconnect" href="c after" />
1288 - <link rel="preconnect" href="c2 after" crossorigin="" />
1289 - <link rel="preload" as="style" href="l after" />
1290 - <link rel="modulepreload" href="lm after" />
1291 - <link rel="modulepreload" href="lm2 after" crossorigin="" />
1292 - <script async="" src="i after" />
1293 - <script type="module" async="" src="m after" />
1294 - <script type="module" async="" src="m2 after" crossorigin="" />
1295 - </head>
1296 - <body />
1297 - </html>,
1298 - );
1299 - expect(getMeaningfulChildren(container)).toEqual(<p>hello world</p>);
1300 - });
1301 -
1302 - it('should support float methods when rendering in Fizz', async () => {
1303 - function Component() {
1304 - return <p>hello world</p>;
1305 - }
1306 -
1307 - const ClientComponent = clientExports(Component);
1308 -
1309 - async function ServerComponent() {
1310 - FlightReactDOM.prefetchDNS('d before');
1311 - FlightReactDOM.preconnect('c before');
1312 - FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1313 - FlightReactDOM.preload('l before', {as: 'style'});
1314 - FlightReactDOM.preloadModule('lm before');
1315 - FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1316 - FlightReactDOM.preinit('i before', {as: 'script'});
1317 - FlightReactDOM.preinitModule('m before');
1318 - FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1319 - await 1;
1320 - FlightReactDOM.prefetchDNS('d after');
1321 - FlightReactDOM.preconnect('c after');
1322 - FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1323 - FlightReactDOM.preload('l after', {as: 'style'});
1324 - FlightReactDOM.preloadModule('lm after');
1325 - FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1326 - FlightReactDOM.preinit('i after', {as: 'script'});
1327 - FlightReactDOM.preinitModule('m after');
1328 - FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1329 - return <ClientComponent />;
1330 - }
1331 -
1332 - const {writable: flightWritable, readable: flightReadable} =
1333 - getTestStream();
1334 - const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
1335 -
1336 - // In a real environment you would want to call the render during the Fizz render.
1337 - // The reason we cannot do this in our test is because we don't actually have two separate
1338 - // module graphs and we are contriving the sequencing to work in a way where
1339 - // the right HostDispatcher is in scope during the Flight Server Float calls and the
1340 - // Flight Client hint dispatches
1341 - const {pipe} = ReactServerDOMServer.renderToPipeableStream(
1342 - <ServerComponent />,
1343 - turbopackMap,
1344 - );
1345 - pipe(flightWritable);
1346 -
1347 - let response = null;
1348 - function getResponse() {
1349 - if (response === null) {
1350 - response =
1351 - ReactServerDOMClient.createFromReadableStream(flightReadable);
1352 - }
1353 - return response;
1354 - }
1355 -
1356 - function App() {
1357 - return (
1358 - <html>
1359 - <body>{getResponse()}</body>
1360 - </html>
1361 - );
1362 - }
1363 -
1364 - await act(async () => {
1365 - ReactDOMFizzServer.renderToPipeableStream(<App />).pipe(fizzWritable);
1366 - });
1367 -
1368 - const decoder = new TextDecoder();
1369 - const reader = fizzReadable.getReader();
1370 - let content = '';
1371 - while (true) {
1372 - const {done, value} = await reader.read();
1373 - if (done) {
1374 - content += decoder.decode();
1375 - break;
1376 - }
1377 - content += decoder.decode(value, {stream: true});
1378 - }
1379 -
1380 - const doc = new JSDOM(content).window.document;
1381 - expect(getMeaningfulChildren(doc)).toEqual(
1382 - <html>
1383 - <head>
1384 - <link rel="dns-prefetch" href="d before" />
1385 - <link rel="preconnect" href="c before" />
1386 - <link rel="preconnect" href="c2 before" crossorigin="" />
1387 - <link rel="dns-prefetch" href="d after" />
1388 - <link rel="preconnect" href="c after" />
1389 - <link rel="preconnect" href="c2 after" crossorigin="" />
1390 - <script async="" src="i before" />
1391 - <script type="module" async="" src="m before" />
1392 - <script type="module" async="" src="m2 before" crossorigin="" />
1393 - <script async="" src="i after" />
1394 - <script type="module" async="" src="m after" />
1395 - <script type="module" async="" src="m2 after" crossorigin="" />
1396 - <link rel="preload" as="style" href="l before" />
1397 - <link rel="modulepreload" href="lm before" />
1398 - <link rel="modulepreload" href="lm2 before" crossorigin="" />
1399 - <link rel="preload" as="style" href="l after" />
1400 - <link rel="modulepreload" href="lm after" />
1401 - <link rel="modulepreload" href="lm2 after" crossorigin="" />
1402 - </head>
1403 - <body>
1404 - <p>hello world</p>
1405 - </body>
1406 - </html>,
1407 - );
1408 - });
1409 -
1410 - it('supports Float hints from concurrent Flight -> Fizz renders', async () => {
1411 - function Component() {
1412 - return <p>hello world</p>;
1413 - }
1414 -
1415 - const ClientComponent = clientExports(Component);
1416 -
1417 - async function ServerComponent1() {
1418 - FlightReactDOM.preload('before1', {as: 'style'});
1419 - await 1;
1420 - FlightReactDOM.preload('after1', {as: 'style'});
1421 - return <ClientComponent />;
1422 - }
1423 -
1424 - async function ServerComponent2() {
1425 - FlightReactDOM.preload('before2', {as: 'style'});
1426 - await 1;
1427 - FlightReactDOM.preload('after2', {as: 'style'});
1428 - return <ClientComponent />;
1429 - }
1430 -
1431 - const {writable: flightWritable1, readable: flightReadable1} =
1432 - getTestStream();
1433 - const {writable: flightWritable2, readable: flightReadable2} =
1434 - getTestStream();
1435 -
1436 - ReactServerDOMServer.renderToPipeableStream(
1437 - <ServerComponent1 />,
1438 - turbopackMap,
1439 - ).pipe(flightWritable1);
1440 -
1441 - ReactServerDOMServer.renderToPipeableStream(
1442 - <ServerComponent2 />,
1443 - turbopackMap,
1444 - ).pipe(flightWritable2);
1445 -
1446 - const responses = new Map();
1447 - function getResponse(stream) {
1448 - let response = responses.get(stream);
1449 - if (!response) {
1450 - response = ReactServerDOMClient.createFromReadableStream(stream);
1451 - responses.set(stream, response);
1452 - }
1453 - return response;
1454 - }
1455 -
1456 - function App({stream}) {
1457 - return (
1458 - <html>
1459 - <body>{getResponse(stream)}</body>
1460 - </html>
1461 - );
1462 - }
1463 -
1464 - // pausing to let Flight runtime tick. This is a test only artifact of the fact that
1465 - // we aren't operating separate module graphs for flight and fiber. In a real app
1466 - // each would have their own dispatcher and there would be no cross dispatching.
1467 - await 1;
1468 -
1469 - const {writable: fizzWritable1, readable: fizzReadable1} = getTestStream();
1470 - const {writable: fizzWritable2, readable: fizzReadable2} = getTestStream();
1471 - await act(async () => {
1472 - ReactDOMFizzServer.renderToPipeableStream(
1473 - <App stream={flightReadable1} />,
1474 - ).pipe(fizzWritable1);
1475 - ReactDOMFizzServer.renderToPipeableStream(
1476 - <App stream={flightReadable2} />,
1477 - ).pipe(fizzWritable2);
1478 - });
1479 -
1480 - async function read(stream) {
1481 - const decoder = new TextDecoder();
1482 - const reader = stream.getReader();
1483 - let buffer = '';
1484 - while (true) {
1485 - const {done, value} = await reader.read();
1486 - if (done) {
1487 - buffer += decoder.decode();
1488 - break;
1489 - }
1490 - buffer += decoder.decode(value, {stream: true});
1491 - }
1492 - return buffer;
1493 - }
1494 -
1495 - const [content1, content2] = await Promise.all([
1496 - read(fizzReadable1),
1497 - read(fizzReadable2),
1498 - ]);
1499 -
1500 - expect(content1).toEqual(
1501 - '<!DOCTYPE html><html><head><link rel="preload" href="before1" as="style"/>' +
1502 - '<link rel="preload" href="after1" as="style"/></head><body><p>hello world</p></body></html>',
1503 - );
1504 - expect(content2).toEqual(
1505 - '<!DOCTYPE html><html><head><link rel="preload" href="before2" as="style"/>' +
1506 - '<link rel="preload" href="after2" as="style"/></head><body><p>hello world</p></body></html>',
1507 - );
1508 - });
1509 -
1510 - it('supports deduping hints by Float key', async () => {
1511 - function Component() {
1512 - return <p>hello world</p>;
1513 - }
1514 -
1515 - const ClientComponent = clientExports(Component);
1516 -
1517 - async function ServerComponent() {
1518 - FlightReactDOM.prefetchDNS('dns');
1519 - FlightReactDOM.preconnect('preconnect');
1520 - FlightReactDOM.preload('load', {as: 'style'});
1521 - FlightReactDOM.preinit('init', {as: 'script'});
1522 - // again but vary preconnect to demonstrate crossOrigin participates in the key
1523 - FlightReactDOM.prefetchDNS('dns');
1524 - FlightReactDOM.preconnect('preconnect', {crossOrigin: 'anonymous'});
1525 - FlightReactDOM.preload('load', {as: 'style'});
1526 - FlightReactDOM.preinit('init', {as: 'script'});
1527 - await 1;
1528 - // after an async point
1529 - FlightReactDOM.prefetchDNS('dns');
1530 - FlightReactDOM.preconnect('preconnect', {crossOrigin: 'use-credentials'});
1531 - FlightReactDOM.preload('load', {as: 'style'});
1532 - FlightReactDOM.preinit('init', {as: 'script'});
1533 - return <ClientComponent />;
1534 - }
1535 -
1536 - const {writable, readable} = getTestStream();
1537 -
1538 - ReactServerDOMServer.renderToPipeableStream(
1539 - <ServerComponent />,
1540 - turbopackMap,
1541 - ).pipe(writable);
1542 -
1543 - const hintRows = [];
1544 - async function collectHints(stream) {
1545 - const decoder = new TextDecoder();
1546 - const reader = stream.getReader();
1547 - let buffer = '';
1548 - while (true) {
1549 - const {done, value} = await reader.read();
1550 - if (done) {
1551 - buffer += decoder.decode();
1552 - if (buffer.includes(':H')) {
1553 - hintRows.push(buffer);
1554 - }
1555 - break;
1556 - }
1557 - buffer += decoder.decode(value, {stream: true});
1558 - let line;
1559 - while ((line = buffer.indexOf('\n')) > -1) {
1560 - const row = buffer.slice(0, line);
1561 - buffer = buffer.slice(line + 1);
1562 - if (row.includes(':H')) {
1563 - hintRows.push(row);
1564 - }
1565 - }
1566 - }
1567 - }
1568 -
1569 - await collectHints(readable);
1570 - expect(hintRows.length).toEqual(6);
1571 - });
1572 -});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMNode-test.js deleted
-256
@@ -1,256 +0,0 @@
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 -// Don't wait before processing work on the server.
13 -// TODO: we can replace this with FlightServer.act().
14 -global.setImmediate = cb => cb();
15 -
16 -let clientExports;
17 -let turbopackMap;
18 -let turbopackModules;
19 -let turbopackModuleLoading;
20 -let React;
21 -let ReactDOMServer;
22 -let ReactServerDOMServer;
23 -let ReactServerDOMClient;
24 -let Stream;
25 -let use;
26 -
27 -describe('ReactFlightDOMNode', () => {
28 - beforeEach(() => {
29 - jest.resetModules();
30 -
31 - // Simulate the condition resolution
32 - jest.mock('react', () => require('react/react.shared-subset'));
33 - jest.mock('react-server-dom-turbopack/server', () =>
34 - require('react-server-dom-turbopack/server.node'),
35 - );
36 - ReactServerDOMServer = require('react-server-dom-turbopack/server');
37 -
38 - const TurbopackMock = require('./utils/TurbopackMock');
39 - clientExports = TurbopackMock.clientExports;
40 - turbopackMap = TurbopackMock.turbopackMap;
41 - turbopackModules = TurbopackMock.turbopackModules;
42 - turbopackModuleLoading = TurbopackMock.moduleLoading;
43 -
44 - jest.resetModules();
45 - __unmockReact();
46 - jest.unmock('react-server-dom-turbopack/server');
47 - jest.mock('react-server-dom-turbopack/client', () =>
48 - require('react-server-dom-turbopack/client.node'),
49 - );
50 -
51 - React = require('react');
52 - ReactDOMServer = require('react-dom/server.node');
53 - ReactServerDOMClient = require('react-server-dom-turbopack/client');
54 - Stream = require('stream');
55 - use = React.use;
56 - });
57 -
58 - function readResult(stream) {
59 - return new Promise((resolve, reject) => {
60 - let buffer = '';
61 - const writable = new Stream.PassThrough();
62 - writable.setEncoding('utf8');
63 - writable.on('data', chunk => {
64 - buffer += chunk;
65 - });
66 - writable.on('error', error => {
67 - reject(error);
68 - });
69 - writable.on('end', () => {
70 - resolve(buffer);
71 - });
72 - stream.pipe(writable);
73 - });
74 - }
75 -
76 - it('should allow an alternative module mapping to be used for SSR', async () => {
77 - function ClientComponent() {
78 - return <span>Client Component</span>;
79 - }
80 - // The Client build may not have the same IDs as the Server bundles for the same
81 - // component.
82 - const ClientComponentOnTheClient = clientExports(
83 - ClientComponent,
84 - 'path/to/chunk.js',
85 - );
86 - const ClientComponentOnTheServer = clientExports(ClientComponent);
87 -
88 - // In the SSR bundle this module won't exist. We simulate this by deleting it.
89 - const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
90 - delete turbopackModules[clientId];
91 -
92 - // Instead, we have to provide a translation from the client meta data to the SSR
93 - // meta data.
94 - const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
95 - const translationMap = {
96 - [clientId]: {
97 - '*': ssrMetadata,
98 - },
99 - };
100 -
101 - function App() {
102 - return <ClientComponentOnTheClient />;
103 - }
104 -
105 - const stream = ReactServerDOMServer.renderToPipeableStream(
106 - <App />,
107 - turbopackMap,
108 - );
109 - const readable = new Stream.PassThrough();
110 -
111 - stream.pipe(readable);
112 -
113 - let response;
114 - function ClientRoot() {
115 - if (!response) {
116 - response = ReactServerDOMClient.createFromNodeStream(readable, {
117 - moduleMap: translationMap,
118 - moduleLoading: turbopackModuleLoading,
119 - });
120 - }
121 - return use(response);
122 - }
123 -
124 - const ssrStream = await ReactDOMServer.renderToPipeableStream(
125 - <ClientRoot />,
126 - );
127 - const result = await readResult(ssrStream);
128 - expect(result).toEqual(
129 - '<script src="/prefix/path/to/chunk.js" async=""></script><span>Client Component</span>',
130 - );
131 - });
132 -
133 - it('should encode long string in a compact format', async () => {
134 - const testString = '"\n\t'.repeat(500) + '🙃';
135 -
136 - const stream = ReactServerDOMServer.renderToPipeableStream({
137 - text: testString,
138 - });
139 -
140 - const readable = new Stream.PassThrough();
141 -
142 - const stringResult = readResult(readable);
143 - const parsedResult = ReactServerDOMClient.createFromNodeStream(readable, {
144 - moduleMap: turbopackMap,
145 - moduleLoading: turbopackModuleLoading,
146 - });
147 -
148 - stream.pipe(readable);
149 -
150 - const serializedContent = await stringResult;
151 - // The content should be compact an unescaped
152 - expect(serializedContent.length).toBeLessThan(2000);
153 - expect(serializedContent).not.toContain('\\n');
154 - expect(serializedContent).not.toContain('\\t');
155 - expect(serializedContent).not.toContain('\\"');
156 - expect(serializedContent).toContain('\t');
157 -
158 - const result = await parsedResult;
159 - // Should still match the result when parsed
160 - expect(result.text).toBe(testString);
161 - });
162 -
163 - // @gate enableBinaryFlight
164 - it('should be able to serialize any kind of typed array', async () => {
165 - const buffer = new Uint8Array([
166 - 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
167 - ]).buffer;
168 - const buffers = [
169 - buffer,
170 - new Int8Array(buffer, 1),
171 - new Uint8Array(buffer, 2),
172 - new Uint8ClampedArray(buffer, 2),
173 - new Int16Array(buffer, 2),
174 - new Uint16Array(buffer, 2),
175 - new Int32Array(buffer, 4),
176 - new Uint32Array(buffer, 4),
177 - new Float32Array(buffer, 4),
178 - new Float64Array(buffer, 0),
179 - new BigInt64Array(buffer, 0),
180 - new BigUint64Array(buffer, 0),
181 - new DataView(buffer, 3),
182 - ];
183 - const stream = ReactServerDOMServer.renderToPipeableStream(buffers);
184 - const readable = new Stream.PassThrough();
185 - const promise = ReactServerDOMClient.createFromNodeStream(readable, {
186 - moduleMap: turbopackMap,
187 - moduleLoading: turbopackModuleLoading,
188 - });
189 - stream.pipe(readable);
190 - const result = await promise;
191 - expect(result).toEqual(buffers);
192 - });
193 -
194 - it('should allow accept a nonce option for Flight preinitialized scripts', async () => {
195 - function ClientComponent() {
196 - return <span>Client Component</span>;
197 - }
198 - // The Client build may not have the same IDs as the Server bundles for the same
199 - // component.
200 - const ClientComponentOnTheClient = clientExports(
201 - ClientComponent,
202 - 'path/to/chunk.js',
203 - );
204 - const ClientComponentOnTheServer = clientExports(ClientComponent);
205 -
206 - // In the SSR bundle this module won't exist. We simulate this by deleting it.
207 - const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
208 - delete turbopackModules[clientId];
209 -
210 - // Instead, we have to provide a translation from the client meta data to the SSR
211 - // meta data.
212 - const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
213 - const translationMap = {
214 - [clientId]: {
215 - '*': ssrMetadata,
216 - },
217 - };
218 - const ssrManifest = {
219 - moduleMap: translationMap,
220 - moduleLoading: turbopackModuleLoading,
221 - };
222 -
223 - function App() {
224 - return <ClientComponentOnTheClient />;
225 - }
226 -
227 - const stream = ReactServerDOMServer.renderToPipeableStream(
228 - <App />,
229 - turbopackMap,
230 - );
231 - const readable = new Stream.PassThrough();
232 - let response;
233 -
234 - stream.pipe(readable);
235 -
236 - function ClientRoot() {
237 - if (response) return use(response);
238 - response = ReactServerDOMClient.createFromNodeStream(
239 - readable,
240 - ssrManifest,
241 - {
242 - nonce: 'r4nd0m',
243 - },
244 - );
245 - return use(response);
246 - }
247 -
248 - const ssrStream = await ReactDOMServer.renderToPipeableStream(
249 - <ClientRoot />,
250 - );
251 - const result = await readResult(ssrStream);
252 - expect(result).toEqual(
253 - '<script src="/prefix/path/to/chunk.js" async="" nonce="r4nd0m"></script><span>Client Component</span>',
254 - );
255 - });
256 -});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightDOMReply-test.js deleted
-234
@@ -1,234 +0,0 @@
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.TextDecoder = require('util').TextDecoder;
17 -
18 -// let serverExports;
19 -let turbopackServerMap;
20 -let ReactServerDOMServer;
21 -let ReactServerDOMClient;
22 -
23 -describe('ReactFlightDOMReply', () => {
24 - beforeEach(() => {
25 - jest.resetModules();
26 - // Simulate the condition resolution
27 - jest.mock('react', () => require('react/react.shared-subset'));
28 - jest.mock('react-server-dom-turbopack/server', () =>
29 - require('react-server-dom-turbopack/server.browser'),
30 - );
31 - const TurbopackMock = require('./utils/TurbopackMock');
32 - // serverExports = TurbopackMock.serverExports;
33 - turbopackServerMap = TurbopackMock.turbopackServerMap;
34 - ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
35 - jest.resetModules();
36 - ReactServerDOMClient = require('react-server-dom-turbopack/client');
37 - });
38 -
39 - // This method should exist on File but is not implemented in JSDOM
40 - async function arrayBuffer(file) {
41 - return new Promise((resolve, reject) => {
42 - const reader = new FileReader();
43 - reader.onload = function () {
44 - return resolve(reader.result);
45 - };
46 - reader.onerror = function () {
47 - return reject(reader.error);
48 - };
49 - reader.readAsArrayBuffer(file);
50 - });
51 - }
52 -
53 - it('can pass undefined as a reply', async () => {
54 - const body = await ReactServerDOMClient.encodeReply(undefined);
55 - const missing = await ReactServerDOMServer.decodeReply(
56 - body,
57 - turbopackServerMap,
58 - );
59 - expect(missing).toBe(undefined);
60 -
61 - const body2 = await ReactServerDOMClient.encodeReply({
62 - array: [undefined, null, undefined],
63 - prop: undefined,
64 - });
65 - const object = await ReactServerDOMServer.decodeReply(
66 - body2,
67 - turbopackServerMap,
68 - );
69 - expect(object.array.length).toBe(3);
70 - expect(object.array[0]).toBe(undefined);
71 - expect(object.array[1]).toBe(null);
72 - expect(object.array[3]).toBe(undefined);
73 - expect(object.prop).toBe(undefined);
74 - // These should really be true but our deserialization doesn't currently deal with it.
75 - expect('3' in object.array).toBe(false);
76 - expect('prop' in object).toBe(false);
77 - });
78 -
79 - it('can pass an iterable as a reply', async () => {
80 - const body = await ReactServerDOMClient.encodeReply({
81 - [Symbol.iterator]: function* () {
82 - yield 'A';
83 - yield 'B';
84 - yield 'C';
85 - },
86 - });
87 - const iterable = await ReactServerDOMServer.decodeReply(
88 - body,
89 - turbopackServerMap,
90 - );
91 - const items = [];
92 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
93 - for (const item of iterable) {
94 - items.push(item);
95 - }
96 - expect(items).toEqual(['A', 'B', 'C']);
97 - });
98 -
99 - it('can pass weird numbers as a reply', async () => {
100 - const nums = [0, -0, Infinity, -Infinity, NaN];
101 - const body = await ReactServerDOMClient.encodeReply(nums);
102 - const nums2 = await ReactServerDOMServer.decodeReply(
103 - body,
104 - turbopackServerMap,
105 - );
106 -
107 - expect(nums).toEqual(nums2);
108 - expect(nums.every((n, i) => Object.is(n, nums2[i]))).toBe(true);
109 - });
110 -
111 - it('can pass a BigInt as a reply', async () => {
112 - const body = await ReactServerDOMClient.encodeReply(90071992547409910000n);
113 - const n = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
114 -
115 - expect(n).toEqual(90071992547409910000n);
116 - });
117 -
118 - it('can pass FormData as a reply', async () => {
119 - const formData = new FormData();
120 - formData.set('hello', 'world');
121 - formData.append('list', '1');
122 - formData.append('list', '2');
123 - formData.append('list', '3');
124 - const typedArray = new Uint8Array([0, 1, 2, 3]);
125 - const blob = new Blob([typedArray]);
126 - formData.append('blob', blob, 'filename.blob');
127 -
128 - const body = await ReactServerDOMClient.encodeReply(formData);
129 - const formData2 = await ReactServerDOMServer.decodeReply(
130 - body,
131 - turbopackServerMap,
132 - );
133 -
134 - expect(formData2).not.toBe(formData);
135 - expect(Array.from(formData2).length).toBe(5);
136 - expect(formData2.get('hello')).toBe('world');
137 - expect(formData2.getAll('list')).toEqual(['1', '2', '3']);
138 - const blob2 = formData.get('blob');
139 - expect(blob2.size).toBe(4);
140 - expect(blob2.name).toBe('filename.blob');
141 - expect(blob2.type).toBe('');
142 - const typedArray2 = new Uint8Array(await arrayBuffer(blob2));
143 - expect(typedArray2).toEqual(typedArray);
144 - });
145 -
146 - it('can pass multiple Files in FormData', async () => {
147 - const typedArrayA = new Uint8Array([0, 1, 2, 3]);
148 - const typedArrayB = new Uint8Array([4, 5]);
149 - const blobA = new Blob([typedArrayA]);
150 - const blobB = new Blob([typedArrayB]);
151 - const formData = new FormData();
152 - formData.append('filelist', 'string');
153 - formData.append('filelist', blobA);
154 - formData.append('filelist', blobB);
155 -
156 - const body = await ReactServerDOMClient.encodeReply(formData);
157 - const formData2 = await ReactServerDOMServer.decodeReply(
158 - body,
159 - turbopackServerMap,
160 - );
161 -
162 - const filelist2 = formData2.getAll('filelist');
163 - expect(filelist2.length).toBe(3);
164 - expect(filelist2[0]).toBe('string');
165 - const blobA2 = filelist2[1];
166 - expect(blobA2.size).toBe(4);
167 - expect(blobA2.name).toBe('blob');
168 - expect(blobA2.type).toBe('');
169 - const typedArrayA2 = new Uint8Array(await arrayBuffer(blobA2));
170 - expect(typedArrayA2).toEqual(typedArrayA);
171 - const blobB2 = filelist2[2];
172 - expect(blobB2.size).toBe(2);
173 - expect(blobB2.name).toBe('blob');
174 - expect(blobB2.type).toBe('');
175 - const typedArrayB2 = new Uint8Array(await arrayBuffer(blobB2));
176 - expect(typedArrayB2).toEqual(typedArrayB);
177 - });
178 -
179 - it('can pass two independent FormData with same keys', async () => {
180 - const formDataA = new FormData();
181 - formDataA.set('greeting', 'hello');
182 - const formDataB = new FormData();
183 - formDataB.set('greeting', 'hi');
184 -
185 - const body = await ReactServerDOMClient.encodeReply({
186 - a: formDataA,
187 - b: formDataB,
188 - });
189 - const {a: formDataA2, b: formDataB2} =
190 - await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
191 -
192 - expect(Array.from(formDataA2).length).toBe(1);
193 - expect(Array.from(formDataB2).length).toBe(1);
194 - expect(formDataA2.get('greeting')).toBe('hello');
195 - expect(formDataB2.get('greeting')).toBe('hi');
196 - });
197 -
198 - it('can pass a Date as a reply', async () => {
199 - const d = new Date(1234567890123);
200 - const body = await ReactServerDOMClient.encodeReply(d);
201 - const d2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
202 -
203 - expect(d).toEqual(d2);
204 - expect(d % 1000).toEqual(123); // double-check the milliseconds made it through
205 - });
206 -
207 - it('can pass a Map as a reply', async () => {
208 - const objKey = {obj: 'key'};
209 - const m = new Map([
210 - ['hi', {greet: 'world'}],
211 - [objKey, 123],
212 - ]);
213 - const body = await ReactServerDOMClient.encodeReply(m);
214 - const m2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
215 -
216 - expect(m2 instanceof Map).toBe(true);
217 - expect(m2.size).toBe(2);
218 - expect(m2.get('hi').greet).toBe('world');
219 - expect(m2).toEqual(m);
220 - });
221 -
222 - it('can pass a Set as a reply', async () => {
223 - const objKey = {obj: 'key'};
224 - const s = new Set(['hi', objKey]);
225 -
226 - const body = await ReactServerDOMClient.encodeReply(s);
227 - const s2 = await ReactServerDOMServer.decodeReply(body, turbopackServerMap);
228 -
229 - expect(s2 instanceof Set).toBe(true);
230 - expect(s2.size).toBe(2);
231 - expect(s2.has('hi')).toBe(true);
232 - expect(s2).toEqual(s);
233 - });
234 -});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js new
+208
@@ -0,0 +1,208 @@
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.TextDecoder = require('util').TextDecoder;
17 +
18 +// Don't wait before processing work on the server.
19 +// TODO: we can replace this with FlightServer.act().
20 +global.setImmediate = cb => cb();
21 +
22 +let act;
23 +let use;
24 +let clientExports;
25 +let turbopackMap;
26 +let Stream;
27 +let React;
28 +let ReactDOMClient;
29 +let ReactServerDOMServer;
30 +let ReactServerDOMClient;
31 +let Suspense;
32 +
33 +describe('ReactFlightDOM', () => {
34 + beforeEach(() => {
35 + // For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
36 + // This can be thought of as essentially being the React Server Components scope with react-server
37 + // condition
38 + jest.resetModules();
39 +
40 + // Simulate the condition resolution
41 + jest.mock('react-server-dom-turbopack/server', () =>
42 + require('react-server-dom-turbopack/server.node.unbundled'),
43 + );
44 + jest.mock('react', () => require('react/react.shared-subset'));
45 +
46 + const TurbopackMock = require('./utils/TurbopackMock');
47 + clientExports = TurbopackMock.clientExports;
48 + turbopackMap = TurbopackMock.turbopackMap;
49 +
50 + ReactServerDOMServer = require('react-server-dom-turbopack/server');
51 +
52 + // This reset is to load modules for the SSR/Browser scope.
53 + jest.resetModules();
54 + __unmockReact();
55 + act = require('internal-test-utils').act;
56 + Stream = require('stream');
57 + React = require('react');
58 + use = React.use;
59 + Suspense = React.Suspense;
60 + ReactDOMClient = require('react-dom/client');
61 + ReactServerDOMClient = require('react-server-dom-turbopack/client');
62 + });
63 +
64 + function getTestStream() {
65 + const writable = new Stream.PassThrough();
66 + const readable = new ReadableStream({
67 + start(controller) {
68 + writable.on('data', chunk => {
69 + controller.enqueue(chunk);
70 + });
71 + writable.on('end', () => {
72 + controller.close();
73 + });
74 + },
75 + });
76 + return {
77 + readable,
78 + writable,
79 + };
80 + }
81 +
82 + it('should resolve HTML using Node streams', async () => {
83 + function Text({children}) {
84 + return <span>{children}</span>;
85 + }
86 + function HTML() {
87 + return (
88 + <div>
89 + <Text>hello</Text>
90 + <Text>world</Text>
91 + </div>
92 + );
93 + }
94 +
95 + function App() {
96 + const model = {
97 + html: <HTML />,
98 + };
99 + return model;
100 + }
101 +
102 + const {writable, readable} = getTestStream();
103 + const {pipe} = ReactServerDOMServer.renderToPipeableStream(
104 + <App />,
105 + turbopackMap,
106 + );
107 + pipe(writable);
108 + const response = ReactServerDOMClient.createFromReadableStream(readable);
109 + const model = await response;
110 + expect(model).toEqual({
111 + html: (
112 + <div>
113 + <span>hello</span>
114 + <span>world</span>
115 + </div>
116 + ),
117 + });
118 + });
119 +
120 + it('should resolve the root', async () => {
121 + // Model
122 + function Text({children}) {
123 + return <span>{children}</span>;
124 + }
125 + function HTML() {
126 + return (
127 + <div>
128 + <Text>hello</Text>
129 + <Text>world</Text>
130 + </div>
131 + );
132 + }
133 + function RootModel() {
134 + return {
135 + html: <HTML />,
136 + };
137 + }
138 +
139 + // View
140 + function Message({response}) {
141 + return <section>{use(response).html}</section>;
142 + }
143 + function App({response}) {
144 + return (
145 + <Suspense fallback={<h1>Loading...</h1>}>
146 + <Message response={response} />
147 + </Suspense>
148 + );
149 + }
150 +
151 + const {writable, readable} = getTestStream();
152 + const {pipe} = ReactServerDOMServer.renderToPipeableStream(
153 + <RootModel />,
154 + turbopackMap,
155 + );
156 + pipe(writable);
157 + const response = ReactServerDOMClient.createFromReadableStream(readable);
158 +
159 + const container = document.createElement('div');
160 + const root = ReactDOMClient.createRoot(container);
161 + await act(() => {
162 + root.render(<App response={response} />);
163 + });
164 + expect(container.innerHTML).toBe(
165 + '<section><div><span>hello</span><span>world</span></div></section>',
166 + );
167 + });
168 +
169 + it('should unwrap async module references', async () => {
170 + const AsyncModule = Promise.resolve(function AsyncModule({text}) {
171 + return 'Async: ' + text;
172 + });
173 +
174 + const AsyncModule2 = Promise.resolve({
175 + exportName: 'Module',
176 + });
177 +
178 + function Print({response}) {
179 + return <p>{use(response)}</p>;
180 + }
181 +
182 + function App({response}) {
183 + return (
184 + <Suspense fallback={<h1>Loading...</h1>}>
185 + <Print response={response} />
186 + </Suspense>
187 + );
188 + }
189 +
190 + const AsyncModuleRef = await clientExports(AsyncModule);
191 + const AsyncModuleRef2 = await clientExports(AsyncModule2);
192 +
193 + const {writable, readable} = getTestStream();
194 + const {pipe} = ReactServerDOMServer.renderToPipeableStream(
195 + <AsyncModuleRef text={AsyncModuleRef2.exportName} />,
196 + turbopackMap,
197 + );
198 + pipe(writable);
199 + const response = ReactServerDOMClient.createFromReadableStream(readable);
200 +
201 + const container = document.createElement('div');
202 + const root = ReactDOMClient.createRoot(container);
203 + await act(() => {
204 + root.render(<App response={response} />);
205 + });
206 + expect(container.innerHTML).toBe('<p>Async: Module</p>');
207 + });
208 +});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMBrowser-test.js renamed
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMEdge-test.js renamed
-95
@@ -54,37 +54,6 @@ describe('ReactFlightDOMEdge', () => {
54 use = React.use;
55 });
56
57 - function passThrough(stream) {
58 - // Simulate more realistic network by splitting up and rejoining some chunks.
59 - // This lets us test that we don't accidentally rely on particular bounds of the chunks.
60 - return new ReadableStream({
61 - async start(controller) {
62 - const reader = stream.getReader();
63 - let prevChunk = new Uint8Array(0);
64 - function push() {
65 - reader.read().then(({done, value}) => {
66 - if (done) {
67 - controller.enqueue(prevChunk);
68 - controller.close();
69 - return;
70 - }
71 - const chunk = new Uint8Array(prevChunk.length + value.length);
72 - chunk.set(prevChunk, 0);
73 - chunk.set(value, prevChunk.length);
74 - if (chunk.length > 50) {
75 - controller.enqueue(chunk.subarray(0, chunk.length - 50));
76 - prevChunk = chunk.subarray(chunk.length - 50);
77 - } else {
78 - prevChunk = chunk;
79 - }
80 - push();
81 - });
82 - }
83 - push();
84 - },
85 - });
86 - }
87 -
57 async function readResult(stream) {
58 const reader = stream.getReader();
59 let result = '';
@@ -144,68 +113,4 @@ describe('ReactFlightDOMEdge', () => {
113 const result = await readResult(ssrStream);
114 expect(result).toEqual('<span>Client Component</span>');
115 });
147 -
148 - it('should encode long string in a compact format', async () => {
149 - const testString = '"\n\t'.repeat(500) + '🙃';
150 - const testString2 = 'hello'.repeat(400);
151 -
152 - const stream = ReactServerDOMServer.renderToReadableStream({
153 - text: testString,
154 - text2: testString2,
155 - });
156 - const [stream1, stream2] = passThrough(stream).tee();
157 -
158 - const serializedContent = await readResult(stream1);
159 - // The content should be compact an unescaped
160 - expect(serializedContent.length).toBeLessThan(4000);
161 - expect(serializedContent).not.toContain('\\n');
162 - expect(serializedContent).not.toContain('\\t');
163 - expect(serializedContent).not.toContain('\\"');
164 - expect(serializedContent).toContain('\t');
165 -
166 - const result = await ReactServerDOMClient.createFromReadableStream(
167 - stream2,
168 - {
169 - ssrManifest: {
170 - moduleMap: null,
171 - moduleLoading: null,
172 - },
173 - },
174 - );
175 - // Should still match the result when parsed
176 - expect(result.text).toBe(testString);
177 - expect(result.text2).toBe(testString2);
178 - });
179 -
180 - // @gate enableBinaryFlight
181 - it('should be able to serialize any kind of typed array', async () => {
182 - const buffer = new Uint8Array([
183 - 123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
184 - ]).buffer;
185 - const buffers = [
186 - buffer,
187 - new Int8Array(buffer, 1),
188 - new Uint8Array(buffer, 2),
189 - new Uint8ClampedArray(buffer, 2),
190 - new Int16Array(buffer, 2),
191 - new Uint16Array(buffer, 2),
192 - new Int32Array(buffer, 4),
193 - new Uint32Array(buffer, 4),
194 - new Float32Array(buffer, 4),
195 - new Float64Array(buffer, 0),
196 - new BigInt64Array(buffer, 0),
197 - new BigUint64Array(buffer, 0),
198 - new DataView(buffer, 3),
199 - ];
200 - const stream = passThrough(
201 - ReactServerDOMServer.renderToReadableStream(buffers),
202 - );
203 - const result = await ReactServerDOMClient.createFromReadableStream(stream, {
204 - ssrManifest: {
205 - moduleMap: null,
206 - moduleLoading: null,
207 - },
208 - });
209 - expect(result).toEqual(buffers);
210 - });
116 });
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMForm-test.js renamed
-109
@@ -145,113 +145,4 @@ describe('ReactFlightDOMForm', () => {
145 expect(result).toBe('hello');
146 expect(foo).toBe('bar');
147 });
148 -
149 - // @gate enableFormActions
150 - it('can submit an imported server action without hydrating it', async () => {
151 - let foo = null;
152 -
153 - const ServerModule = serverExports(function action(formData) {
154 - foo = formData.get('foo');
155 - return 'hi';
156 - });
157 - const serverAction = ReactServerDOMClient.createServerReference(
158 - ServerModule.$$id,
159 - );
160 - function App() {
161 - return (
162 - <form action={serverAction}>
163 - <input type="text" name="foo" defaultValue="bar" />
164 - </form>
165 - );
166 - }
167 -
168 - const ssrStream = await ReactDOMServer.renderToReadableStream(<App />);
169 - await readIntoContainer(ssrStream);
170 -
171 - const form = container.firstChild;
172 -
173 - expect(foo).toBe(null);
174 -
175 - const result = await submit(form);
176 -
177 - expect(result).toBe('hi');
178 -
179 - expect(foo).toBe('bar');
180 - });
181 -
182 - // @gate enableFormActions
183 - it('can submit a complex closure server action without hydrating it', async () => {
184 - let foo = null;
185 -
186 - const serverAction = serverExports(function action(bound, formData) {
187 - foo = formData.get('foo') + bound.complex;
188 - return 'hello';
189 - });
190 - function App() {
191 - return (
192 - <form action={serverAction.bind(null, {complex: 'object'})}>
193 - <input type="text" name="foo" defaultValue="bar" />
194 - </form>
195 - );
196 - }
197 - const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
198 - const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
199 - ssrManifest: {
200 - moduleMap: null,
201 - moduleLoading: null,
202 - },
203 - });
204 - const ssrStream = await ReactDOMServer.renderToReadableStream(response);
205 - await readIntoContainer(ssrStream);
206 -
207 - const form = container.firstChild;
208 -
209 - expect(foo).toBe(null);
210 -
211 - const result = await submit(form);
212 -
213 - expect(result).toBe('hello');
214 - expect(foo).toBe('barobject');
215 - });
216 -
217 - // @gate enableFormActions
218 - it('can submit a multiple complex closure server action without hydrating it', async () => {
219 - let foo = null;
220 -
221 - const serverAction = serverExports(function action(bound, formData) {
222 - foo = formData.get('foo') + bound.complex;
223 - return 'hello' + bound.complex;
224 - });
225 - function App() {
226 - return (
227 - <form action={serverAction.bind(null, {complex: 'a'})}>
228 - <input type="text" name="foo" defaultValue="bar" />
229 - <button formAction={serverAction.bind(null, {complex: 'b'})} />
230 - <button formAction={serverAction.bind(null, {complex: 'c'})} />
231 - <input
232 - type="submit"
233 - formAction={serverAction.bind(null, {complex: 'd'})}
234 - />
235 - </form>
236 - );
237 - }
238 - const rscStream = ReactServerDOMServer.renderToReadableStream(<App />);
239 - const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
240 - ssrManifest: {
241 - moduleMap: null,
242 - moduleLoading: null,
243 - },
244 - });
245 - const ssrStream = await ReactDOMServer.renderToReadableStream(response);
246 - await readIntoContainer(ssrStream);
247 -
248 - const form = container.firstChild;
249 -
250 - expect(foo).toBe(null);
251 -
252 - const result = await submit(form.getElementsByTagName('button')[1]);
253 -
254 - expect(result).toBe('helloc');
255 - expect(foo).toBe('barc');
256 - });
148 });
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMNode-test.js new
+132
@@ -0,0 +1,132 @@
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 +// Don't wait before processing work on the server.
13 +// TODO: we can replace this with FlightServer.act().
14 +global.setImmediate = cb => cb();
15 +
16 +let clientExports;
17 +let turbopackMap;
18 +let turbopackModules;
19 +let turbopackModuleLoading;
20 +let React;
21 +let ReactDOMServer;
22 +let ReactServerDOMServer;
23 +let ReactServerDOMClient;
24 +let Stream;
25 +let use;
26 +
27 +describe('ReactFlightDOMNode', () => {
28 + beforeEach(() => {
29 + jest.resetModules();
30 +
31 + // Simulate the condition resolution
32 + jest.mock('react', () => require('react/react.shared-subset'));
33 + jest.mock('react-server-dom-turbopack/server', () =>
34 + require('react-server-dom-turbopack/server.node'),
35 + );
36 + ReactServerDOMServer = require('react-server-dom-turbopack/server');
37 +
38 + const TurbopackMock = require('./utils/TurbopackMock');
39 + clientExports = TurbopackMock.clientExports;
40 + turbopackMap = TurbopackMock.turbopackMap;
41 + turbopackModules = TurbopackMock.turbopackModules;
42 + turbopackModuleLoading = TurbopackMock.moduleLoading;
43 +
44 + jest.resetModules();
45 + __unmockReact();
46 + jest.unmock('react-server-dom-turbopack/server');
47 + jest.mock('react-server-dom-turbopack/client', () =>
48 + require('react-server-dom-turbopack/client.node'),
49 + );
50 +
51 + React = require('react');
52 + ReactDOMServer = require('react-dom/server.node');
53 + ReactServerDOMClient = require('react-server-dom-turbopack/client');
54 + Stream = require('stream');
55 + use = React.use;
56 + });
57 +
58 + function readResult(stream) {
59 + return new Promise((resolve, reject) => {
60 + let buffer = '';
61 + const writable = new Stream.PassThrough();
62 + writable.setEncoding('utf8');
63 + writable.on('data', chunk => {
64 + buffer += chunk;
65 + });
66 + writable.on('error', error => {
67 + reject(error);
68 + });
69 + writable.on('end', () => {
70 + resolve(buffer);
71 + });
72 + stream.pipe(writable);
73 + });
74 + }
75 +
76 + it('should allow an alternative module mapping to be used for SSR', async () => {
77 + function ClientComponent() {
78 + return <span>Client Component</span>;
79 + }
80 + // The Client build may not have the same IDs as the Server bundles for the same
81 + // component.
82 + const ClientComponentOnTheClient = clientExports(
83 + ClientComponent,
84 + 'path/to/chunk.js',
85 + );
86 + const ClientComponentOnTheServer = clientExports(ClientComponent);
87 +
88 + // In the SSR bundle this module won't exist. We simulate this by deleting it.
89 + const clientId = turbopackMap[ClientComponentOnTheClient.$$id].id;
90 + delete turbopackModules[clientId];
91 +
92 + // Instead, we have to provide a translation from the client meta data to the SSR
93 + // meta data.
94 + const ssrMetadata = turbopackMap[ClientComponentOnTheServer.$$id];
95 + const translationMap = {
96 + [clientId]: {
97 + '*': ssrMetadata,
98 + },
99 + };
100 +
101 + function App() {
102 + return <ClientComponentOnTheClient />;
103 + }
104 +
105 + const stream = ReactServerDOMServer.renderToPipeableStream(
106 + <App />,
107 + turbopackMap,
108 + );
109 + const readable = new Stream.PassThrough();
110 +
111 + stream.pipe(readable);
112 +
113 + let response;
114 + function ClientRoot() {
115 + if (!response) {
116 + response = ReactServerDOMClient.createFromNodeStream(readable, {
117 + moduleMap: translationMap,
118 + moduleLoading: turbopackModuleLoading,
119 + });
120 + }
121 + return use(response);
122 + }
123 +
124 + const ssrStream = await ReactDOMServer.renderToPipeableStream(
125 + <ClientRoot />,
126 + );
127 + const result = await readResult(ssrStream);
128 + expect(result).toEqual(
129 + '<script src="/prefix/path/to/chunk.js" async=""></script><span>Client Component</span>',
130 + );
131 + });
132 +});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReply-test.js new
+48
@@ -0,0 +1,48 @@
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.TextDecoder = require('util').TextDecoder;
17 +
18 +// let serverExports;
19 +let turbopackServerMap;
20 +let ReactServerDOMServer;
21 +let ReactServerDOMClient;
22 +
23 +describe('ReactFlightDOMReply', () => {
24 + beforeEach(() => {
25 + jest.resetModules();
26 + // Simulate the condition resolution
27 + jest.mock('react', () => require('react/react.shared-subset'));
28 + jest.mock('react-server-dom-turbopack/server', () =>
29 + require('react-server-dom-turbopack/server.browser'),
30 + );
31 + const TurbopackMock = require('./utils/TurbopackMock');
32 + // serverExports = TurbopackMock.serverExports;
33 + turbopackServerMap = TurbopackMock.turbopackServerMap;
34 + ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
35 + jest.resetModules();
36 + ReactServerDOMClient = require('react-server-dom-turbopack/client');
37 + });
38 +
39 + it('can encode a reply', async () => {
40 + const body = await ReactServerDOMClient.encodeReply({some: 'object'});
41 + const decoded = await ReactServerDOMServer.decodeReply(
42 + body,
43 + turbopackServerMap,
44 + );
45 +
46 + expect(decoded).toEqual({some: 'object'});
47 + });
48 +});
packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOMReplyEdge-test.js renamed