/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type Store from 'react-devtools-shared/src/devtools/store'; import {getVersionedRenderImplementation} from './utils'; describe('commit tree', () => { let React = require('react'); let Scheduler; let store: Store; let utils; beforeEach(() => { utils = require('./utils'); utils.beforeEachProfiling(); store = global.store; store.collapseNodesByDefault = false; store.recordChangeDescriptions = true; React = require('react'); Scheduler = require('scheduler'); }); const {render} = getVersionedRenderImplementation(); // @reactVersion >= 16.9 it('should be able to rebuild the store tree for each commit', () => { const Parent = ({count}) => { Scheduler.unstable_advanceTime(10); return new Array(count) .fill(true) .map((_, index) => ); }; const Child = React.memo(function Child() { Scheduler.unstable_advanceTime(2); return null; }); utils.act(() => store.profilerStore.startProfiling()); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [Memo] `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [Memo] [Memo] [Memo] `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [Memo] [Memo] `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] `); utils.act(() => store.profilerStore.stopProfiling()); const rootID = store.roots[0]; const commitTrees = []; for (let commitIndex = 0; commitIndex < 4; commitIndex++) { commitTrees.push( store.profilerStore.profilingCache.getCommitTree({ commitIndex, rootID, }), ); } expect(commitTrees[0].nodes.size).toBe(3); // + + expect(commitTrees[1].nodes.size).toBe(5); // + + x 3 expect(commitTrees[2].nodes.size).toBe(4); // + + x 2 expect(commitTrees[3].nodes.size).toBe(2); // + }); describe('Lazy', () => { async function fakeImport(result) { return {default: result}; } const LazyInnerComponent = () => null; const App = ({renderChildren}) => { if (renderChildren) { return ( ); } else { return null; } }; let LazyComponent; beforeEach(() => { LazyComponent = React.lazy(() => fakeImport(LazyInnerComponent)); }); // @reactVersion >= 16.9 it('should support Lazy components', async () => { utils.act(() => store.profilerStore.startProfiling()); utils.act(() => render()); await Promise.resolve(); expect(store).toMatchInlineSnapshot(` [root] ▾ [suspense-root] rects={null} `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [suspense-root] rects={null} `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] `); utils.act(() => store.profilerStore.stopProfiling()); const rootID = store.roots[0]; const commitTrees = []; for (let commitIndex = 0; commitIndex < 3; commitIndex++) { commitTrees.push( store.profilerStore.profilingCache.getCommitTree({ commitIndex, rootID, }), ); } expect(commitTrees[0].nodes.size).toBe(3); expect(commitTrees[1].nodes.size).toBe(4); // + + + expect(commitTrees[2].nodes.size).toBe(2); // + }); // @reactVersion >= 16.9 it('should support Lazy components that are unmounted before resolving', async () => { utils.act(() => store.profilerStore.startProfiling()); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [suspense-root] rects={null} `); utils.act(() => render()); expect(store).toMatchInlineSnapshot(` [root] `); utils.act(() => store.profilerStore.stopProfiling()); const rootID = store.roots[0]; const commitTrees = []; for (let commitIndex = 0; commitIndex < 2; commitIndex++) { commitTrees.push( store.profilerStore.profilingCache.getCommitTree({ commitIndex, rootID, }), ); } expect(commitTrees[0].nodes.size).toBe(3); expect(commitTrees[1].nodes.size).toBe(2); // + }); }); describe('Suspense', () => { it('should handle transitioning from fallback back to content during profiling', async () => { let resolvePromise; let promise = null; let childTreeBaseDuration = 10; function Wrapper({children}) { Scheduler.unstable_advanceTime(2); return children; } function Child() { Scheduler.unstable_advanceTime(childTreeBaseDuration); if (promise !== null) { React.use(promise); } return ; } function GrandChild() { Scheduler.unstable_advanceTime(5); return null; } function Fallback() { Scheduler.unstable_advanceTime(2); return null; } function App() { Scheduler.unstable_advanceTime(1); return ( }> ); } utils.act(() => store.profilerStore.startProfiling()); // Commit 1: Mount with primary utils.act(() => render()); // Commit 2: Suspend, show fallback promise = new Promise(resolve => (resolvePromise = resolve)); await utils.actAsync(() => render()); // Commit 3: Resolve suspended promise, show primary content with a different duration. childTreeBaseDuration = 20; promise = null; await utils.actAsync(resolvePromise); utils.act(() => render()); utils.act(() => store.profilerStore.stopProfiling()); const rootID = store.roots[0]; const dataForRoot = store.profilerStore.getDataForRoot(rootID); const numCommits = dataForRoot.commitData.length; for (let commitIndex = 0; commitIndex < numCommits; commitIndex++) { store.profilerStore.profilingCache.getCommitTree({ commitIndex, rootID, }); } }); }); });