Skip to content

Commit 98eb5ae

Browse files
authored
TestRenderer toJSON should not expose the Array wrapper Suspense uses for hidden trees (#14392)
1 parent 39489e7 commit 98eb5ae

3 files changed

Lines changed: 86 additions & 4 deletions

File tree

‎packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
267267
describe('when suspending during mount',()=>{
268268
it('properly accounts for base durations when a suspended times out in a sync tree',()=>{
269269
constroot=ReactTestRenderer.create(<AppshouldSuspend={true}/>);
270-
expect(root.toJSON()).toEqual(['Loading...']);
270+
expect(root.toJSON()).toEqual('Loading...');
271271
expect(onRender).toHaveBeenCalledTimes(1);
272272

273273
// Initial mount only shows the "Loading..." Fallback.
@@ -343,7 +343,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
343343
expect(onRender.mock.calls[0][3]).toBe(5);
344344

345345
root.update(<AppshouldSuspend={true}textRenderDuration={5}/>);
346-
expect(root.toJSON()).toEqual(['Loading...']);
346+
expect(root.toJSON()).toEqual('Loading...');
347347
expect(onRender).toHaveBeenCalledTimes(2);
348348

349349
// The suspense update should only show the "Loading..." Fallback.
@@ -355,7 +355,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
355355
root.update(
356356
<AppshouldSuspend={true}text="New"textRenderDuration={6}/>,
357357
);
358-
expect(root.toJSON()).toEqual(['Loading...']);
358+
expect(root.toJSON()).toEqual('Loading...');
359359
expect(onRender).toHaveBeenCalledTimes(3);
360360

361361
// If we force another update while still timed out,

‎packages/react-test-renderer/src/ReactTestRenderer.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,15 @@ const ReactTestRendererFiber = {
461461
if(container.children.length===1){
462462
returntoJSON(container.children[0]);
463463
}
464-
464+
if(
465+
container.children.length===2&&
466+
container.children[0].isHidden===true&&
467+
container.children[1].isHidden===false
468+
){
469+
// Omit timed out children from output entirely, including the fact that we
470+
// temporarily wrap fallback and timed out children in an array.
471+
returntoJSON(container.children[1]);
472+
}
465473
letrenderedChildren=null;
466474
if(container.children&&container.children.length){
467475
for(leti=0;i<container.children.length;i++){

‎packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const ReactDOM = require('react-dom');
1313

1414
// Isolate test renderer.
1515
jest.resetModules();
16+
constReact=require('react');
17+
constReactCache=require('react-cache');
1618
constReactTestRenderer=require('react-test-renderer');
1719

1820
describe('ReactTestRenderer',()=>{
@@ -26,4 +28,76 @@ describe('ReactTestRenderer', () => {
2628
withoutStack: true,
2729
});
2830
});
31+
32+
describe('timed out Suspense hidden subtrees should not be observable via toJSON',()=>{
33+
letAsyncText;
34+
letPendingResources;
35+
letTextResource;
36+
37+
beforeEach(()=>{
38+
PendingResources={};
39+
TextResource=ReactCache.unstable_createResource(
40+
text=>
41+
newPromise(resolve=>{
42+
PendingResources[text]=resolve;
43+
}),
44+
text=>text,
45+
);
46+
47+
AsyncText=({text})=>{
48+
constvalue=TextResource.read(text);
49+
returnvalue;
50+
};
51+
});
52+
53+
it('for root Suspense components',asyncdone=>{
54+
constApp=({text})=>{
55+
return(
56+
<React.Suspensefallback="fallback">
57+
<AsyncTexttext={text}/>
58+
</React.Suspense>
59+
);
60+
};
61+
62+
constroot=ReactTestRenderer.create(<Apptext="initial"/>);
63+
PendingResources.initial('initial');
64+
awaitPromise.resolve();
65+
expect(root.toJSON()).toEqual('initial');
66+
67+
root.update(<Apptext="dynamic"/>);
68+
expect(root.toJSON()).toEqual('fallback');
69+
70+
PendingResources.dynamic('dynamic');
71+
awaitPromise.resolve();
72+
expect(root.toJSON()).toEqual('dynamic');
73+
74+
done();
75+
});
76+
77+
it('for nested Suspense components',asyncdone=>{
78+
constApp=({text})=>{
79+
return(
80+
<div>
81+
<React.Suspensefallback="fallback">
82+
<AsyncTexttext={text}/>
83+
</React.Suspense>
84+
</div>
85+
);
86+
};
87+
88+
constroot=ReactTestRenderer.create(<Apptext="initial"/>);
89+
PendingResources.initial('initial');
90+
awaitPromise.resolve();
91+
expect(root.toJSON().children).toEqual(['initial']);
92+
93+
root.update(<Apptext="dynamic"/>);
94+
expect(root.toJSON().children).toEqual(['fallback']);
95+
96+
PendingResources.dynamic('dynamic');
97+
awaitPromise.resolve();
98+
expect(root.toJSON().children).toEqual(['dynamic']);
99+
100+
done();
101+
});
102+
});
29103
});

0 commit comments

Comments
 (0)