@samitouri / QOS-React / commits / 98eb5ae531

TestRenderer toJSON should not expose the Array wrapper Suspense uses for hidden trees (#14392)

Brian Vaughn committed Dec 5, 2018 at 11:09 UTC 98eb5ae5319e1d26b5b89a33ead80716db725f83
3 files changed +86 -4
packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+3 -3
@@ -267,7 +267,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
267 describe('when suspending during mount', () => {
268 it('properly accounts for base durations when a suspended times out in a sync tree', () => {
269 const root = ReactTestRenderer.create(<App shouldSuspend={true} />);
270 - expect(root.toJSON()).toEqual(['Loading...']);
270 + expect(root.toJSON()).toEqual('Loading...');
271 expect(onRender).toHaveBeenCalledTimes(1);
272
273 // Initial mount only shows the "Loading..." Fallback.
@@ -343,7 +343,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
343 expect(onRender.mock.calls[0][3]).toBe(5);
344
345 root.update(<App shouldSuspend={true} textRenderDuration={5} />);
346 - expect(root.toJSON()).toEqual(['Loading...']);
346 + expect(root.toJSON()).toEqual('Loading...');
347 expect(onRender).toHaveBeenCalledTimes(2);
348
349 // The suspense update should only show the "Loading..." Fallback.
@@ -355,7 +355,7 @@ function runPlaceholderTests(suiteLabel, loadReactNoop) {
355 root.update(
356 <App shouldSuspend={true} text="New" textRenderDuration={6} />,
357 );
358 - expect(root.toJSON()).toEqual(['Loading...']);
358 + expect(root.toJSON()).toEqual('Loading...');
359 expect(onRender).toHaveBeenCalledTimes(3);
360
361 // If we force another update while still timed out,
packages/react-test-renderer/src/ReactTestRenderer.js
+9 -1
@@ -461,7 +461,15 @@ const ReactTestRendererFiber = {
461 if (container.children.length === 1) {
462 return toJSON(container.children[0]);
463 }
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 + return toJSON(container.children[1]);
472 + }
473 let renderedChildren = null;
474 if (container.children && container.children.length) {
475 for (let i = 0; i < container.children.length; i++) {
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js
+74
@@ -13,6 +13,8 @@ const ReactDOM = require('react-dom');
13
14 // Isolate test renderer.
15 jest.resetModules();
16 +const React = require('react');
17 +const ReactCache = require('react-cache');
18 const ReactTestRenderer = require('react-test-renderer');
19
20 describe('ReactTestRenderer', () => {
@@ -26,4 +28,76 @@ describe('ReactTestRenderer', () => {
28 withoutStack: true,
29 });
30 });
31 +
32 + describe('timed out Suspense hidden subtrees should not be observable via toJSON', () => {
33 + let AsyncText;
34 + let PendingResources;
35 + let TextResource;
36 +
37 + beforeEach(() => {
38 + PendingResources = {};
39 + TextResource = ReactCache.unstable_createResource(
40 + text =>
41 + new Promise(resolve => {
42 + PendingResources[text] = resolve;
43 + }),
44 + text => text,
45 + );
46 +
47 + AsyncText = ({text}) => {
48 + const value = TextResource.read(text);
49 + return value;
50 + };
51 + });
52 +
53 + it('for root Suspense components', async done => {
54 + const App = ({text}) => {
55 + return (
56 + <React.Suspense fallback="fallback">
57 + <AsyncText text={text} />
58 + </React.Suspense>
59 + );
60 + };
61 +
62 + const root = ReactTestRenderer.create(<App text="initial" />);
63 + PendingResources.initial('initial');
64 + await Promise.resolve();
65 + expect(root.toJSON()).toEqual('initial');
66 +
67 + root.update(<App text="dynamic" />);
68 + expect(root.toJSON()).toEqual('fallback');
69 +
70 + PendingResources.dynamic('dynamic');
71 + await Promise.resolve();
72 + expect(root.toJSON()).toEqual('dynamic');
73 +
74 + done();
75 + });
76 +
77 + it('for nested Suspense components', async done => {
78 + const App = ({text}) => {
79 + return (
80 + <div>
81 + <React.Suspense fallback="fallback">
82 + <AsyncText text={text} />
83 + </React.Suspense>
84 + </div>
85 + );
86 + };
87 +
88 + const root = ReactTestRenderer.create(<App text="initial" />);
89 + PendingResources.initial('initial');
90 + await Promise.resolve();
91 + expect(root.toJSON().children).toEqual(['initial']);
92 +
93 + root.update(<App text="dynamic" />);
94 + expect(root.toJSON().children).toEqual(['fallback']);
95 +
96 + PendingResources.dynamic('dynamic');
97 + await Promise.resolve();
98 + expect(root.toJSON().children).toEqual(['dynamic']);
99 +
100 + done();
101 + });
102 + });
103 });