'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.enableDeferRootSchedulingToMicrotask)) {
assertLog([
'read current time',
'read current time',
'read current time',
'read current time',
]);
} else if (gate(flags => !flags.allowConcurrentByDefault)) {
assertLog([
'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',
]);
}
});
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(1100);
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(0);
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(1100);
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(100000000);
expect(call[3]).toBe(11221221);
});
});