From 08444e28c8c24b55928449df48df404bcd2d1cfc Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Wed, 20 Mar 2024 21:47:13 +0100 Subject: [PATCH] Add a test for issue #28595 The added test, intended to fail and reproduce the [reported issue](https://github.com/facebook/react/issues/28595), unexpectedly passes in its current state. I see three possible reasons: 1. The bug report could be invalid. 2. How I've structured the test might be insufficient to replicate what `ai/rsc` is doing. 3. Something in the test setup could be masking the actual error. (Possibly related to fake timers?) If the problem lies in reason 2 or 3, this test could possibly serve as a foundation for further investigation. --- .../src/__tests__/ReactFlightDOM-test.js | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 9ff119ac1419d..1b16d843a0eae 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -812,6 +812,111 @@ describe('ReactFlightDOM', () => { expect(reportedErrors).toEqual([]); }); + it('should handle streaming async server components', async () => { + const reportedErrors = []; + + const Row = async ({current, next}) => { + const chunk = await next; + + if (chunk.done) { + return chunk.value; + } + + return ( + + + + ); + }; + + function createResolvablePromise() { + let _resolve, _reject; + + const promise = new Promise((resolve, reject) => { + _resolve = resolve; + _reject = reject; + }); + + return {promise, resolve: _resolve, reject: _reject}; + } + + function createSuspensedChunk(initialValue) { + const {promise, resolve, reject} = createResolvablePromise(); + + return { + row: ( + + + + ), + resolve, + reject, + }; + } + + function Text({children}) { + return
{children}
; + } + + function makeDelayedText() { + const {promise, resolve, reject} = createResolvablePromise(); + async function DelayedText() { + const data = await promise; + return {data}; + } + return [DelayedText, resolve, reject]; + } + + const [Posts, resolvePostsData] = makeDelayedText(); + const suspendedChunk = createSuspensedChunk(

loading

); + const model = {rootContent: suspendedChunk.row}; + + function ProfilePage({response}) { + return use(response).rootContent; + } + + const {writable, readable} = getTestStream(); + const {pipe} = ReactServerDOMServer.renderToPipeableStream( + model, + webpackMap, + { + onError(error) { + reportedErrors.push(error); + }, + }, + ); + pipe(writable); + const response = ReactServerDOMClient.createFromReadableStream(readable); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render(); + }); + + expect(container.innerHTML).toBe('

loading

'); + + const resolvable = createResolvablePromise(); + const value = ; + + await act(async () => { + suspendedChunk.resolve({value, done: false, next: resolvable.promise}); + await Promise.resolve(); + resolvable.resolve({value, done: true}); + }); + + expect(container.innerHTML).toBe('

loading

'); + + await act(async () => { + jest.advanceTimersByTime(500); + await resolvePostsData('posts'); + await 'the inner async function'; + }); + + expect(container.innerHTML).toBe('
posts
'); + expect(reportedErrors).toEqual([]); + }); + it('should preserve state of client components on refetch', async () => { // Client