diff --git a/src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap b/src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap index 22f2fdf406..9527d9f602 100644 --- a/src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap +++ b/src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap @@ -115,6 +115,260 @@ exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurr `; +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 1`] = ` +[root] + ▾ + + ▾ + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 2`] = ` +[root] + ▾ + + ▾ + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 3`] = ` +[root] + ▾ + + ▾ + ▾ + + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 4`] = ` +[root] + ▾ + + ▾ + ▾ + + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 5`] = ` +[root] + ▾ + + ▾ + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 6`] = ` +[root] + ▾ + + ▾ + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 7`] = ` +[root] + ▾ + + ▾ + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 8`] = ` +[root] + ▾ + + ▾ + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 9`] = ` +[root] + ▾ + + ▾ + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 10`] = ` +[root] + ▾ + + ▾ + ▾ + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 11`] = ` +[root] + ▾ + + ▾ + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 12`] = ` +[root] + ▾ + + ▾ + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 13`] = ` +[root] + ▾ + + ▾ + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 14`] = ` +[root] + ▾ + + ▾ + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 15`] = ` +[root] + ▾ + + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 16`] = ` +[root] + ▾ + + ▾ + + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 17`] = ` +[root] + ▾ + + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 18`] = ` +[root] + ▾ + + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 19`] = ` +[root] + ▾ + + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 20`] = ` +[root] + ▾ + + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 21`] = ` +[root] + ▾ + + ▾ + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 22`] = ` +[root] + ▾ + + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 23`] = ` +[root] + ▾ + + ▾ + + +`; + +exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 24`] = ` +[root] + ▾ + + ▾ + + +`; + exports[`StoreStressConcurrent should handle a stress test with different tree operations (Concurrent Mode): 1: abcde 1`] = ` [root] ▾ diff --git a/src/__tests__/storeStressTestConcurrent-test.js b/src/__tests__/storeStressTestConcurrent-test.js index 6bfd867337..546db55066 100644 --- a/src/__tests__/storeStressTestConcurrent-test.js +++ b/src/__tests__/storeStressTestConcurrent-test.js @@ -668,4 +668,435 @@ describe('StoreStressConcurrent', () => { } } }); + + it('should handle a stress test for Suspense without type change (Concurrent Mode)', () => { + const A = () => 'a'; + const B = () => 'b'; + const C = () => 'c'; + const X = () => 'x'; + const Y = () => 'y'; + const Z = () => 'z'; + const a = ; + const b = ; + const c = ; + const z = ; + + // prettier-ignore + const steps = [ + a, + [a], + [a, b, c], + [c, b, a], + [c, null, a], + {c}{a}, +
{c}{a}
, +
{a}{b}
, + [[a]], + null, + b, + a + ]; + + const Never = () => { + throw new Promise(() => {}); + }; + + const MaybeSuspend = ({ children, suspend }) => { + if (suspend) { + return ( +
+ {children} + + +
+ ); + } + return ( +
+ {children} + +
+ ); + }; + + const Root = ({ children }) => { + return children; + }; + + // 1. For each step, check Suspense can render them as initial primary content. + // This is the only step where we use Jest snapshots. + let snapshots = []; + let container = document.createElement('div'); + // $FlowFixMe + let root = ReactDOM.unstable_createRoot(container); + for (let i = 0; i < steps.length; i++) { + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + // We snapshot each step once so it doesn't regress. + expect(store).toMatchSnapshot(); + snapshots.push(print(store)); + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + + // 2. Verify check Suspense can render same steps as initial fallback content. + // We don't actually assert here because the tree includes + // which is different from the snapshots above. So we take more snapshots. + let fallbackSnapshots = []; + for (let i = 0; i < steps.length; i++) { + act(() => + root.render( + + + + + {steps[i]} + + + + + ) + ); + // We snapshot each step once so it doesn't regress. + expect(store).toMatchSnapshot(); + fallbackSnapshots.push(print(store)); + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + + // 3. Verify we can update from each step to each step in primary mode. + for (let i = 0; i < steps.length; i++) { + for (let j = 0; j < steps.length; j++) { + // Always start with a fresh container and steps[i]. + container = document.createElement('div'); + // $FlowFixMe + root = ReactDOM.unstable_createRoot(container); + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + expect(print(store)).toEqual(snapshots[i]); + // Re-render with steps[j]. + act(() => + root.render( + + + + {steps[j]} + + + + ) + ); + // Verify the successful transition to steps[j]. + expect(print(store)).toEqual(snapshots[j]); + // Check that we can transition back again. + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + expect(print(store)).toEqual(snapshots[i]); + // Clean up after every iteration. + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + } + + // 4. Verify we can update from each step to each step in fallback mode. + for (let i = 0; i < steps.length; i++) { + for (let j = 0; j < steps.length; j++) { + // Always start with a fresh container and steps[i]. + container = document.createElement('div'); + // $FlowFixMe + root = ReactDOM.unstable_createRoot(container); + act(() => + root.render( + + + + + + + + + + + + + ) + ); + expect(print(store)).toEqual(fallbackSnapshots[i]); + // Re-render with steps[j]. + act(() => + root.render( + + + + + + + + + + + + + ) + ); + // Verify the successful transition to steps[j]. + expect(print(store)).toEqual(fallbackSnapshots[j]); + // Check that we can transition back again. + act(() => + root.render( + + + + + + + + + + + + + ) + ); + expect(print(store)).toEqual(fallbackSnapshots[i]); + // Clean up after every iteration. + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + } + + // 5. Verify we can update from each step to each step when moving primary -> fallback. + for (let i = 0; i < steps.length; i++) { + for (let j = 0; j < steps.length; j++) { + // Always start with a fresh container and steps[i]. + container = document.createElement('div'); + // $FlowFixMe + root = ReactDOM.unstable_createRoot(container); + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + expect(print(store)).toEqual(snapshots[i]); + // Re-render with steps[j]. + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + // Verify the successful transition to steps[j]. + expect(print(store)).toEqual(fallbackSnapshots[j]); + // Check that we can transition back again. + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + expect(print(store)).toEqual(snapshots[i]); + // Clean up after every iteration. + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + } + + // 6. Verify we can update from each step to each step when moving fallback -> primary. + for (let i = 0; i < steps.length; i++) { + for (let j = 0; j < steps.length; j++) { + // Always start with a fresh container and steps[i]. + container = document.createElement('div'); + // $FlowFixMe + root = ReactDOM.unstable_createRoot(container); + act(() => + root.render( + + + + {steps[j]} + + + + ) + ); + expect(print(store)).toEqual(fallbackSnapshots[i]); + // Re-render with steps[j]. + act(() => + root.render( + + + + {steps[j]} + + + + ) + ); + // Verify the successful transition to steps[j]. + expect(print(store)).toEqual(snapshots[j]); + // Check that we can transition back again. + act(() => + root.render( + + + + {steps[j]} + + + + ) + ); + expect(print(store)).toEqual(fallbackSnapshots[i]); + // Clean up after every iteration. + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + } + + // 7. Verify we can update from each step to each step when toggling Suspense. + for (let i = 0; i < steps.length; i++) { + for (let j = 0; j < steps.length; j++) { + // Always start with a fresh container and steps[i]. + container = document.createElement('div'); + // $FlowFixMe + root = ReactDOM.unstable_createRoot(container); + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + + // We get ID from the index in the tree above: + // Root, X, Suspense, ... + // ^ (index is 2) + const suspenseID = store.getElementIDAtIndex(2); + + // Force fallback. + expect(print(store)).toEqual(snapshots[i]); + act(() => { + const suspenseID = store.getElementIDAtIndex(2); + bridge.send('overrideSuspense', { + id: suspenseID, + rendererID: store.getRendererIDForElement(suspenseID), + forceFallback: true, + }); + }); + expect(print(store)).toEqual(fallbackSnapshots[j]); + + // Stop forcing fallback. + act(() => { + bridge.send('overrideSuspense', { + id: suspenseID, + rendererID: store.getRendererIDForElement(suspenseID), + forceFallback: false, + }); + }); + expect(print(store)).toEqual(snapshots[i]); + + // Trigger actual fallback. + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + expect(print(store)).toEqual(fallbackSnapshots[j]); + + // Force fallback while we're in fallback mode. + act(() => { + bridge.send('overrideSuspense', { + id: suspenseID, + rendererID: store.getRendererIDForElement(suspenseID), + forceFallback: true, + }); + }); + // Keep seeing fallback content. + expect(print(store)).toEqual(fallbackSnapshots[j]); + + // Switch to primary mode. + act(() => + root.render( + + + + {steps[i]} + + + + ) + ); + // Fallback is still forced though. + expect(print(store)).toEqual(fallbackSnapshots[j]); + + // Stop forcing fallback. This reverts to primary content. + act(() => { + bridge.send('overrideSuspense', { + id: suspenseID, + rendererID: store.getRendererIDForElement(suspenseID), + forceFallback: false, + }); + }); + // Now we see primary content. + expect(print(store)).toEqual(snapshots[i]); + + // Clean up after every iteration. + act(() => root.unmount()); + expect(print(store)).toBe(''); + } + } + }); });