Skip to content

Commit e6734cd

Browse files
committed
Moar tests
1 parent 6f96d0a commit e6734cd

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

+187
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,193 @@ describe('ReactDOMFizzServer', () => {
22202220
},
22212221
);
22222222

2223+
// @gate experimental
2224+
it('does not recreate the fallback if server errors and hydration suspends', async () => {
2225+
let isClient = false;
2226+
2227+
function Child() {
2228+
if (isClient) {
2229+
readText('Yay!');
2230+
} else {
2231+
throw Error('Oops.');
2232+
}
2233+
Scheduler.unstable_yieldValue('Yay!');
2234+
return 'Yay!';
2235+
}
2236+
2237+
const fallbackRef = React.createRef();
2238+
function App() {
2239+
return (
2240+
<div>
2241+
<Suspense fallback={<p ref={fallbackRef}>Loading...</p>}>
2242+
<span>
2243+
<Child />
2244+
</span>
2245+
</Suspense>
2246+
</div>
2247+
);
2248+
}
2249+
await act(async () => {
2250+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2251+
<App fallbackText="Loading..." />,
2252+
{
2253+
onError(error) {
2254+
Scheduler.unstable_yieldValue('[s!] ' + error.message);
2255+
},
2256+
},
2257+
);
2258+
pipe(writable);
2259+
});
2260+
expect(Scheduler).toHaveYielded(['[s!] Oops.']);
2261+
2262+
// The server could not complete this boundary, so we'll retry on the client.
2263+
const serverFallback = container.getElementsByTagName('p')[0];
2264+
expect(serverFallback.innerHTML).toBe('Loading...');
2265+
2266+
// Hydrate the tree. This will suspend.
2267+
isClient = true;
2268+
ReactDOMClient.hydrateRoot(container, <App />, {
2269+
onRecoverableError(error) {
2270+
Scheduler.unstable_yieldValue('[c!] ' + error.message);
2271+
},
2272+
});
2273+
// This should not report any errors yet.
2274+
expect(Scheduler).toFlushAndYield([]);
2275+
expect(getVisibleChildren(container)).toEqual(
2276+
<div>
2277+
<p>Loading...</p>
2278+
</div>,
2279+
);
2280+
2281+
// Normally, hydrating after server error would force a clean client render.
2282+
// However, it suspended so at best we'd only get the same fallback anyway.
2283+
// We don't want to recreate the same fallback in the DOM again because
2284+
// that's extra work and would restart animations etc. Check we don't do that.
2285+
const clientFallback = container.getElementsByTagName('p')[0];
2286+
expect(serverFallback).toBe(clientFallback);
2287+
2288+
// When we're able to fully hydrate, we expect a clean client render.
2289+
await act(async () => {
2290+
resolveText('Yay!');
2291+
});
2292+
expect(Scheduler).toFlushAndYield([
2293+
'Yay!',
2294+
'[c!] The server could not finish this Suspense boundary, ' +
2295+
'likely due to an error during server rendering. ' +
2296+
'Switched to client rendering.',
2297+
]);
2298+
expect(getVisibleChildren(container)).toEqual(
2299+
<div>
2300+
<span>Yay!</span>
2301+
</div>,
2302+
);
2303+
});
2304+
2305+
// @gate experimental
2306+
it(
2307+
'recreates the fallback if server errors and hydration suspends but ' +
2308+
'client receives new props',
2309+
async () => {
2310+
let isClient = false;
2311+
2312+
function Child() {
2313+
const value = 'Yay!';
2314+
if (isClient) {
2315+
readText(value);
2316+
} else {
2317+
throw Error('Oops.');
2318+
}
2319+
Scheduler.unstable_yieldValue(value);
2320+
return value;
2321+
}
2322+
2323+
const fallbackRef = React.createRef();
2324+
function App({fallbackText}) {
2325+
return (
2326+
<div>
2327+
<Suspense fallback={<p ref={fallbackRef}>{fallbackText}</p>}>
2328+
<span>
2329+
<Child />
2330+
</span>
2331+
</Suspense>
2332+
</div>
2333+
);
2334+
}
2335+
2336+
await act(async () => {
2337+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2338+
<App fallbackText="Loading..." />,
2339+
{
2340+
onError(error) {
2341+
Scheduler.unstable_yieldValue('[s!] ' + error.message);
2342+
},
2343+
},
2344+
);
2345+
pipe(writable);
2346+
});
2347+
expect(Scheduler).toHaveYielded(['[s!] Oops.']);
2348+
2349+
const serverFallback = container.getElementsByTagName('p')[0];
2350+
expect(serverFallback.innerHTML).toBe('Loading...');
2351+
2352+
// Hydrate the tree. This will suspend.
2353+
isClient = true;
2354+
const root = ReactDOMClient.hydrateRoot(
2355+
container,
2356+
<App fallbackText="Loading..." />,
2357+
{
2358+
onRecoverableError(error) {
2359+
Scheduler.unstable_yieldValue('[c!] ' + error.message);
2360+
},
2361+
},
2362+
);
2363+
// This should not report any errors yet.
2364+
expect(Scheduler).toFlushAndYield([]);
2365+
expect(getVisibleChildren(container)).toEqual(
2366+
<div>
2367+
<p>Loading...</p>
2368+
</div>,
2369+
);
2370+
2371+
// Normally, hydration after server error would force a clean client render.
2372+
// However, that suspended so at best we'd only get a fallback anyway.
2373+
// We don't want to replace a fallback with the same fallback because
2374+
// that's extra work and would restart animations etc. Verify we don't do that.
2375+
const clientFallback1 = container.getElementsByTagName('p')[0];
2376+
expect(serverFallback).toBe(clientFallback1);
2377+
2378+
// However, an update may have changed the fallback props. In that case we have to
2379+
// actually force it to re-render on the client and throw away the server one.
2380+
root.render(<App fallbackText="More loading..." />);
2381+
Scheduler.unstable_flushAll();
2382+
jest.runAllTimers();
2383+
expect(Scheduler).toHaveYielded([
2384+
'[c!] The server could not finish this Suspense boundary, ' +
2385+
'likely due to an error during server rendering. ' +
2386+
'Switched to client rendering.',
2387+
]);
2388+
expect(getVisibleChildren(container)).toEqual(
2389+
<div>
2390+
<p>More loading...</p>
2391+
</div>,
2392+
);
2393+
// This should be a clean render without reusing DOM.
2394+
const clientFallback2 = container.getElementsByTagName('p')[0];
2395+
expect(clientFallback2).not.toBe(clientFallback1);
2396+
2397+
// Verify we can still do a clean content render after.
2398+
await act(async () => {
2399+
resolveText('Yay!');
2400+
});
2401+
expect(Scheduler).toFlushAndYield(['Yay!']);
2402+
expect(getVisibleChildren(container)).toEqual(
2403+
<div>
2404+
<span>Yay!</span>
2405+
</div>,
2406+
);
2407+
},
2408+
);
2409+
22232410
// @gate experimental
22242411
it(
22252412
'errors during hydration force a client render at the nearest Suspense ' +

0 commit comments

Comments
 (0)