From 6cb9f184c1246a29a0c5db00a5db6647c13dbdab Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 1 Apr 2022 02:38:51 +0100 Subject: [PATCH] Add test for transitions --- .../src/__tests__/ReactDOMFizzServer-test.js | 107 ++++++++++++++++-- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 8e4719e0b170b..295e99b26a691 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -2247,14 +2247,11 @@ describe('ReactDOMFizzServer', () => { ); } await act(async () => { - const {pipe} = ReactDOMFizzServer.renderToPipeableStream( - , - { - onError(error) { - Scheduler.unstable_yieldValue('[s!] ' + error.message); - }, + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(, { + onError(error) { + Scheduler.unstable_yieldValue('[s!] ' + error.message); }, - ); + }); pipe(writable); }); expect(Scheduler).toHaveYielded(['[s!] Oops.']); @@ -2302,6 +2299,102 @@ describe('ReactDOMFizzServer', () => { ); }); + // @gate experimental + it( + 'does not recreate the fallback if server errors and hydration suspends ' + + 'and root receives a transition', + async () => { + let isClient = false; + + function Child({color}) { + if (isClient) { + readText('Yay!'); + } else { + throw Error('Oops.'); + } + Scheduler.unstable_yieldValue('Yay! (' + color + ')'); + return 'Yay! (' + color + ')'; + } + + const fallbackRef = React.createRef(); + function App({color}) { + return ( +
+ Loading...

}> + + + +
+
+ ); + } + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + { + onError(error) { + Scheduler.unstable_yieldValue('[s!] ' + error.message); + }, + }, + ); + pipe(writable); + }); + expect(Scheduler).toHaveYielded(['[s!] Oops.']); + + // The server could not complete this boundary, so we'll retry on the client. + const serverFallback = container.getElementsByTagName('p')[0]; + expect(serverFallback.innerHTML).toBe('Loading...'); + + // Hydrate the tree. This will suspend. + isClient = true; + const root = ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + Scheduler.unstable_yieldValue('[c!] ' + error.message); + }, + }); + // This should not report any errors yet. + expect(Scheduler).toFlushAndYield([]); + expect(getVisibleChildren(container)).toEqual( +
+

Loading...

+
, + ); + + // Normally, hydrating after server error would force a clean client render. + // However, it suspended so at best we'd only get the same fallback anyway. + // We don't want to recreate the same fallback in the DOM again because + // that's extra work and would restart animations etc. Check we don't do that. + const clientFallback = container.getElementsByTagName('p')[0]; + expect(serverFallback).toBe(clientFallback); + + // Transition updates shouldn't recreate the fallback either. + React.startTransition(() => { + root.render(); + }); + Scheduler.unstable_flushAll(); + jest.runAllTimers(); + const clientFallback2 = container.getElementsByTagName('p')[0]; + expect(clientFallback2).toBe(serverFallback); + + // When we're able to fully hydrate, we expect a clean client render. + await act(async () => { + resolveText('Yay!'); + }); + expect(Scheduler).toFlushAndYield([ + 'Yay! (red)', + '[c!] The server could not finish this Suspense boundary, ' + + 'likely due to an error during server rendering. ' + + 'Switched to client rendering.', + 'Yay! (blue)', + ]); + expect(getVisibleChildren(container)).toEqual( +
+ Yay! (blue) +
, + ); + }, + ); + // @gate experimental it( 'recreates the fallback if server errors and hydration suspends but ' +