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>
);
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);
});
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;
}
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);
});
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>
`);
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;
}
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)),
);
});
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>
`);
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;
}
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)),
);
});
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>
`);
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;
}
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(0): any): number)),
);
});
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>
`);
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;
}
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(0): any): number)),
);
});
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>
`);
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);
await utils.actAsync(() =>
TestRenderer.create(
<Contexts
defaultInspectedElementID={id}
defaultInspectedElementIndex={3}>
<ContextReader />
</Contexts>,
),
);
expect(inspectedElementID).toBe(id);
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);
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
expect(inspectedElementID).toBe(otherID);
expect(context).not.toBeNull();
expect(context.rootID).toBe(store.getRootIDForElement(id));
});
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>
`);
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);
await utils.actAsync(() =>
TestRenderer.create(
<Contexts
defaultInspectedElementID={id}
defaultInspectedElementIndex={3}>
<ContextReader />
</Contexts>,
),
);
expect(inspectedElementID).toBe(id);
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);
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
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);
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();
await utils.actAsync(() => context.selectFiber(parentID, 'Parent'));
expect(inspectedElementID).toBe(parentID);
await utils.actAsync(() => context.selectFiber(childID, 'Child'));
expect(inspectedElementID).toBe(parentID);
});
it('should toggle profiling when the keyboard shortcut is pressed', async () => {
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');
const Component = () => null;
utils.act(() => render(<Component />));
const profilerContainer = document.createElement('div');
document.body.appendChild(profilerContainer);
const profilerRoot = ReactDOMClient.createRoot(profilerContainer);
utils.act(() => {
profilerRoot.render(
<Contexts>
<SettingsContextController browserTheme="light">
<ModalDialogContextController>
<TimelineContextController>
<Profiler />
</TimelineContextController>
</ModalDialogContextController>
</SettingsContextController>
</Contexts>,
);
});
expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(false);
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,
});
await utils.actAsync(() => {
ownerWindow.dispatchEvent(keyEvent);
}, false);
expect(store.profilerStore.isProfilingBasedOnUserInput).toBe(true);
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 />));
await utils.actAsync(() => store.profilerStore.startProfiling());
await utils.actAsync(() => root.render(<Parent />));
await utils.actAsync(() => root.render(<Parent />));
await utils.actAsync(() => root.render(<Parent />));
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>,
);
});
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);
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(() => {
ownerWindow.dispatchEvent(arrowRightEvent);
}, false);
expect(context.selectedCommitIndex).toBe(2);
await utils.actAsync(() => {
ownerWindow.dispatchEvent(arrowRightEvent);
}, false);
expect(context.selectedCommitIndex).toBe(0);
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);
await utils.actAsync(() => profilerRoot.unmount());
document.body.removeChild(profilerContainer);
});
it('should reset commit index when switching to a different root', 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 />));
await utils.actAsync(() => store.profilerStore.startProfiling());
await utils.actAsync(() => rootA.render(<Parent />));
await utils.actAsync(() => rootA.render(<Parent />));
await utils.actAsync(() => rootA.render(<Parent />));
await utils.actAsync(() => rootA.render(<Parent />));
await utils.actAsync(() => rootA.render(<Parent />));
await utils.actAsync(() => rootB.render(<Parent />));
await utils.actAsync(() => rootB.render(<Parent />));
await utils.actAsync(() => store.profilerStore.stopProfiling());
let context: Context = ((null: any): Context);
function ContextReader() {
context = React.useContext(ProfilerContext);
return null;
}
await utils.actAsync(() =>
TestRenderer.create(
<Contexts>
<ContextReader />
</Contexts>,
),
);
expect(context.didRecordCommits).toBe(true);
expect(context.profilingData).not.toBeNull();
const rootIDs = Array.from(context.profilingData.dataForRoots.keys());
expect(rootIDs.length).toBe(2);
const [rootAID, rootBID] = rootIDs;
const rootAData = context.profilingData.dataForRoots.get(rootAID);
const rootBData = context.profilingData.dataForRoots.get(rootBID);
expect(rootAData.commitData.length).toBe(5);
expect(rootBData.commitData.length).toBe(2);
await utils.actAsync(() => context.setRootID(rootAID));
expect(context.rootID).toBe(rootAID);
expect(context.selectedCommitIndex).toBe(0);
await utils.actAsync(() => context.selectCommitIndex(3));
expect(context.selectedCommitIndex).toBe(3);
await utils.actAsync(() => context.setRootID(rootBID));
expect(context.rootID).toBe(rootBID);
expect(context.selectedCommitIndex).toBe(0);
await utils.actAsync(() => context.selectCommitIndex(1));
expect(context.selectedCommitIndex).toBe(1);
await utils.actAsync(() => context.setRootID(rootAID));
expect(context.rootID).toBe(rootAID);
expect(context.selectedCommitIndex).toBe(0);
});
it('should handle commit selection edge cases when filtering commits', async () => {
const Scheduler = require('scheduler');
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} />));
await utils.actAsync(() => store.profilerStore.startProfiling());
await utils.actAsync(() => root.render(<Parent count={5} />));
await utils.actAsync(() => root.render(<Parent count={20} />));
await utils.actAsync(() => root.render(<Parent count={50} />));
await utils.actAsync(() => root.render(<Parent count={10} />));
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>,
);
});
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);
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);
});
expect(context.filteredCommitIndices.length).toBe(4);
expect(context.selectedCommitIndex).toBe(1);
expect(context.selectedFilteredCommitIndex).toBe(1);
await utils.actAsync(() => {
context.setMinCommitDuration(1000000);
});
expect(context.filteredCommitIndices).toEqual([]);
expect(context.selectedCommitIndex).toBe(null);
expect(context.selectedFilteredCommitIndex).toBe(null);
await utils.actAsync(() => {
context.setMinCommitDuration(0);
});
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);
const commitDurations = dataForRoot.commitData.map(
commit => commit.duration,
);
const selectedCommitDuration = commitDurations[3];
const filterThreshold = selectedCommitDuration + 0.001;
await utils.actAsync(() => {
context.setMinCommitDuration(filterThreshold);
});
expect(context.selectedCommitIndex).not.toBe(null);
expect(context.selectedFilteredCommitIndex).toBe(1);
await utils.actAsync(() => {
context.setIsCommitFilterEnabled(false);
});
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);
});
});