/
ashipov
/
react
Обзор
Документация
Войти
/
ashipov
/
react
Код
Запросы
0
Задачи 2.0
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
packages/react-devtools-shared/src/__tests__/profilerContext-test.js
942 строки
32 KB
lauren
[ci] Increase DevTools test shards and bump timeout (#35459)
07 янв 2026, 05:23
Не верифицирован
07 янв 2026, 05:23
00908be
Код
Авторство
О чём код?
/** * 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 typeof ReactTestRenderer from 'react-test-renderer'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; import type {Context} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext'; import type {DispatcherContext} from 'react-devtools-shared/src/devtools/views/Components/TreeContext'; import type Store from 'react-devtools-shared/src/devtools/store'; import {getVersionedRenderImplementation} from './utils'; describe('ProfilerContext', () => { let React; let ReactDOM; let ReactDOMClient; let TestRenderer: ReactTestRenderer; let bridge: FrontendBridge; let legacyRender; let store: Store; let utils; let BridgeContext; let ProfilerContext; let ProfilerContextController; let StoreContext; let TreeContextController; let TreeDispatcherContext; let TreeStateContext; beforeEach(() => { utils = require('./utils'); utils.beforeEachProfiling(); legacyRender = utils.legacyRender; bridge = global.bridge; store = global.store; store.collapseNodesByDefault = false; store.recordChangeDescriptions = true; React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); TestRenderer = utils.requireTestRenderer(); BridgeContext = require('react-devtools-shared/src/devtools/views/context').BridgeContext; ProfilerContext = require('react-devtools-shared/src/devtools/views/Profiler/ProfilerContext').ProfilerContext; ProfilerContextController = require('react-devtools-shared/src/devtools/views/Profiler/ProfilerContext').ProfilerContextController; StoreContext = require('react-devtools-shared/src/devtools/views/context').StoreContext; TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext').TreeContextController; TreeDispatcherContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext').TreeDispatcherContext; TreeStateContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext').TreeStateContext; }); const {render} = getVersionedRenderImplementation(); const Contexts = ({ children = null, defaultInspectedElementID = null, defaultInspectedElementIndex = null, }: any) => ( <BridgeContext.Provider value={bridge}> <StoreContext.Provider value={store}> <TreeContextController defaultInspectedElementID={defaultInspectedElementID} defaultInspectedElementIndex={defaultInspectedElementIndex}> <ProfilerContextController>{children}</ProfilerContextController> </TreeContextController> </StoreContext.Provider> </BridgeContext.Provider> ); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('updates updates profiling support based on the attached roots (legacy render)', async () => { const Component = () => null; let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } await utils.actAsync(() => { TestRenderer.create( <Contexts> <ContextReader /> </Contexts>, ); }); expect(context.supportsProfiling).toBe(false); const containerA = document.createElement('div'); const containerB = document.createElement('div'); await utils.actAsync(() => legacyRender(<Component />, containerA)); expect(context.supportsProfiling).toBe(true); await utils.actAsync(() => legacyRender(<Component />, containerB)); await utils.actAsync(() => ReactDOM.unmountComponentAtNode(containerA)); expect(context.supportsProfiling).toBe(true); await utils.actAsync(() => ReactDOM.unmountComponentAtNode(containerB)); expect(context.supportsProfiling).toBe(false); }); // @reactVersion >= 18 it('updates updates profiling support based on the attached roots (createRoot)', async () => { const Component = () => null; let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } await utils.actAsync(() => { TestRenderer.create( <Contexts> <ContextReader /> </Contexts>, ); }); expect(context.supportsProfiling).toBe(false); const containerA = document.createElement('div'); const containerB = document.createElement('div'); const rootA = ReactDOMClient.createRoot(containerA); const rootB = ReactDOMClient.createRoot(containerB); await utils.actAsync(() => rootA.render(<Component />)); expect(context.supportsProfiling).toBe(true); await utils.actAsync(() => rootB.render(<Component />)); await utils.actAsync(() => rootA.unmount()); expect(context.supportsProfiling).toBe(true); await utils.actAsync(() => rootB.unmount()); expect(context.supportsProfiling).toBe(false); }); it('should gracefully handle an empty profiling session (with no recorded commits)', async () => { const Example = () => null; utils.act(() => render(<Example />)); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } // Profile but don't record any updates. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => { TestRenderer.create( <Contexts> <ContextReader /> </Contexts>, ); }); expect(context).not.toBeNull(); expect(context.didRecordCommits).toBe(false); expect(context.isProcessingData).toBe(false); expect(context.isProfiling).toBe(true); expect(context.profilingData).toBe(null); await utils.actAsync(() => store.profilerStore.stopProfiling()); expect(context).not.toBeNull(); expect(context.didRecordCommits).toBe(false); expect(context.isProcessingData).toBe(false); expect(context.isProfiling).toBe(false); expect(context.profilingData).toBe(null); }); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('should auto-select the root ID matching the Components tab selection if it has profiling data (legacy render)', async () => { const Parent = () => <Child />; const Child = () => null; const containerOne = document.createElement('div'); const containerTwo = document.createElement('div'); utils.act(() => legacyRender(<Parent />, containerOne)); utils.act(() => legacyRender(<Parent />, containerTwo)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates to both roots. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => legacyRender(<Parent />, containerOne)); await utils.actAsync(() => legacyRender(<Parent />, containerTwo)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={store.getElementIDAtIndex(3)} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); expect(context).not.toBeNull(); expect(context.rootID).toBe( store.getRootIDForElement(((store.getElementIDAtIndex(3): any): number)), ); }); // @reactVersion >= 18 it('should auto-select the root ID matching the Components tab selection if it has profiling data (createRoot)', async () => { const Parent = () => <Child />; const Child = () => null; const containerOne = document.createElement('div'); const containerTwo = document.createElement('div'); const rootOne = ReactDOMClient.createRoot(containerOne); const rootTwo = ReactDOMClient.createRoot(containerTwo); utils.act(() => rootOne.render(<Parent />)); utils.act(() => rootTwo.render(<Parent />)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates to both roots. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => rootOne.render(<Parent />)); await utils.actAsync(() => rootTwo.render(<Parent />)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={store.getElementIDAtIndex(3)} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); expect(context).not.toBeNull(); expect(context.rootID).toBe( store.getRootIDForElement(((store.getElementIDAtIndex(3): any): number)), ); }); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('should not select the root ID matching the Components tab selection if it has no profiling data (legacy render)', async () => { const Parent = () => <Child />; const Child = () => null; const containerOne = document.createElement('div'); const containerTwo = document.createElement('div'); utils.act(() => legacyRender(<Parent />, containerOne)); utils.act(() => legacyRender(<Parent />, containerTwo)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates to only the first root. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => legacyRender(<Parent />, containerOne)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={store.getElementIDAtIndex(3)} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); // Verify the default profiling root is the first one. expect(context).not.toBeNull(); expect(context.rootID).toBe( store.getRootIDForElement(((store.getElementIDAtIndex(0): any): number)), ); }); // @reactVersion >= 18 it('should not select the root ID matching the Components tab selection if it has no profiling data (createRoot)', async () => { const Parent = () => <Child />; const Child = () => null; const containerOne = document.createElement('div'); const containerTwo = document.createElement('div'); const rootOne = ReactDOMClient.createRoot(containerOne); const rootTwo = ReactDOMClient.createRoot(containerTwo); utils.act(() => rootOne.render(<Parent />)); utils.act(() => rootTwo.render(<Parent />)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates to only the first root. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => rootOne.render(<Parent />)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={store.getElementIDAtIndex(3)} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); // Verify the default profiling root is the first one. expect(context).not.toBeNull(); expect(context.rootID).toBe( store.getRootIDForElement(((store.getElementIDAtIndex(0): any): number)), ); }); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('should maintain root selection between profiling sessions so long as there is data for that root (legacy render)', async () => { const Parent = () => <Child />; const Child = () => null; const containerA = document.createElement('div'); const containerB = document.createElement('div'); utils.act(() => legacyRender(<Parent />, containerA)); utils.act(() => legacyRender(<Parent />, containerB)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => legacyRender(<Parent />, containerA)); await utils.actAsync(() => legacyRender(<Parent />, containerB)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); let dispatch: DispatcherContext = ((null: any): DispatcherContext); let inspectedElementID = null; function ContextReader() { context = React.useContext(ProfilerContext); dispatch = React.useContext(TreeDispatcherContext); inspectedElementID = React.useContext(TreeStateContext).inspectedElementID; return null; } const id = ((store.getElementIDAtIndex(3): any): number); // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={id} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); expect(inspectedElementID).toBe(id); // Profile and record more updates to both roots await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => legacyRender(<Parent />, containerA)); await utils.actAsync(() => legacyRender(<Parent />, containerB)); await utils.actAsync(() => store.profilerStore.stopProfiling()); const otherID = ((store.getElementIDAtIndex(0): any): number); // Change the selected element within a the Components tab. utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0})); // Verify that the initial Profiler root selection is maintained. expect(inspectedElementID).toBe(otherID); expect(context).not.toBeNull(); expect(context.rootID).toBe(store.getRootIDForElement(id)); }); // @reactVersion >= 18.0 it('should maintain root selection between profiling sessions so long as there is data for that root (createRoot)', async () => { const Parent = () => <Child />; const Child = () => null; const containerA = document.createElement('div'); const containerB = document.createElement('div'); const rootA = ReactDOMClient.createRoot(containerA); const rootB = ReactDOMClient.createRoot(containerB); utils.act(() => rootA.render(<Parent />)); utils.act(() => rootB.render(<Parent />)); expect(store).toMatchInlineSnapshot(` [root] ▾ <Parent> <Child> [root] ▾ <Parent> <Child> `); // Profile and record updates. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => rootA.render(<Parent />)); await utils.actAsync(() => rootB.render(<Parent />)); await utils.actAsync(() => store.profilerStore.stopProfiling()); let context: Context = ((null: any): Context); let dispatch: DispatcherContext = ((null: any): DispatcherContext); let inspectedElementID = null; function ContextReader() { context = React.useContext(ProfilerContext); dispatch = React.useContext(TreeDispatcherContext); inspectedElementID = React.useContext(TreeStateContext).inspectedElementID; return null; } const id = ((store.getElementIDAtIndex(3): any): number); // Select an element within the second root. await utils.actAsync(() => TestRenderer.create( <Contexts defaultInspectedElementID={id} defaultInspectedElementIndex={3}> <ContextReader /> </Contexts>, ), ); expect(inspectedElementID).toBe(id); // Profile and record more updates to both roots await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => rootA.render(<Parent />)); await utils.actAsync(() => rootB.render(<Parent />)); await utils.actAsync(() => store.profilerStore.stopProfiling()); const otherID = ((store.getElementIDAtIndex(0): any): number); // Change the selected element within a the Components tab. utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0})); // Verify that the initial Profiler root selection is maintained. expect(inspectedElementID).toBe(otherID); expect(context).not.toBeNull(); expect(context.rootID).toBe(store.getRootIDForElement(id)); }); it('should sync selected element in the Components tab too, provided the element is a match', async () => { const GrandParent = ({includeChild}) => ( <Parent includeChild={includeChild} /> ); const Parent = ({includeChild}) => (includeChild ? <Child /> : null); const Child = () => null; utils.act(() => render(<GrandParent includeChild={true} />)); expect(store).toMatchInlineSnapshot(` [root] ▾ <GrandParent> ▾ <Parent> <Child> `); const parentID = ((store.getElementIDAtIndex(1): any): number); const childID = ((store.getElementIDAtIndex(2): any): number); // Profile and record updates. await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => render(<GrandParent includeChild={true} />)); await utils.actAsync(() => render(<GrandParent includeChild={false} />)); await utils.actAsync(() => store.profilerStore.stopProfiling()); expect(store).toMatchInlineSnapshot(` [root] ▾ <GrandParent> <Parent> `); let context: Context = ((null: any): Context); let inspectedElementID = null; function ContextReader() { context = React.useContext(ProfilerContext); inspectedElementID = React.useContext(TreeStateContext).inspectedElementID; return null; } await utils.actAsync(() => TestRenderer.create( <Contexts> <ContextReader /> </Contexts>, ), ); expect(inspectedElementID).toBeNull(); // Select an element in the Profiler tab and verify that the selection is synced to the Components tab. await utils.actAsync(() => context.selectFiber(parentID, 'Parent')); expect(inspectedElementID).toBe(parentID); // Select an unmounted element and verify no Components tab selection doesn't change. await utils.actAsync(() => context.selectFiber(childID, 'Child')); expect(inspectedElementID).toBe(parentID); }); it('should toggle profiling when the keyboard shortcut is pressed', async () => { // Context providers const Profiler = require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default; const { TimelineContextController, } = require('react-devtools-timeline/src/TimelineContext'); const { SettingsContextController, } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext'); const { ModalDialogContextController, } = require('react-devtools-shared/src/devtools/views/ModalDialog'); // Dom component for profiling to be enabled const Component = () => null; utils.act(() => render(<Component />)); const profilerContainer = document.createElement('div'); document.body.appendChild(profilerContainer); // Create a root for the profiler const profilerRoot = ReactDOMClient.createRoot(profilerContainer); // Render the profiler utils.act(() => { profilerRoot.render( <Contexts> <SettingsContextController browserTheme="light"> <ModalDialogContextController> <TimelineContextController> <Profiler /> </TimelineContextController> </ModalDialogContextController> </SettingsContextController> </Contexts>, ); }); // Verify that the profiler is not profiling. expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(false); // Trigger the keyboard shortcut. const ownerWindow = profilerContainer.ownerDocument.defaultView; const isMac = typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; const keyEvent = new KeyboardEvent('keydown', { key: 'e', metaKey: isMac, ctrlKey: !isMac, bubbles: true, }); // Dispatch keyboard event to toggle profiling on // Try utils.actAsync with recursivelyFlush=false await utils.actAsync(() => { ownerWindow.dispatchEvent(keyEvent); }, false); expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(true); // Dispatch keyboard event to toggle profiling off await utils.actAsync(() => { ownerWindow.dispatchEvent(keyEvent); }, false); expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(false); document.body.removeChild(profilerContainer); }, 20000); it('should navigate between commits when the keyboard shortcut is pressed', async () => { const Parent = () => <Child />; const Child = () => null; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); utils.act(() => root.render(<Parent />)); // Profile and record multiple commits await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => root.render(<Parent />)); // Commit 1 await utils.actAsync(() => root.render(<Parent />)); // Commit 2 await utils.actAsync(() => root.render(<Parent />)); // Commit 3 await utils.actAsync(() => store.profilerStore.stopProfiling()); const Profiler = require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default; const { TimelineContextController, } = require('react-devtools-timeline/src/TimelineContext'); const { SettingsContextController, } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext'); const { ModalDialogContextController, } = require('react-devtools-shared/src/devtools/views/ModalDialog'); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } const profilerContainer = document.createElement('div'); document.body.appendChild(profilerContainer); const profilerRoot = ReactDOMClient.createRoot(profilerContainer); await utils.actAsync(() => { profilerRoot.render( <Contexts> <SettingsContextController browserTheme="light"> <ModalDialogContextController> <TimelineContextController> <Profiler /> <ContextReader /> </TimelineContextController> </ModalDialogContextController> </SettingsContextController> </Contexts>, ); }); // Verify we have profiling data with 3 commits expect(context.didRecordCommits).toBe(true); expect(context.profilingData).not.toBeNull(); const rootID = context.rootID; expect(rootID).not.toBeNull(); const dataForRoot = context.profilingData.dataForRoots.get(rootID); expect(dataForRoot.commitData.length).toBe(3); // Should start at the first commit expect(context.selectedCommitIndex).toBe(0); const ownerWindow = profilerContainer.ownerDocument.defaultView; const isMac = typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; // Test ArrowRight navigation (forward) with correct modifier const arrowRightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', metaKey: isMac, ctrlKey: !isMac, bubbles: true, }); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(1); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(2); // Test wrap-around (last -> first) await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(0); // Test ArrowLeft navigation (backward) with correct modifier const arrowLeftEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft', metaKey: isMac, ctrlKey: !isMac, bubbles: true, }); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowLeftEvent); }, false); expect(context.selectedCommitIndex).toBe(2); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowLeftEvent); }, false); expect(context.selectedCommitIndex).toBe(1); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowLeftEvent); }, false); expect(context.selectedCommitIndex).toBe(0); // Cleanup await utils.actAsync(() => profilerRoot.unmount()); document.body.removeChild(profilerContainer); }); it('should handle commit selection edge cases when filtering commits', async () => { const Scheduler = require('scheduler'); // Create components that do varying amounts of work to generate different commit durations const Parent = ({count}) => { Scheduler.unstable_advanceTime(10); const items = []; for (let i = 0; i < count; i++) { items.push(<Child key={i} duration={i} />); } return <div>{items}</div>; }; const Child = ({duration}) => { Scheduler.unstable_advanceTime(duration); return <span>{duration}</span>; }; const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container); utils.act(() => root.render(<Parent count={1} />)); // Profile and record multiple commits with different amounts of work await utils.actAsync(() => store.profilerStore.startProfiling()); await utils.actAsync(() => root.render(<Parent count={5} />)); // Commit 1 - 20ms await utils.actAsync(() => root.render(<Parent count={20} />)); // Commit 2 - 200ms await utils.actAsync(() => root.render(<Parent count={50} />)); // Commit 3 - 1235ms await utils.actAsync(() => root.render(<Parent count={10} />)); // Commit 4 - 55ms await utils.actAsync(() => store.profilerStore.stopProfiling()); // Context providers const Profiler = require('react-devtools-shared/src/devtools/views/Profiler/Profiler').default; const { TimelineContextController, } = require('react-devtools-timeline/src/TimelineContext'); const { SettingsContextController, } = require('react-devtools-shared/src/devtools/views/Settings/SettingsContext'); const { ModalDialogContextController, } = require('react-devtools-shared/src/devtools/views/ModalDialog'); let context: Context = ((null: any): Context); function ContextReader() { context = React.useContext(ProfilerContext); return null; } const profilerContainer = document.createElement('div'); document.body.appendChild(profilerContainer); const profilerRoot = ReactDOMClient.createRoot(profilerContainer); await utils.actAsync(() => { profilerRoot.render( <Contexts> <SettingsContextController browserTheme="light"> <ModalDialogContextController> <TimelineContextController> <Profiler /> <ContextReader /> </TimelineContextController> </ModalDialogContextController> </SettingsContextController> </Contexts>, ); }); // Verify we have profiling data with 4 commits expect(context.didRecordCommits).toBe(true); expect(context.profilingData).not.toBeNull(); const rootID = context.rootID; expect(rootID).not.toBeNull(); const dataForRoot = context.profilingData.dataForRoots.get(rootID); expect(dataForRoot.commitData.length).toBe(4); // Edge case 1: Should start at the first commit expect(context.selectedCommitIndex).toBe(0); const ownerWindow = profilerContainer.ownerDocument.defaultView; const isMac = typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0; const arrowRightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', metaKey: isMac, ctrlKey: !isMac, bubbles: true, }); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(1); await utils.actAsync(() => { context.setIsCommitFilterEnabled(true); }); // Edge case 2: When filtering is enabled, selected commit should remain if it's still visible expect(context.filteredCommitIndices.length).toBe(4); expect(context.selectedCommitIndex).toBe(1); expect(context.selectedFilteredCommitIndex).toBe(1); await utils.actAsync(() => { context.setMinCommitDuration(1000000); }); // Edge case 3: When all commits are filtered out, selection should be null expect(context.filteredCommitIndices).toEqual([]); expect(context.selectedCommitIndex).toBe(null); expect(context.selectedFilteredCommitIndex).toBe(null); await utils.actAsync(() => { context.setMinCommitDuration(0); }); // Edge case 4: After restoring commits, first commit should be auto-selected expect(context.filteredCommitIndices.length).toBe(4); expect(context.selectedCommitIndex).toBe(0); expect(context.selectedFilteredCommitIndex).toBe(0); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(1); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(2); await utils.actAsync(() => { ownerWindow.dispatchEvent(arrowRightEvent); }, false); expect(context.selectedCommitIndex).toBe(3); // Filter out the currently selected commit using actual commit data const commitDurations = dataForRoot.commitData.map( commit => commit.duration, ); const selectedCommitDuration = commitDurations[3]; const filterThreshold = selectedCommitDuration + 0.001; await utils.actAsync(() => { context.setMinCommitDuration(filterThreshold); }); // Edge case 5: Should auto-select first available commit when current one is filtered expect(context.selectedCommitIndex).not.toBe(null); expect(context.selectedFilteredCommitIndex).toBe(1); await utils.actAsync(() => { context.setIsCommitFilterEnabled(false); }); // Edge case 6: When filtering is disabled, selected commit should remain expect(context.filteredCommitIndices.length).toBe(4); expect(context.selectedCommitIndex).toBe(2); expect(context.selectedFilteredCommitIndex).toBe(2); await utils.actAsync(() => profilerRoot.unmount()); document.body.removeChild(profilerContainer); }); });