/**
* 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('ProfilerStore', () => {
let React;
let store: Store;
let utils;
beforeEach(() => {
global.IS_REACT_ACT_ENVIRONMENT = true;
utils = require('./utils');
utils.beforeEachProfiling();
store = global.store;
store.collapseNodesByDefault = false;
store.recordChangeDescriptions = true;
React = require('react');
});
const {render, unmount} = getVersionedRenderImplementation();
const {render: renderOther, unmount: unmountOther} =
getVersionedRenderImplementation();
// @reactVersion >= 16.9
it('should not remove profiling data when roots are unmounted', async () => {
const Parent = ({count}) =>
new Array(count)
.fill(true)
.map((_, index) => );
const Child = () =>
Hi!
;
utils.act(() => {
render();
renderOther();
});
utils.act(() => store.profilerStore.startProfiling());
utils.act(() => {
render();
renderOther();
});
utils.act(() => store.profilerStore.stopProfiling());
const rootA = store.roots[0];
const rootB = store.roots[1];
utils.act(() => unmountOther());
expect(store.profilerStore.getDataForRoot(rootA)).not.toBeNull();
utils.act(() => unmount());
expect(store.profilerStore.getDataForRoot(rootB)).not.toBeNull();
});
// @reactVersion >= 16.9
it('should not allow new/saved profiling data to be set while profiling is in progress', () => {
utils.act(() => store.profilerStore.startProfiling());
const fauxProfilingData = {
dataForRoots: new Map(),
};
jest.spyOn(console, 'warn').mockImplementation(() => {});
store.profilerStore.profilingData = fauxProfilingData;
expect(store.profilerStore.profilingData).not.toBe(fauxProfilingData);
expect(console.warn).toHaveBeenCalledTimes(1);
expect(console.warn).toHaveBeenCalledWith(
'Profiling data cannot be updated while profiling is in progress.',
);
utils.act(() => store.profilerStore.stopProfiling());
store.profilerStore.profilingData = fauxProfilingData;
expect(store.profilerStore.profilingData).toBe(fauxProfilingData);
});
// @reactVersion >= 16.9
// This test covers current broken behavior (arguably) with the synthetic event system.
it('should filter empty commits', () => {
const inputRef = React.createRef();
const ControlledInput = () => {
const [name, setName] = React.useState('foo');
const handleChange = event => setName(event.target.value);
return ;
};
// It's important that this test uses legacy sync mode.
// The root API does not trigger this particular failing case.
utils.act(() => render());
utils.act(() => store.profilerStore.startProfiling());
// Sets a value in a way that React doesn't see,
// so that a subsequent "change" event will trigger the event handler.
const setUntrackedValue = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
'value',
).set;
const target = inputRef.current;
setUntrackedValue.call(target, 'bar');
target.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
expect(target.value).toBe('bar');
utils.act(() => store.profilerStore.stopProfiling());
// Only one commit should have been recorded (in response to the "change" event).
const root = store.roots[0];
const data = store.profilerStore.getDataForRoot(root);
expect(data.commitData).toHaveLength(1);
expect(data.operations).toHaveLength(1);
});
// @reactVersion >= 16.9
it('should filter empty commits alt', () => {
let commitCount = 0;
const inputRef = React.createRef();
const Example = () => {
const [, setTouched] = React.useState(false);
const handleBlur = () => {
setTouched(true);
};
require('scheduler').unstable_advanceTime(1);
React.useLayoutEffect(() => {
commitCount++;
});
return ;
};
// It's important that this test uses legacy sync mode.
// The root API does not trigger this particular failing case.
utils.act(() => render());
expect(commitCount).toBe(1);
commitCount = 0;
utils.act(() => store.profilerStore.startProfiling());
// Focus and blur.
const target = inputRef.current;
utils.act(() => target.focus());
utils.act(() => target.blur());
utils.act(() => target.focus());
utils.act(() => target.blur());
expect(commitCount).toBe(1);
utils.act(() => store.profilerStore.stopProfiling());
// Only one commit should have been recorded (in response to the "change" event).
const root = store.roots[0];
const data = store.profilerStore.getDataForRoot(root);
expect(data.commitData).toHaveLength(1);
expect(data.operations).toHaveLength(1);
});
// @reactVersion >= 16.9
it('should throw if component filters are modified while profiling', () => {
utils.act(() => store.profilerStore.startProfiling());
expect(() => {
utils.act(() => {
const {
ElementTypeHostComponent,
} = require('react-devtools-shared/src/frontend/types');
store.componentFilters = [
utils.createElementTypeFilter(ElementTypeHostComponent),
];
});
}).toThrow('Cannot modify filter preferences while profiling');
});
// @reactVersion >= 16.9
it('should not throw if state contains a property hasOwnProperty', () => {
let setStateCallback;
const ControlledInput = () => {
const [state, setState] = React.useState({hasOwnProperty: true});
setStateCallback = setState;
return state.hasOwnProperty;
};
// It's important that this test uses legacy sync mode.
// The root API does not trigger this particular failing case.
utils.act(() => render());
utils.act(() => store.profilerStore.startProfiling());
utils.act(() =>
setStateCallback({
hasOwnProperty: false,
}),
);
utils.act(() => store.profilerStore.stopProfiling());
// Only one commit should have been recorded (in response to the "change" event).
const root = store.roots[0];
const data = store.profilerStore.getDataForRoot(root);
expect(data.commitData).toHaveLength(1);
expect(data.operations).toHaveLength(1);
});
// @reactVersion >= 18.0
it('should not throw while initializing context values for Fibers within a not-yet-mounted subtree', () => {
const promise = new Promise(resolve => {});
const SuspendingView = () => {
throw promise;
};
const App = () => {
return (
);
};
utils.act(() => render());
utils.act(() => store.profilerStore.startProfiling());
});
});