'use strict';
let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let act;
let AdvanceTime;
let assertLog;
let waitFor;
let waitForAll;
let waitForThrow;
function loadModules({
enableProfilerTimer = true,
enableProfilerCommitHooks = true,
enableProfilerNestedUpdatePhase = true,
} = {}) {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;
ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks;
ReactFeatureFlags.enableProfilerNestedUpdatePhase =
enableProfilerNestedUpdatePhase;
React = require('react');
Scheduler = require('scheduler');
ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
act = InternalTestUtils.act;
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
waitForAll = InternalTestUtils.waitForAll;
waitForThrow = InternalTestUtils.waitForThrow;
AdvanceTime = class extends React.Component {
static defaultProps = {
byAmount: 10,
shouldComponentUpdate: true,
};
shouldComponentUpdate(nextProps) {
return nextProps.shouldComponentUpdate;
}
render() {
Scheduler.unstable_advanceTime(this.props.byAmount);
return this.props.children || null;
}
};
}
describe(`onRender`, () => {
beforeEach(() => {
jest.resetModules();
loadModules();
});
it('should handle errors thrown', async () => {
const callback = jest.fn(id => {
if (id === 'throw') {
throw Error('expected');
}
});
let didMount = false;
class ClassComponent extends React.Component {
componentDidMount() {
didMount = true;
}
render() {
return this.props.children;
}
}
await expect(
act(() => {
ReactNoop.render(
<ClassComponent>
<React.Profiler id="do-not-throw" onRender={callback}>
<React.Profiler id="throw" onRender={callback}>
<div />
</React.Profiler>
</React.Profiler>
</ClassComponent>,
);
}),
).rejects.toThrow('expected');
expect(didMount).toBe(true);
expect(callback).toHaveBeenCalledTimes(2);
});
it('is not invoked until the commit phase', async () => {
const callback = jest.fn();
const Yield = ({value}) => {
Scheduler.log(value);
return null;
};
React.startTransition(() => {
ReactNoop.render(
<React.Profiler id="test" onRender={callback}>
<Yield value="first" />
<Yield value="last" />
</React.Profiler>,
);
});
await waitFor(['first']);
expect(callback).toHaveBeenCalledTimes(0);
await waitForAll(['last']);
expect(callback).toHaveBeenCalledTimes(1);
});
it('does not record times for components outside of Profiler tree', async () => {
jest.mock('scheduler', obj => {
const ActualScheduler = jest.requireActual('scheduler/unstable_mock');
return {
...ActualScheduler,
unstable_now: function mockUnstableNow() {
ActualScheduler.log('read current time');
return ActualScheduler.unstable_now();
},
};
});
jest.resetModules();
loadModules();
Scheduler.unstable_clearLog();
await act(() => {
ReactNoop.render(
<div>
<AdvanceTime />
<AdvanceTime />
<AdvanceTime />
<AdvanceTime />
<AdvanceTime />
</div>,
);
});
jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));
if (gate(flags => flags.enableComponentPerformanceTrack)) {
assertLog([
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
]);
} else {
assertLog([
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
'read current time',
]);
}
});
it('does not report work done on a sibling', async () => {
const callback = jest.fn();
const DoesNotUpdate = React.memo(
function DoesNotUpdateInner() {
Scheduler.unstable_advanceTime(10);
return null;
},
() => true,
);
let updateProfilerSibling;
function ProfilerSibling() {
const [count, setCount] = React.useState(0);
updateProfilerSibling = () => setCount(count + 1);
return null;
}
function App() {
return (
<React.Fragment>
<React.Profiler id="test" onRender={callback}>
<DoesNotUpdate />
</React.Profiler>
<ProfilerSibling />
</React.Fragment>
);
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(<App />);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(6);
expect(call[0]).toBe('test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(10);
expect(call[3]).toBe(10);
expect(call[4]).toBe(0);
expect(call[5]).toBe(10);
callback.mockReset();
Scheduler.unstable_advanceTime(20);
await act(() => {
root.render(<App />);
});
if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
expect(callback).not.toHaveBeenCalled();
} else {
expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
expect(call).toHaveLength(6);
expect(call[0]).toBe('test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(0);
expect(call[3]).toBe(10);
expect(call[4]).toBe(30);
expect(call[5]).toBe(30);
callback.mockReset();
}
Scheduler.unstable_advanceTime(20);
await act(() => updateProfilerSibling());
expect(callback).not.toHaveBeenCalled();
});
it('logs render times for both mount and update', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<AdvanceTime />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let [call] = callback.mock.calls;
expect(call).toHaveLength(6);
expect(call[0]).toBe('test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(10);
expect(call[3]).toBe(10);
expect(call[4]).toBe(5);
expect(call[5]).toBe(15);
callback.mockReset();
Scheduler.unstable_advanceTime(20);
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<AdvanceTime />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
[call] = callback.mock.calls;
expect(call).toHaveLength(6);
expect(call[0]).toBe('test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(10);
expect(call[3]).toBe(10);
expect(call[4]).toBe(35);
expect(call[5]).toBe(45);
callback.mockReset();
Scheduler.unstable_advanceTime(20);
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<AdvanceTime byAmount={4} />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
[call] = callback.mock.calls;
expect(call).toHaveLength(6);
expect(call[0]).toBe('test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(4);
expect(call[3]).toBe(4);
expect(call[4]).toBe(65);
expect(call[5]).toBe(69);
});
it('includes render times of nested Profilers in their parent times', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Fragment>
<React.Profiler id="parent" onRender={callback}>
<AdvanceTime byAmount={10}>
<React.Profiler id="child" onRender={callback}>
<AdvanceTime byAmount={20} />
</React.Profiler>
</AdvanceTime>
</React.Profiler>
</React.Fragment>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [childCall, parentCall] = callback.mock.calls;
expect(childCall[0]).toBe('child');
expect(parentCall[0]).toBe('parent');
expect(childCall[2]).toBe(20);
expect(childCall[3]).toBe(20);
expect(childCall[4]).toBe(15);
expect(childCall[5]).toBe(35);
expect(parentCall[2]).toBe(30);
expect(parentCall[3]).toBe(30);
expect(parentCall[4]).toBe(5);
expect(parentCall[5]).toBe(35);
});
it('traces sibling Profilers separately', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Fragment>
<React.Profiler id="first" onRender={callback}>
<AdvanceTime byAmount={20} />
</React.Profiler>
<React.Profiler id="second" onRender={callback}>
<AdvanceTime byAmount={5} />
</React.Profiler>
</React.Fragment>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [firstCall, secondCall] = callback.mock.calls;
expect(firstCall[0]).toBe('first');
expect(secondCall[0]).toBe('second');
expect(firstCall[2]).toBe(20);
expect(firstCall[3]).toBe(20);
expect(firstCall[4]).toBe(5);
expect(firstCall[5]).toBe(30);
expect(secondCall[2]).toBe(5);
expect(secondCall[3]).toBe(5);
expect(secondCall[4]).toBe(25);
expect(secondCall[5]).toBe(30);
});
it('does not include time spent outside of profile root', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Fragment>
<AdvanceTime byAmount={20} />
<React.Profiler id="test" onRender={callback}>
<AdvanceTime byAmount={5} />
</React.Profiler>
<AdvanceTime byAmount={20} />
</React.Fragment>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
const [call] = callback.mock.calls;
expect(call[0]).toBe('test');
expect(call[2]).toBe(5);
expect(call[3]).toBe(5);
expect(call[4]).toBe(25);
expect(call[5]).toBe(50);
});
it('is not called when blocked by sCU false', async () => {
const callback = jest.fn();
let instance;
class Updater extends React.Component {
state = {};
render() {
instance = this;
return this.props.children;
}
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="outer" onRender={callback}>
<Updater>
<React.Profiler id="inner" onRender={callback}>
<div />
</React.Profiler>
</Updater>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
callback.mockReset();
ReactNoop.flushSync(() => {
instance.setState({
count: 1,
});
});
expect(callback).toHaveBeenCalledTimes(1);
expect(callback.mock.calls[0][0]).toBe('outer');
});
it('decreases actual time but not base time when sCU prevents an update', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<AdvanceTime byAmount={10}>
<AdvanceTime byAmount={13} shouldComponentUpdate={false} />
</AdvanceTime>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
Scheduler.unstable_advanceTime(30);
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<AdvanceTime byAmount={4}>
<AdvanceTime byAmount={7} shouldComponentUpdate={false} />
</AdvanceTime>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [mountCall, updateCall] = callback.mock.calls;
expect(mountCall[1]).toBe('mount');
expect(mountCall[2]).toBe(23);
expect(mountCall[3]).toBe(23);
expect(mountCall[4]).toBe(5);
expect(mountCall[5]).toBe(28);
expect(updateCall[1]).toBe('update');
expect(updateCall[2]).toBe(4);
expect(updateCall[3]).toBe(17);
expect(updateCall[4]).toBe(58);
expect(updateCall[5]).toBe(62);
});
it('includes time spent in render phase lifecycles', async () => {
class WithLifecycles extends React.Component {
state = {};
static getDerivedStateFromProps() {
Scheduler.unstable_advanceTime(3);
return null;
}
shouldComponentUpdate() {
Scheduler.unstable_advanceTime(7);
return true;
}
render() {
Scheduler.unstable_advanceTime(5);
return null;
}
}
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<WithLifecycles />
</React.Profiler>,
);
});
Scheduler.unstable_advanceTime(15);
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<WithLifecycles />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [mountCall, updateCall] = callback.mock.calls;
expect(mountCall[1]).toBe('mount');
expect(mountCall[2]).toBe(8);
expect(mountCall[3]).toBe(8);
expect(mountCall[4]).toBe(5);
expect(mountCall[5]).toBe(13);
expect(updateCall[1]).toBe('update');
expect(updateCall[2]).toBe(15);
expect(updateCall[3]).toBe(15);
expect(updateCall[4]).toBe(28);
expect(updateCall[5]).toBe(43);
});
it('should clear nested-update flag when multiple cascading renders are scheduled', async () => {
jest.resetModules();
loadModules();
function Component() {
const [didMount, setDidMount] = React.useState(false);
const [didMountAndUpdate, setDidMountAndUpdate] = React.useState(false);
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
React.useEffect(() => {
if (didMount && !didMountAndUpdate) {
setDidMountAndUpdate(true);
}
}, [didMount, didMountAndUpdate]);
Scheduler.log(`${didMount}:${didMountAndUpdate}`);
return null;
}
const onRender = jest.fn();
await act(() => {
ReactNoop.render(
<React.Profiler id="root" onRender={onRender}>
<Component />
</React.Profiler>,
);
});
assertLog(['false:false', 'true:false', 'true:true']);
expect(onRender).toHaveBeenCalledTimes(3);
expect(onRender.mock.calls[0][1]).toBe('mount');
expect(onRender.mock.calls[1][1]).toBe('nested-update');
expect(onRender.mock.calls[2][1]).toBe('update');
});
it('is properly distinguish updates and nested-updates when there is more than sync remaining work', () => {
jest.resetModules();
loadModules();
function Component() {
const [didMount, setDidMount] = React.useState(false);
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
Scheduler.log(didMount);
return didMount;
}
const onRender = jest.fn();
React.startTransition(() =>
ReactNoop.render(
<React.Profiler id="root" onRender={onRender}>
<Component />
</React.Profiler>,
),
);
ReactNoop.flushSync(() => {
ReactNoop.render(
<React.Profiler id="root" onRender={onRender}>
<Component />
</React.Profiler>,
);
});
assertLog([false, true]);
expect(onRender).toHaveBeenCalledTimes(2);
expect(onRender.mock.calls[0][1]).toBe('mount');
expect(onRender.mock.calls[1][1]).toBe('nested-update');
});
describe('with regard to interruptions', () => {
it('should accumulate actual time after a scheduling interruptions', async () => {
const callback = jest.fn();
const Yield = ({renderTime}) => {
Scheduler.unstable_advanceTime(renderTime);
Scheduler.log('Yield:' + renderTime);
return null;
};
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
React.startTransition(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={2} />
<Yield renderTime={3} />
</React.Profiler>,
);
});
await waitFor(['Yield:2']);
expect(callback).toHaveBeenCalledTimes(0);
await waitForAll(['Yield:3']);
expect(callback).toHaveBeenCalledTimes(1);
const [call] = callback.mock.calls;
expect(call[2]).toBe(5);
expect(call[3]).toBe(5);
expect(call[4]).toBe(5);
expect(call[5]).toBe(10);
});
it('should not include time between frames', async () => {
const callback = jest.fn();
const Yield = ({renderTime}) => {
Scheduler.unstable_advanceTime(renderTime);
Scheduler.log('Yield:' + renderTime);
return null;
};
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
React.startTransition(() => {
root.render(
<React.Profiler id="outer" onRender={callback}>
<Yield renderTime={5} />
<Yield renderTime={10} />
<React.Profiler id="inner" onRender={callback}>
<Yield renderTime={17} />
</React.Profiler>
</React.Profiler>,
);
});
await waitFor(['Yield:5']);
expect(callback).toHaveBeenCalledTimes(0);
Scheduler.unstable_advanceTime(50);
await waitForAll(['Yield:10', 'Yield:17']);
expect(callback).toHaveBeenCalledTimes(2);
const [innerCall, outerCall] = callback.mock.calls;
expect(innerCall[0]).toBe('inner');
expect(innerCall[2]).toBe(17);
expect(innerCall[3]).toBe(17);
expect(innerCall[4]).toBe(70);
expect(innerCall[5]).toBe(87);
expect(outerCall[0]).toBe('outer');
expect(outerCall[2]).toBe(32);
expect(outerCall[3]).toBe(32);
expect(outerCall[4]).toBe(5);
expect(outerCall[5]).toBe(87);
});
it('should report the expected times when a high-pri update replaces a mount in-progress', async () => {
const callback = jest.fn();
const Yield = ({renderTime}) => {
Scheduler.unstable_advanceTime(renderTime);
Scheduler.log('Yield:' + renderTime);
return null;
};
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
React.startTransition(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={10} />
<Yield renderTime={20} />
</React.Profiler>,
);
});
await waitFor(['Yield:10']);
expect(callback).toHaveBeenCalledTimes(0);
Scheduler.unstable_advanceTime(100);
ReactNoop.flushSync(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={5} />
</React.Profiler>,
);
});
assertLog(['Yield:5']);
expect(callback).toHaveBeenCalledTimes(1);
const call = callback.mock.calls[0];
expect(call[2]).toBe(5);
expect(call[3]).toBe(5);
expect(call[4]).toBe(115);
expect(call[5]).toBe(120);
callback.mockReset();
await waitForAll([]);
expect(callback).toHaveBeenCalledTimes(0);
});
it('should report the expected times when a high-priority update replaces a low-priority update', async () => {
const callback = jest.fn();
const Yield = ({renderTime}) => {
Scheduler.unstable_advanceTime(renderTime);
Scheduler.log('Yield:' + renderTime);
return null;
};
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={6} />
<Yield renderTime={15} />
</React.Profiler>,
);
await waitForAll(['Yield:6', 'Yield:15']);
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call[2]).toBe(21);
expect(call[3]).toBe(21);
expect(call[4]).toBe(5);
expect(call[5]).toBe(26);
callback.mockReset();
Scheduler.unstable_advanceTime(30);
React.startTransition(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={3} />
<Yield renderTime={5} />
<Yield renderTime={9} />
</React.Profiler>,
);
});
await waitFor(['Yield:3']);
expect(callback).toHaveBeenCalledTimes(0);
Scheduler.unstable_advanceTime(100);
await waitFor(['Yield:5']);
expect(callback).toHaveBeenCalledTimes(0);
Scheduler.unstable_advanceTime(100);
ReactNoop.flushSync(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<Yield renderTime={11} />
</React.Profiler>,
);
});
assertLog(['Yield:11']);
expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
expect(call[2]).toBe(11);
expect(call[3]).toBe(11);
expect(call[4]).toBe(264);
expect(call[5]).toBe(275);
await waitForAll([]);
expect(callback).toHaveBeenCalledTimes(1);
});
it('should report the expected times when a high-priority update interrupts a low-priority update', async () => {
const callback = jest.fn();
const Yield = ({renderTime}) => {
Scheduler.unstable_advanceTime(renderTime);
Scheduler.log('Yield:' + renderTime);
return null;
};
let first;
class FirstComponent extends React.Component {
state = {renderTime: 1};
render() {
first = this;
Scheduler.unstable_advanceTime(this.state.renderTime);
Scheduler.log('FirstComponent:' + this.state.renderTime);
return <Yield renderTime={4} />;
}
}
let second;
class SecondComponent extends React.Component {
state = {renderTime: 2};
render() {
second = this;
Scheduler.unstable_advanceTime(this.state.renderTime);
Scheduler.log('SecondComponent:' + this.state.renderTime);
return <Yield renderTime={7} />;
}
}
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
root.render(
<React.Profiler id="test" onRender={callback}>
<FirstComponent />
<SecondComponent />
</React.Profiler>,
);
await waitForAll([
'FirstComponent:1',
'Yield:4',
'SecondComponent:2',
'Yield:7',
]);
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call[2]).toBe(14);
expect(call[3]).toBe(14);
expect(call[4]).toBe(5);
expect(call[5]).toBe(19);
callback.mockClear();
Scheduler.unstable_advanceTime(100);
React.startTransition(() => {
first.setState({renderTime: 10});
});
await waitFor(['FirstComponent:10']);
expect(callback).toHaveBeenCalledTimes(0);
Scheduler.unstable_advanceTime(100);
ReactNoop.flushSync(() => second.setState({renderTime: 30}));
assertLog(['SecondComponent:30', 'Yield:7']);
expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
expect(call[2]).toBe(37);
expect(call[3]).toBe(42);
expect(call[4]).toBe(229);
expect(call[5]).toBe(266);
callback.mockClear();
Scheduler.unstable_advanceTime(100);
await waitForAll(['FirstComponent:10', 'Yield:4']);
expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
expect(call[2]).toBe(14);
expect(call[3]).toBe(51);
expect(call[4]).toBe(366);
expect(call[5]).toBe(380);
});
it('should accumulate actual time after an error handled by componentDidCatch()', async () => {
const callback = jest.fn();
const ThrowsError = ({unused}) => {
Scheduler.unstable_advanceTime(3);
throw Error('expected error');
};
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
Scheduler.unstable_advanceTime(2);
return this.state.error === null ? (
this.props.children
) : (
<AdvanceTime byAmount={20} />
);
}
}
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<ErrorBoundary>
<AdvanceTime byAmount={9} />
<ThrowsError />
</ErrorBoundary>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [mountCall, updateCall] = callback.mock.calls;
expect(mountCall[1]).toBe('mount');
expect(mountCall[2]).toBe(14);
expect(mountCall[3]).toBe(2);
expect(mountCall[4]).toBe(19);
expect(mountCall[5]).toBe(33);
expect(updateCall[1]).toBe('nested-update');
expect(updateCall[2]).toBe(22);
expect(updateCall[3]).toBe(22);
expect(updateCall[4]).toBe(33);
expect(updateCall[5]).toBe(55);
});
it('should accumulate actual time after an error handled by getDerivedStateFromError()', async () => {
const callback = jest.fn();
const ThrowsError = ({unused}) => {
Scheduler.unstable_advanceTime(10);
throw Error('expected error');
};
class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
Scheduler.unstable_advanceTime(2);
return this.state.error === null ? (
this.props.children
) : (
<AdvanceTime byAmount={20} />
);
}
}
Scheduler.unstable_advanceTime(5);
await act(() => {
const root = ReactNoop.createRoot();
root.render(
<React.Profiler id="test" onRender={callback}>
<ErrorBoundary>
<AdvanceTime byAmount={5} />
<ThrowsError />
</ErrorBoundary>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
const [mountCall] = callback.mock.calls;
expect(mountCall[1]).toBe('mount');
expect(mountCall[2]).toBe(39);
expect(mountCall[3]).toBe(22);
expect(mountCall[4]).toBe(44);
expect(mountCall[5]).toBe(83);
});
it('should reset the fiber stack correct after a "complete" phase error', async () => {
jest.resetModules();
loadModules({
useNoopRenderer: true,
});
ReactNoop.render(
<React.Profiler id="profiler" onRender={jest.fn()}>
<errorInCompletePhase>hi</errorInCompletePhase>
</React.Profiler>,
);
await waitForThrow('Error in host config.');
ReactNoop.render(
<React.Profiler id="profiler" onRender={jest.fn()}>
<errorInCompletePhase>
<span>hi</span>
</errorInCompletePhase>
</React.Profiler>,
);
await waitForThrow('Error in host config.');
ReactNoop.render(
<React.Profiler id="profiler" onRender={jest.fn()}>
<span>hi</span>
</React.Profiler>,
);
await waitForAll([]);
});
});
it('reflects the most recently rendered id value', async () => {
const callback = jest.fn();
Scheduler.unstable_advanceTime(5);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="one" onRender={callback}>
<AdvanceTime byAmount={2} />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
Scheduler.unstable_advanceTime(20);
await act(() => {
root.render(
<React.Profiler id="two" onRender={callback}>
<AdvanceTime byAmount={1} />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
const [mountCall, updateCall] = callback.mock.calls;
expect(mountCall[0]).toBe('one');
expect(mountCall[1]).toBe('mount');
expect(mountCall[2]).toBe(2);
expect(mountCall[3]).toBe(2);
expect(mountCall[4]).toBe(5);
expect(updateCall[0]).toBe('two');
expect(updateCall[1]).toBe('update');
expect(updateCall[2]).toBe(1);
expect(updateCall[3]).toBe(1);
expect(updateCall[4]).toBe(27);
});
it('should not be called until after mutations', async () => {
let classComponentMounted = false;
const callback = jest.fn(
(id, phase, actualDuration, baseDuration, startTime, commitTime) => {
expect(classComponentMounted).toBe(true);
expect(commitTime).toBe(2);
},
);
class ClassComponent extends React.Component {
componentDidMount() {
Scheduler.unstable_advanceTime(5);
classComponentMounted = true;
}
render() {
Scheduler.unstable_advanceTime(2);
return null;
}
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="test" onRender={callback}>
<ClassComponent />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
});
});
describe(`onCommit`, () => {
beforeEach(() => {
jest.resetModules();
loadModules();
});
it('should report time spent in layout effects and commit lifecycles', async () => {
const callback = jest.fn();
const ComponentWithEffects = () => {
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(10);
return () => {
Scheduler.unstable_advanceTime(100);
};
}, []);
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(1000);
return () => {
Scheduler.unstable_advanceTime(10000);
};
});
React.useEffect(() => {
Scheduler.unstable_advanceTime(5);
return () => {
Scheduler.unstable_advanceTime(7);
};
});
return null;
};
class ComponentWithCommitHooks extends React.Component {
componentDidMount() {
Scheduler.unstable_advanceTime(100000);
}
componentDidUpdate() {
Scheduler.unstable_advanceTime(1000000);
}
render() {
return null;
}
}
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="mount-test" onCommit={callback}>
<ComponentWithEffects />
<ComponentWithCommitHooks />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(101010);
expect(call[3]).toBe(1);
Scheduler.unstable_advanceTime(1);
await act(() => {
root.render(
<React.Profiler id="update-test" onCommit={callback}>
<ComponentWithEffects />
<ComponentWithCommitHooks />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('update-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(1011000);
expect(call[3]).toBe(101017);
Scheduler.unstable_advanceTime(1);
await act(() => {
root.render(<React.Profiler id="unmount-test" onCommit={callback} />);
});
expect(callback).toHaveBeenCalledTimes(3);
call = callback.mock.calls[2];
expect(call).toHaveLength(4);
expect(call[0]).toBe('unmount-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(10100);
expect(call[3]).toBe(1112030);
});
it('should report time spent in layout effects and commit lifecycles with cascading renders', async () => {
const callback = jest.fn();
const ComponentWithEffects = ({shouldCascade}) => {
const [didCascade, setDidCascade] = React.useState(false);
Scheduler.unstable_advanceTime(100000000);
React.useLayoutEffect(() => {
if (shouldCascade && !didCascade) {
setDidCascade(true);
}
Scheduler.unstable_advanceTime(didCascade ? 30 : 10);
return () => {
Scheduler.unstable_advanceTime(100);
};
}, [didCascade, shouldCascade]);
return null;
};
class ComponentWithCommitHooks extends React.Component {
state = {
didCascade: false,
};
componentDidMount() {
Scheduler.unstable_advanceTime(1000);
}
componentDidUpdate() {
Scheduler.unstable_advanceTime(10000);
if (this.props.shouldCascade && !this.state.didCascade) {
this.setState({didCascade: true});
}
}
render() {
Scheduler.unstable_advanceTime(1000000000);
return null;
}
}
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="mount-test" onCommit={callback}>
<ComponentWithEffects shouldCascade={true} />
<ComponentWithCommitHooks />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(1010);
expect(call[3]).toBe(1100000001);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('nested-update');
expect(call[2]).toBe(130);
expect(call[3]).toBe(1200001011);
Scheduler.unstable_advanceTime(1);
await act(() => {
root.render(
<React.Profiler id="update-test" onCommit={callback}>
<ComponentWithEffects />
<ComponentWithCommitHooks shouldCascade={true} />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(4);
call = callback.mock.calls[2];
expect(call).toHaveLength(4);
expect(call[0]).toBe('update-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(10130);
expect(call[3]).toBe(2300001142);
call = callback.mock.calls[3];
expect(call).toHaveLength(4);
expect(call[0]).toBe('update-test');
expect(call[1]).toBe('nested-update');
expect(call[2]).toBe(10000);
expect(call[3]).toBe(3300011272);
});
it('should include time spent in ref callbacks', async () => {
const callback = jest.fn();
const refSetter = ref => {
if (ref !== null) {
Scheduler.unstable_advanceTime(10);
} else {
Scheduler.unstable_advanceTime(100);
}
};
class ClassComponent extends React.Component {
render() {
return null;
}
}
const Component = () => {
Scheduler.unstable_advanceTime(1000);
return <ClassComponent ref={refSetter} />;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root" onCommit={callback}>
<Component />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(10);
expect(call[3]).toBe(1001);
callback.mockClear();
await act(() => {
root.render(<React.Profiler id="root" onCommit={callback} />);
});
expect(callback).toHaveBeenCalledTimes(1);
call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
expect(call[2]).toBe(100);
expect(call[3]).toBe(1011);
});
it('should bubble time spent in layout effects to higher profilers', async () => {
const callback = jest.fn();
const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => {
const setCount = React.useState(0)[1];
if (setCountRef != null) {
setCountRef.current = setCount;
}
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(duration);
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
};
});
Scheduler.unstable_advanceTime(1);
return null;
};
const setCountRef = React.createRef(null);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root-mount" onCommit={callback}>
<React.Profiler id="a">
<ComponentWithEffects
duration={10}
cleanupDuration={100}
setCountRef={setCountRef}
/>
</React.Profiler>
<React.Profiler id="b">
<ComponentWithEffects duration={1000} cleanupDuration={10000} />
</React.Profiler>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-mount');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(1010);
expect(call[3]).toBe(2);
await act(() => setCountRef.current(count => count + 1));
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-mount');
expect(call[1]).toBe('update');
expect(call[2]).toBe(110);
expect(call[3]).toBe(1013);
await act(() => {
root.render(
<React.Profiler id="root-update" onCommit={callback}>
<React.Profiler id="b">
<ComponentWithEffects duration={1000} cleanupDuration={10000} />
</React.Profiler>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(3);
call = callback.mock.calls[2];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-update');
expect(call[1]).toBe('update');
expect(call[2]).toBe(11100);
expect(call[3]).toBe(1124);
});
it('should properly report time in layout effects even when there are errors', async () => {
const callback = jest.fn();
class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
return this.state.error === null
? this.props.children
: this.props.fallback;
}
}
const ComponentWithEffects = ({
cleanupDuration,
duration,
effectDuration,
shouldThrow,
}) => {
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(effectDuration);
if (shouldThrow) {
throw Error('expected');
}
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
};
});
Scheduler.unstable_advanceTime(duration);
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root" onCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={true}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(100100);
expect(call[3]).toBe(10011);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('nested-update');
expect(call[2]).toBe(100000000);
expect(call[3]).toBe(10110111);
});
it('should properly report time in layout effect cleanup functions even when there are errors', async () => {
const callback = jest.fn();
class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
return this.state.error === null
? this.props.children
: this.props.fallback;
}
}
const ComponentWithEffects = ({
cleanupDuration,
duration,
effectDuration,
shouldThrow = false,
}) => {
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(effectDuration);
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
if (shouldThrow) {
throw Error('expected');
}
};
});
Scheduler.unstable_advanceTime(duration);
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root" onCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={true}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(100100);
expect(call[3]).toBe(10011);
callback.mockClear();
await act(() => {
root.render(
<React.Profiler id="root" onCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={false}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
expect(call[2]).toBe(1101100);
expect(call[3]).toBe(120121);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('nested-update');
expect(call[2]).toBe(100001000);
expect(call[3]).toBe(11221221);
});
});
describe(`onPostCommit`, () => {
beforeEach(() => {
jest.resetModules();
loadModules();
});
it('should report time spent in passive effects', async () => {
const callback = jest.fn();
const ComponentWithEffects = () => {
React.useLayoutEffect(() => {
Scheduler.unstable_advanceTime(5);
return () => {
Scheduler.unstable_advanceTime(7);
};
});
React.useEffect(() => {
Scheduler.unstable_advanceTime(10);
return () => {
Scheduler.unstable_advanceTime(100);
};
}, []);
React.useEffect(() => {
Scheduler.unstable_advanceTime(1000);
return () => {
Scheduler.unstable_advanceTime(10000);
};
});
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="mount-test" onPostCommit={callback}>
<ComponentWithEffects />
</React.Profiler>,
);
});
await waitForAll([]);
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(1010);
expect(call[3]).toBe(1);
Scheduler.unstable_advanceTime(1);
await act(() => {
root.render(
<React.Profiler id="update-test" onPostCommit={callback}>
<ComponentWithEffects />
</React.Profiler>,
);
});
await waitForAll([]);
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('update-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(11000);
expect(call[3]).toBe(1017);
Scheduler.unstable_advanceTime(1);
await act(() => {
root.render(<React.Profiler id="unmount-test" onPostCommit={callback} />);
});
await waitForAll([]);
expect(callback).toHaveBeenCalledTimes(3);
call = callback.mock.calls[2];
expect(call).toHaveLength(4);
expect(call[0]).toBe('unmount-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(10100);
expect(call[3]).toBe(12030);
});
it('should report time spent in passive effects with cascading renders', async () => {
const callback = jest.fn();
const ComponentWithEffects = () => {
const [didMount, setDidMount] = React.useState(false);
Scheduler.unstable_advanceTime(1000);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}
Scheduler.unstable_advanceTime(didMount ? 30 : 10);
return () => {
Scheduler.unstable_advanceTime(100);
};
}, [didMount]);
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="mount-test" onPostCommit={callback}>
<ComponentWithEffects />
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(10);
expect(call[3]).toBe(1001);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('mount-test');
expect(call[1]).toBe('update');
expect(call[2]).toBe(130);
expect(call[3]).toBe(2011);
});
it('should bubble time spent in effects to higher profilers', async () => {
const callback = jest.fn();
const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => {
const setCount = React.useState(0)[1];
if (setCountRef != null) {
setCountRef.current = setCount;
}
React.useEffect(() => {
Scheduler.unstable_advanceTime(duration);
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
};
});
Scheduler.unstable_advanceTime(1);
return null;
};
const setCountRef = React.createRef(null);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root-mount" onPostCommit={callback}>
<React.Profiler id="a">
<ComponentWithEffects
duration={10}
cleanupDuration={100}
setCountRef={setCountRef}
/>
</React.Profiler>
<React.Profiler id="b">
<ComponentWithEffects duration={1000} cleanupDuration={10000} />
</React.Profiler>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-mount');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(1010);
expect(call[3]).toBe(2);
await act(() => setCountRef.current(count => count + 1));
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-mount');
expect(call[1]).toBe('update');
expect(call[2]).toBe(110);
expect(call[3]).toBe(1013);
await act(() => {
root.render(
<React.Profiler id="root-update" onPostCommit={callback}>
<React.Profiler id="b">
<ComponentWithEffects duration={1000} cleanupDuration={10000} />
</React.Profiler>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(3);
call = callback.mock.calls[2];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root-update');
expect(call[1]).toBe('update');
expect(call[2]).toBe(11100);
expect(call[3]).toBe(1124);
});
it('should properly report time in passive effects even when there are errors', async () => {
const callback = jest.fn();
class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
return this.state.error === null
? this.props.children
: this.props.fallback;
}
}
const ComponentWithEffects = ({
cleanupDuration,
duration,
effectDuration,
shouldThrow,
}) => {
React.useEffect(() => {
Scheduler.unstable_advanceTime(effectDuration);
if (shouldThrow) {
throw Error('expected');
}
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
};
});
Scheduler.unstable_advanceTime(duration);
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root" onPostCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={true}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(100100);
expect(call[3]).toBe(10011);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
expect(call[2]).toBe(100000000);
expect(call[3]).toBe(10110111);
});
it('should properly report time in passive effect cleanup functions even when there are errors', async () => {
const callback = jest.fn();
class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
return this.state.error === null
? this.props.children
: this.props.fallback;
}
}
const ComponentWithEffects = ({
cleanupDuration,
duration,
effectDuration,
shouldThrow = false,
id,
}) => {
React.useEffect(() => {
Scheduler.unstable_advanceTime(effectDuration);
return () => {
Scheduler.unstable_advanceTime(cleanupDuration);
if (shouldThrow) {
throw Error('expected');
}
};
});
Scheduler.unstable_advanceTime(duration);
return null;
};
Scheduler.unstable_advanceTime(1);
const root = ReactNoop.createRoot();
await act(() => {
root.render(
<React.Profiler id="root" onPostCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={true}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(1);
let call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('mount');
expect(call[2]).toBe(100100);
expect(call[3]).toBe(10011);
callback.mockClear();
await act(() => {
root.render(
<React.Profiler id="root" onPostCommit={callback}>
<ErrorBoundary
fallback={
<ComponentWithEffects
duration={10000000}
effectDuration={100000000}
cleanupDuration={1000000000}
/>
}>
<ComponentWithEffects
duration={10}
effectDuration={100}
cleanupDuration={1000}
shouldThrow={false}
/>
</ErrorBoundary>
<ComponentWithEffects
duration={10000}
effectDuration={100000}
cleanupDuration={1000000}
/>
</React.Profiler>,
);
});
expect(callback).toHaveBeenCalledTimes(2);
call = callback.mock.calls[0];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
expect(call[2]).toBe(1101100);
expect(call[3]).toBe(120121);
call = callback.mock.calls[1];
expect(call).toHaveLength(4);
expect(call[0]).toBe('root');
expect(call[1]).toBe('update');
expect(call[2]).toBe(100001000);
expect(call[3]).toBe(11221221);
});
});