Use strict equality (===) to prevent type coercion bugs
if (id === 'throw') {
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @emails react-core8 * @jest-environment node9 */1011'use strict';1213let React;14let ReactFeatureFlags;15let ReactNoop;16let Scheduler;17let act;18let AdvanceTime;19let assertLog;20let waitFor;21let waitForAll;22let waitForThrow;2324function loadModules({25 enableProfilerTimer = true,26 enableProfilerCommitHooks = true,27 enableProfilerNestedUpdatePhase = true,28} = {}) {29 ReactFeatureFlags = require('shared/ReactFeatureFlags');3031 ReactFeatureFlags.enableProfilerTimer = enableProfilerTimer;32 ReactFeatureFlags.enableProfilerCommitHooks = enableProfilerCommitHooks;33 ReactFeatureFlags.enableProfilerNestedUpdatePhase =34 enableProfilerNestedUpdatePhase;3536 React = require('react');37 Scheduler = require('scheduler');38 ReactNoop = require('react-noop-renderer');39 const InternalTestUtils = require('internal-test-utils');40 act = InternalTestUtils.act;41 assertLog = InternalTestUtils.assertLog;42 waitFor = InternalTestUtils.waitFor;43 waitForAll = InternalTestUtils.waitForAll;44 waitForThrow = InternalTestUtils.waitForThrow;4546 AdvanceTime = class extends React.Component {47 static defaultProps = {48 byAmount: 10,49 shouldComponentUpdate: true,50 };51 shouldComponentUpdate(nextProps) {52 return nextProps.shouldComponentUpdate;53 }54 render() {55 // Simulate time passing when this component is rendered56 Scheduler.unstable_advanceTime(this.props.byAmount);57 return this.props.children || null;58 }59 };60}6162describe(`onRender`, () => {63 beforeEach(() => {64 jest.resetModules();65 loadModules();66 });6768 it('should handle errors thrown', async () => {69 const callback = jest.fn(id => {70 if (id === 'throw') {71 throw Error('expected');72 }73 });7475 let didMount = false;76 class ClassComponent extends React.Component {77 componentDidMount() {78 didMount = true;79 }80 render() {81 return this.props.children;82 }83 }8485 // Errors thrown from onRender should not break the commit phase,86 // Or prevent other lifecycles from being called.87 await expect(88 act(() => {89 ReactNoop.render(90 <ClassComponent>91 <React.Profiler id="do-not-throw" onRender={callback}>92 <React.Profiler id="throw" onRender={callback}>93 <div />94 </React.Profiler>95 </React.Profiler>96 </ClassComponent>,97 );98 }),99 ).rejects.toThrow('expected');100 expect(didMount).toBe(true);101 expect(callback).toHaveBeenCalledTimes(2);102 });103104 it('is not invoked until the commit phase', async () => {105 const callback = jest.fn();106107 const Yield = ({value}) => {108 Scheduler.log(value);109 return null;110 };111112 React.startTransition(() => {113 ReactNoop.render(114 <React.Profiler id="test" onRender={callback}>115 <Yield value="first" />116 <Yield value="last" />117 </React.Profiler>,118 );119 });120121 // Times are logged until a render is committed.122 await waitFor(['first']);123 expect(callback).toHaveBeenCalledTimes(0);124 await waitForAll(['last']);125 expect(callback).toHaveBeenCalledTimes(1);126 });127128 // @gate !__DEV__129 it('does not record times for components outside of Profiler tree', async () => {130 // Mock the Scheduler module so we can track how many times the current131 // time is read132 jest.mock('scheduler', obj => {133 const ActualScheduler = jest.requireActual('scheduler/unstable_mock');134 return {135 ...ActualScheduler,136 unstable_now: function mockUnstableNow() {137 ActualScheduler.log('read current time');138 return ActualScheduler.unstable_now();139 },140 };141 });142143 jest.resetModules();144145 loadModules();146147 // Clear yields in case the current time is read during initialization.148 Scheduler.unstable_clearLog();149150 await act(() => {151 ReactNoop.render(152 <div>153 <AdvanceTime />154 <AdvanceTime />155 <AdvanceTime />156 <AdvanceTime />157 <AdvanceTime />158 </div>,159 );160 });161162 // Restore original mock163 jest.mock('scheduler', () => jest.requireActual('scheduler/unstable_mock'));164165 if (gate(flags => flags.enableComponentPerformanceTrack)) {166 assertLog([167 'read current time',168 'read current time',169 'read current time',170 'read current time',171 'read current time',172 'read current time',173 'read current time',174 'read current time',175 'read current time',176 'read current time',177 'read current time',178 'read current time',179 'read current time',180 'read current time',181 'read current time',182 'read current time',183 ]);184 } else {185 assertLog([186 'read current time',187 'read current time',188 'read current time',189 'read current time',190 'read current time',191 'read current time',192 'read current time',193 'read current time',194 'read current time',195 'read current time',196 'read current time',197 ]);198 }199 });200201 it('does not report work done on a sibling', async () => {202 const callback = jest.fn();203204 const DoesNotUpdate = React.memo(205 function DoesNotUpdateInner() {206 Scheduler.unstable_advanceTime(10);207 return null;208 },209 () => true,210 );211212 let updateProfilerSibling;213214 function ProfilerSibling() {215 const [count, setCount] = React.useState(0);216 updateProfilerSibling = () => setCount(count + 1);217 return null;218 }219220 function App() {221 return (222 <React.Fragment>223 <React.Profiler id="test" onRender={callback}>224 <DoesNotUpdate />225 </React.Profiler>226 <ProfilerSibling />227 </React.Fragment>228 );229 }230231 const root = ReactNoop.createRoot();232 await act(() => {233 root.render(<App />);234 });235236 expect(callback).toHaveBeenCalledTimes(1);237238 let call = callback.mock.calls[0];239240 expect(call).toHaveLength(6);241 expect(call[0]).toBe('test');242 expect(call[1]).toBe('mount');243 expect(call[2]).toBe(10); // actual time244 expect(call[3]).toBe(10); // base time245 expect(call[4]).toBe(0); // start time246 expect(call[5]).toBe(10); // commit time247248 callback.mockReset();249250 Scheduler.unstable_advanceTime(20); // 10 -> 30251252 await act(() => {253 root.render(<App />);254 });255256 if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {257 // None of the Profiler's subtree was rendered because App bailed out before the Profiler.258 // So we expect onRender not to be called.259 expect(callback).not.toHaveBeenCalled();260 } else {261 // Updating a parent reports a re-render,262 // since React technically did a little bit of work between the Profiler and the bailed out subtree.263 // This is not optimal but it's how the old reconciler fork works.264 expect(callback).toHaveBeenCalledTimes(1);265266 call = callback.mock.calls[0];267268 expect(call).toHaveLength(6);269 expect(call[0]).toBe('test');270 expect(call[1]).toBe('update');271 expect(call[2]).toBe(0); // actual time272 expect(call[3]).toBe(10); // base time273 expect(call[4]).toBe(30); // start time274 expect(call[5]).toBe(30); // commit time275276 callback.mockReset();277 }278279 Scheduler.unstable_advanceTime(20); // 30 -> 50280281 // Updating a sibling should not report a re-render.282 await act(() => updateProfilerSibling());283284 expect(callback).not.toHaveBeenCalled();285 });286287 it('logs render times for both mount and update', async () => {288 const callback = jest.fn();289290 Scheduler.unstable_advanceTime(5); // 0 -> 5291292 const root = ReactNoop.createRoot();293 await act(() => {294 root.render(295 <React.Profiler id="test" onRender={callback}>296 <AdvanceTime />297 </React.Profiler>,298 );299 });300301 expect(callback).toHaveBeenCalledTimes(1);302303 let [call] = callback.mock.calls;304305 expect(call).toHaveLength(6);306 expect(call[0]).toBe('test');307 expect(call[1]).toBe('mount');308 expect(call[2]).toBe(10); // actual time309 expect(call[3]).toBe(10); // base time310 expect(call[4]).toBe(5); // start time311 expect(call[5]).toBe(15); // commit time312313 callback.mockReset();314315 Scheduler.unstable_advanceTime(20); // 15 -> 35316317 await act(() => {318 root.render(319 <React.Profiler id="test" onRender={callback}>320 <AdvanceTime />321 </React.Profiler>,322 );323 });324325 expect(callback).toHaveBeenCalledTimes(1);326327 [call] = callback.mock.calls;328329 expect(call).toHaveLength(6);330 expect(call[0]).toBe('test');331 expect(call[1]).toBe('update');332 expect(call[2]).toBe(10); // actual time333 expect(call[3]).toBe(10); // base time334 expect(call[4]).toBe(35); // start time335 expect(call[5]).toBe(45); // commit time336337 callback.mockReset();338339 Scheduler.unstable_advanceTime(20); // 45 -> 65340341 await act(() => {342 root.render(343 <React.Profiler id="test" onRender={callback}>344 <AdvanceTime byAmount={4} />345 </React.Profiler>,346 );347 });348349 expect(callback).toHaveBeenCalledTimes(1);350351 [call] = callback.mock.calls;352353 expect(call).toHaveLength(6);354 expect(call[0]).toBe('test');355 expect(call[1]).toBe('update');356 expect(call[2]).toBe(4); // actual time357 expect(call[3]).toBe(4); // base time358 expect(call[4]).toBe(65); // start time359 expect(call[5]).toBe(69); // commit time360 });361362 it('includes render times of nested Profilers in their parent times', async () => {363 const callback = jest.fn();364365 Scheduler.unstable_advanceTime(5); // 0 -> 5366367 const root = ReactNoop.createRoot();368 await act(() => {369 root.render(370 <React.Fragment>371 <React.Profiler id="parent" onRender={callback}>372 <AdvanceTime byAmount={10}>373 <React.Profiler id="child" onRender={callback}>374 <AdvanceTime byAmount={20} />375 </React.Profiler>376 </AdvanceTime>377 </React.Profiler>378 </React.Fragment>,379 );380 });381382 expect(callback).toHaveBeenCalledTimes(2);383384 // Callbacks bubble (reverse order).385 const [childCall, parentCall] = callback.mock.calls;386 expect(childCall[0]).toBe('child');387 expect(parentCall[0]).toBe('parent');388389 // Parent times should include child times390 expect(childCall[2]).toBe(20); // actual time391 expect(childCall[3]).toBe(20); // base time392 expect(childCall[4]).toBe(15); // start time393 expect(childCall[5]).toBe(35); // commit time394 expect(parentCall[2]).toBe(30); // actual time395 expect(parentCall[3]).toBe(30); // base time396 expect(parentCall[4]).toBe(5); // start time397 expect(parentCall[5]).toBe(35); // commit time398 });399400 it('traces sibling Profilers separately', async () => {401 const callback = jest.fn();402403 Scheduler.unstable_advanceTime(5); // 0 -> 5404405 const root = ReactNoop.createRoot();406 await act(() => {407 root.render(408 <React.Fragment>409 <React.Profiler id="first" onRender={callback}>410 <AdvanceTime byAmount={20} />411 </React.Profiler>412 <React.Profiler id="second" onRender={callback}>413 <AdvanceTime byAmount={5} />414 </React.Profiler>415 </React.Fragment>,416 );417 });418419 expect(callback).toHaveBeenCalledTimes(2);420421 const [firstCall, secondCall] = callback.mock.calls;422 expect(firstCall[0]).toBe('first');423 expect(secondCall[0]).toBe('second');424425 // Parent times should include child times426 expect(firstCall[2]).toBe(20); // actual time427 expect(firstCall[3]).toBe(20); // base time428 expect(firstCall[4]).toBe(5); // start time429 expect(firstCall[5]).toBe(30); // commit time430 expect(secondCall[2]).toBe(5); // actual time431 expect(secondCall[3]).toBe(5); // base time432 expect(secondCall[4]).toBe(25); // start time433 expect(secondCall[5]).toBe(30); // commit time434 });435436 it('does not include time spent outside of profile root', async () => {437 const callback = jest.fn();438439 Scheduler.unstable_advanceTime(5); // 0 -> 5440441 const root = ReactNoop.createRoot();442 await act(() => {443 root.render(444 <React.Fragment>445 <AdvanceTime byAmount={20} />446 <React.Profiler id="test" onRender={callback}>447 <AdvanceTime byAmount={5} />448 </React.Profiler>449 <AdvanceTime byAmount={20} />450 </React.Fragment>,451 );452 });453454 expect(callback).toHaveBeenCalledTimes(1);455456 const [call] = callback.mock.calls;457 expect(call[0]).toBe('test');458 expect(call[2]).toBe(5); // actual time459 expect(call[3]).toBe(5); // base time460 expect(call[4]).toBe(25); // start time461 expect(call[5]).toBe(50); // commit time462 });463464 it('is not called when blocked by sCU false', async () => {465 const callback = jest.fn();466467 let instance;468 class Updater extends React.Component {469 state = {};470 render() {471 instance = this;472 return this.props.children;473 }474 }475476 const root = ReactNoop.createRoot();477 await act(() => {478 root.render(479 <React.Profiler id="outer" onRender={callback}>480 <Updater>481 <React.Profiler id="inner" onRender={callback}>482 <div />483 </React.Profiler>484 </Updater>485 </React.Profiler>,486 );487 });488 // All profile callbacks are called for initial render489 expect(callback).toHaveBeenCalledTimes(2);490491 callback.mockReset();492493 ReactNoop.flushSync(() => {494 instance.setState({495 count: 1,496 });497 });498499 // Only call onRender for paths that have re-rendered.500 // Since the Updater's props didn't change,501 // React does not re-render its children.502 expect(callback).toHaveBeenCalledTimes(1);503 expect(callback.mock.calls[0][0]).toBe('outer');504 });505506 it('decreases actual time but not base time when sCU prevents an update', async () => {507 const callback = jest.fn();508509 Scheduler.unstable_advanceTime(5); // 0 -> 5510511 const root = ReactNoop.createRoot();512 await act(() => {513 root.render(514 <React.Profiler id="test" onRender={callback}>515 <AdvanceTime byAmount={10}>516 <AdvanceTime byAmount={13} shouldComponentUpdate={false} />517 </AdvanceTime>518 </React.Profiler>,519 );520 });521522 expect(callback).toHaveBeenCalledTimes(1);523524 Scheduler.unstable_advanceTime(30); // 28 -> 58525526 await act(() => {527 root.render(528 <React.Profiler id="test" onRender={callback}>529 <AdvanceTime byAmount={4}>530 <AdvanceTime byAmount={7} shouldComponentUpdate={false} />531 </AdvanceTime>532 </React.Profiler>,533 );534 });535536 expect(callback).toHaveBeenCalledTimes(2);537538 const [mountCall, updateCall] = callback.mock.calls;539540 expect(mountCall[1]).toBe('mount');541 expect(mountCall[2]).toBe(23); // actual time542 expect(mountCall[3]).toBe(23); // base time543 expect(mountCall[4]).toBe(5); // start time544 expect(mountCall[5]).toBe(28); // commit time545546 expect(updateCall[1]).toBe('update');547 expect(updateCall[2]).toBe(4); // actual time548 expect(updateCall[3]).toBe(17); // base time549 expect(updateCall[4]).toBe(58); // start time550 expect(updateCall[5]).toBe(62); // commit time551 });552553 it('includes time spent in render phase lifecycles', async () => {554 class WithLifecycles extends React.Component {555 state = {};556 static getDerivedStateFromProps() {557 Scheduler.unstable_advanceTime(3);558 return null;559 }560 shouldComponentUpdate() {561 Scheduler.unstable_advanceTime(7);562 return true;563 }564 render() {565 Scheduler.unstable_advanceTime(5);566 return null;567 }568 }569570 const callback = jest.fn();571572 Scheduler.unstable_advanceTime(5); // 0 -> 5573574 const root = ReactNoop.createRoot();575 await act(() => {576 root.render(577 <React.Profiler id="test" onRender={callback}>578 <WithLifecycles />579 </React.Profiler>,580 );581 });582583 Scheduler.unstable_advanceTime(15); // 13 -> 28584585 await act(() => {586 root.render(587 <React.Profiler id="test" onRender={callback}>588 <WithLifecycles />589 </React.Profiler>,590 );591 });592593 expect(callback).toHaveBeenCalledTimes(2);594595 const [mountCall, updateCall] = callback.mock.calls;596597 expect(mountCall[1]).toBe('mount');598 expect(mountCall[2]).toBe(8); // actual time599 expect(mountCall[3]).toBe(8); // base time600 expect(mountCall[4]).toBe(5); // start time601 expect(mountCall[5]).toBe(13); // commit time602603 expect(updateCall[1]).toBe('update');604 expect(updateCall[2]).toBe(15); // actual time605 expect(updateCall[3]).toBe(15); // base time606 expect(updateCall[4]).toBe(28); // start time607 expect(updateCall[5]).toBe(43); // commit time608 });609610 it('should clear nested-update flag when multiple cascading renders are scheduled', async () => {611 jest.resetModules();612 loadModules();613614 function Component() {615 const [didMount, setDidMount] = React.useState(false);616 const [didMountAndUpdate, setDidMountAndUpdate] = React.useState(false);617618 React.useLayoutEffect(() => {619 setDidMount(true);620 }, []);621622 React.useEffect(() => {623 if (didMount && !didMountAndUpdate) {624 setDidMountAndUpdate(true);625 }626 }, [didMount, didMountAndUpdate]);627628 Scheduler.log(`${didMount}:${didMountAndUpdate}`);629630 return null;631 }632633 const onRender = jest.fn();634635 await act(() => {636 ReactNoop.render(637 <React.Profiler id="root" onRender={onRender}>638 <Component />639 </React.Profiler>,640 );641 });642 assertLog(['false:false', 'true:false', 'true:true']);643644 expect(onRender).toHaveBeenCalledTimes(3);645 expect(onRender.mock.calls[0][1]).toBe('mount');646 expect(onRender.mock.calls[1][1]).toBe('nested-update');647 expect(onRender.mock.calls[2][1]).toBe('update');648 });649650 it('is properly distinguish updates and nested-updates when there is more than sync remaining work', () => {651 jest.resetModules();652 loadModules();653654 function Component() {655 const [didMount, setDidMount] = React.useState(false);656657 React.useLayoutEffect(() => {658 setDidMount(true);659 }, []);660 Scheduler.log(didMount);661 return didMount;662 }663664 const onRender = jest.fn();665666 // Schedule low-priority work.667 React.startTransition(() =>668 ReactNoop.render(669 <React.Profiler id="root" onRender={onRender}>670 <Component />671 </React.Profiler>,672 ),673 );674675 // Flush sync work with a nested update676 ReactNoop.flushSync(() => {677 ReactNoop.render(678 <React.Profiler id="root" onRender={onRender}>679 <Component />680 </React.Profiler>,681 );682 });683 assertLog([false, true]);684685 // Verify that the nested update inside of the sync work is appropriately tagged.686 expect(onRender).toHaveBeenCalledTimes(2);687 expect(onRender.mock.calls[0][1]).toBe('mount');688 expect(onRender.mock.calls[1][1]).toBe('nested-update');689 });690691 describe('with regard to interruptions', () => {692 it('should accumulate actual time after a scheduling interruptions', async () => {693 const callback = jest.fn();694695 const Yield = ({renderTime}) => {696 Scheduler.unstable_advanceTime(renderTime);697 Scheduler.log('Yield:' + renderTime);698 return null;699 };700701 Scheduler.unstable_advanceTime(5); // 0 -> 5702703 const root = ReactNoop.createRoot();704 // Render partially, but run out of time before completing.705 React.startTransition(() => {706 root.render(707 <React.Profiler id="test" onRender={callback}>708 <Yield renderTime={2} />709 <Yield renderTime={3} />710 </React.Profiler>,711 );712 });713714 await waitFor(['Yield:2']);715 expect(callback).toHaveBeenCalledTimes(0);716717 // Resume render for remaining children.718 await waitForAll(['Yield:3']);719720 // Verify that logged times include both durations above.721 expect(callback).toHaveBeenCalledTimes(1);722 const [call] = callback.mock.calls;723 expect(call[2]).toBe(5); // actual time724 expect(call[3]).toBe(5); // base time725 expect(call[4]).toBe(5); // start time726 expect(call[5]).toBe(10); // commit time727 });728729 it('should not include time between frames', async () => {730 const callback = jest.fn();731732 const Yield = ({renderTime}) => {733 Scheduler.unstable_advanceTime(renderTime);734 Scheduler.log('Yield:' + renderTime);735 return null;736 };737738 Scheduler.unstable_advanceTime(5); // 0 -> 5739740 const root = ReactNoop.createRoot();741 // Render partially, but don't finish.742 // This partial render should take 5ms of simulated time.743 React.startTransition(() => {744 root.render(745 <React.Profiler id="outer" onRender={callback}>746 <Yield renderTime={5} />747 <Yield renderTime={10} />748 <React.Profiler id="inner" onRender={callback}>749 <Yield renderTime={17} />750 </React.Profiler>751 </React.Profiler>,752 );753 });754755 await waitFor(['Yield:5']);756 expect(callback).toHaveBeenCalledTimes(0);757758 // Simulate time moving forward while frame is paused.759 Scheduler.unstable_advanceTime(50); // 10 -> 60760761 // Flush the remaining work,762 // Which should take an additional 10ms of simulated time.763 await waitForAll(['Yield:10', 'Yield:17']);764 expect(callback).toHaveBeenCalledTimes(2);765766 const [innerCall, outerCall] = callback.mock.calls;767768 // Verify that the actual time includes all work times,769 // But not the time that elapsed between frames.770 expect(innerCall[0]).toBe('inner');771 expect(innerCall[2]).toBe(17); // actual time772 expect(innerCall[3]).toBe(17); // base time773 expect(innerCall[4]).toBe(70); // start time774 expect(innerCall[5]).toBe(87); // commit time775 expect(outerCall[0]).toBe('outer');776 expect(outerCall[2]).toBe(32); // actual time777 expect(outerCall[3]).toBe(32); // base time778 expect(outerCall[4]).toBe(5); // start time779 expect(outerCall[5]).toBe(87); // commit time780 });781782 it('should report the expected times when a high-pri update replaces a mount in-progress', async () => {783 const callback = jest.fn();784785 const Yield = ({renderTime}) => {786 Scheduler.unstable_advanceTime(renderTime);787 Scheduler.log('Yield:' + renderTime);788 return null;789 };790791 Scheduler.unstable_advanceTime(5); // 0 -> 5792793 const root = ReactNoop.createRoot();794 // Render a partially update, but don't finish.795 // This partial render should take 10ms of simulated time.796 React.startTransition(() => {797 root.render(798 <React.Profiler id="test" onRender={callback}>799 <Yield renderTime={10} />800 <Yield renderTime={20} />801 </React.Profiler>,802 );803 });804805 await waitFor(['Yield:10']);806 expect(callback).toHaveBeenCalledTimes(0);807808 // Simulate time moving forward while frame is paused.809 Scheduler.unstable_advanceTime(100); // 15 -> 115810811 // Interrupt with higher priority work.812 // The interrupted work simulates an additional 5ms of time.813 ReactNoop.flushSync(() => {814 root.render(815 <React.Profiler id="test" onRender={callback}>816 <Yield renderTime={5} />817 </React.Profiler>,818 );819 });820 assertLog(['Yield:5']);821822 // The initial work was thrown away in this case,823 // So the actual and base times should only include the final rendered tree times.824 expect(callback).toHaveBeenCalledTimes(1);825 const call = callback.mock.calls[0];826 expect(call[2]).toBe(5); // actual time827 expect(call[3]).toBe(5); // base time828 expect(call[4]).toBe(115); // start time829 expect(call[5]).toBe(120); // commit time830831 callback.mockReset();832833 // Verify no more unexpected callbacks from low priority work834 await waitForAll([]);835 expect(callback).toHaveBeenCalledTimes(0);836 });837838 it('should report the expected times when a high-priority update replaces a low-priority update', async () => {839 const callback = jest.fn();840841 const Yield = ({renderTime}) => {842 Scheduler.unstable_advanceTime(renderTime);843 Scheduler.log('Yield:' + renderTime);844 return null;845 };846847 Scheduler.unstable_advanceTime(5); // 0 -> 5848 const root = ReactNoop.createRoot();849 root.render(850 <React.Profiler id="test" onRender={callback}>851 <Yield renderTime={6} />852 <Yield renderTime={15} />853 </React.Profiler>,854 );855856 // Render everything initially.857 // This should take 21 seconds of actual and base time.858 await waitForAll(['Yield:6', 'Yield:15']);859 expect(callback).toHaveBeenCalledTimes(1);860 let call = callback.mock.calls[0];861 expect(call[2]).toBe(21); // actual time862 expect(call[3]).toBe(21); // base time863 expect(call[4]).toBe(5); // start time864 expect(call[5]).toBe(26); // commit time865866 callback.mockReset();867868 Scheduler.unstable_advanceTime(30); // 26 -> 56869870 // Render a partially update, but don't finish.871 // This partial render should take 3ms of simulated time.872 React.startTransition(() => {873 root.render(874 <React.Profiler id="test" onRender={callback}>875 <Yield renderTime={3} />876 <Yield renderTime={5} />877 <Yield renderTime={9} />878 </React.Profiler>,879 );880 });881882 await waitFor(['Yield:3']);883 expect(callback).toHaveBeenCalledTimes(0);884885 // Simulate time moving forward while frame is paused.886 Scheduler.unstable_advanceTime(100); // 59 -> 159887888 // Render another 5ms of simulated time.889 await waitFor(['Yield:5']);890 expect(callback).toHaveBeenCalledTimes(0);891892 // Simulate time moving forward while frame is paused.893 Scheduler.unstable_advanceTime(100); // 164 -> 264894895 // Interrupt with higher priority work.896 // The interrupted work simulates an additional 11ms of time.897 ReactNoop.flushSync(() => {898 root.render(899 <React.Profiler id="test" onRender={callback}>900 <Yield renderTime={11} />901 </React.Profiler>,902 );903 });904 assertLog(['Yield:11']);905906 // The actual time should include only the most recent render,907 // Because this lets us avoid a lot of commit phase reset complexity.908 // The base time includes only the final rendered tree times.909 expect(callback).toHaveBeenCalledTimes(1);910 call = callback.mock.calls[0];911 expect(call[2]).toBe(11); // actual time912 expect(call[3]).toBe(11); // base time913 expect(call[4]).toBe(264); // start time914 expect(call[5]).toBe(275); // commit time915916 // Verify no more unexpected callbacks from low priority work917 await waitForAll([]);918 expect(callback).toHaveBeenCalledTimes(1);919 });920921 it('should report the expected times when a high-priority update interrupts a low-priority update', async () => {922 const callback = jest.fn();923924 const Yield = ({renderTime}) => {925 Scheduler.unstable_advanceTime(renderTime);926 Scheduler.log('Yield:' + renderTime);927 return null;928 };929930 let first;931 class FirstComponent extends React.Component {932 state = {renderTime: 1};933 render() {934 first = this;935 Scheduler.unstable_advanceTime(this.state.renderTime);936 Scheduler.log('FirstComponent:' + this.state.renderTime);937 return <Yield renderTime={4} />;938 }939 }940 let second;941 class SecondComponent extends React.Component {942 state = {renderTime: 2};943 render() {944 second = this;945 Scheduler.unstable_advanceTime(this.state.renderTime);946 Scheduler.log('SecondComponent:' + this.state.renderTime);947 return <Yield renderTime={7} />;948 }949 }950951 Scheduler.unstable_advanceTime(5); // 0 -> 5952953 const root = ReactNoop.createRoot();954 root.render(955 <React.Profiler id="test" onRender={callback}>956 <FirstComponent />957 <SecondComponent />958 </React.Profiler>,959 );960961 // Render everything initially.962 // This simulates a total of 14ms of actual render time.963 // The base render time is also 14ms for the initial render.964 await waitForAll([965 'FirstComponent:1',966 'Yield:4',967 'SecondComponent:2',968 'Yield:7',969 ]);970 expect(callback).toHaveBeenCalledTimes(1);971 let call = callback.mock.calls[0];972 expect(call[2]).toBe(14); // actual time973 expect(call[3]).toBe(14); // base time974 expect(call[4]).toBe(5); // start time975 expect(call[5]).toBe(19); // commit time976977 callback.mockClear();978979 Scheduler.unstable_advanceTime(100); // 19 -> 119980981 // Render a partially update, but don't finish.982 // This partial render will take 10ms of actual render time.983 React.startTransition(() => {984 first.setState({renderTime: 10});985 });986987 await waitFor(['FirstComponent:10']);988 expect(callback).toHaveBeenCalledTimes(0);989990 // Simulate time moving forward while frame is paused.991 Scheduler.unstable_advanceTime(100); // 129 -> 229992993 // Interrupt with higher priority work.994 // This simulates a total of 37ms of actual render time.995 ReactNoop.flushSync(() => second.setState({renderTime: 30}));996 assertLog(['SecondComponent:30', 'Yield:7']);997998 // The actual time should include only the most recent render (37ms),999 // Because this greatly simplifies the commit phase logic.1000 // The base time should include the more recent times for the SecondComponent subtree,1001 // As well as the original times for the FirstComponent subtree.1002 expect(callback).toHaveBeenCalledTimes(1);1003 call = callback.mock.calls[0];1004 expect(call[2]).toBe(37); // actual time1005 expect(call[3]).toBe(42); // base time1006 expect(call[4]).toBe(229); // start time1007 expect(call[5]).toBe(266); // commit time10081009 callback.mockClear();10101011 // Simulate time moving forward while frame is paused.1012 Scheduler.unstable_advanceTime(100); // 266 -> 36610131014 // Resume the original low priority update, with rebased state.1015 // This simulates a total of 14ms of actual render time,1016 // And does not include the original (interrupted) 10ms.1017 // The tree contains 42ms of base render time at this point,1018 // Reflecting the most recent (longer) render durations.1019 // TODO: This actual time should decrease by 10ms once the scheduler supports resuming.1020 await waitForAll(['FirstComponent:10', 'Yield:4']);1021 expect(callback).toHaveBeenCalledTimes(1);1022 call = callback.mock.calls[0];1023 expect(call[2]).toBe(14); // actual time1024 expect(call[3]).toBe(51); // base time1025 expect(call[4]).toBe(366); // start time1026 expect(call[5]).toBe(380); // commit time1027 });10281029 it('should accumulate actual time after an error handled by componentDidCatch()', async () => {1030 const callback = jest.fn();10311032 const ThrowsError = ({unused}) => {1033 Scheduler.unstable_advanceTime(3);1034 throw Error('expected error');1035 };10361037 class ErrorBoundary extends React.Component {1038 state = {error: null};1039 componentDidCatch(error) {1040 this.setState({error});1041 }1042 render() {1043 Scheduler.unstable_advanceTime(2);1044 return this.state.error === null ? (1045 this.props.children1046 ) : (1047 <AdvanceTime byAmount={20} />1048 );1049 }1050 }10511052 Scheduler.unstable_advanceTime(5); // 0 -> 510531054 const root = ReactNoop.createRoot();1055 await act(() => {1056 root.render(1057 <React.Profiler id="test" onRender={callback}>1058 <ErrorBoundary>1059 <AdvanceTime byAmount={9} />1060 <ThrowsError />1061 </ErrorBoundary>1062 </React.Profiler>,1063 );1064 });10651066 expect(callback).toHaveBeenCalledTimes(2);10671068 // Callbacks bubble (reverse order).1069 const [mountCall, updateCall] = callback.mock.calls;10701071 // The initial mount only includes the ErrorBoundary (which takes 2)1072 // But it spends time rendering all of the failed subtree also.1073 expect(mountCall[1]).toBe('mount');1074 // actual time includes: 2 (ErrorBoundary) + 9 (AdvanceTime) + 3 (ThrowsError)1075 // We don't count the time spent in replaying the failed unit of work (ThrowsError)1076 expect(mountCall[2]).toBe(14);1077 // base time includes: 2 (ErrorBoundary)1078 // Since the tree is empty for the initial commit1079 expect(mountCall[3]).toBe(2);10801081 // start time: 5 initially + 14 of work1082 // Add an additional 3 (ThrowsError) if we replayed the failed work1083 expect(mountCall[4]).toBe(19);1084 // commit time: 19 initially + 14 of work1085 // Add an additional 6 (ThrowsError *2) if we replayed the failed work1086 expect(mountCall[5]).toBe(33);10871088 // The update includes the ErrorBoundary and its fallback child1089 expect(updateCall[1]).toBe('nested-update');1090 // actual time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)1091 expect(updateCall[2]).toBe(22);1092 // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)1093 expect(updateCall[3]).toBe(22);1094 // start time1095 expect(updateCall[4]).toBe(33);1096 // commit time: 19 (startTime) + 2 (ErrorBoundary) + 20 (AdvanceTime)1097 // Add an additional 3 (ThrowsError) if we replayed the failed work1098 expect(updateCall[5]).toBe(55);1099 });11001101 it('should accumulate actual time after an error handled by getDerivedStateFromError()', async () => {1102 const callback = jest.fn();11031104 const ThrowsError = ({unused}) => {1105 Scheduler.unstable_advanceTime(10);1106 throw Error('expected error');1107 };11081109 class ErrorBoundary extends React.Component {1110 state = {error: null};1111 static getDerivedStateFromError(error) {1112 return {error};1113 }1114 render() {1115 Scheduler.unstable_advanceTime(2);1116 return this.state.error === null ? (1117 this.props.children1118 ) : (1119 <AdvanceTime byAmount={20} />1120 );1121 }1122 }11231124 Scheduler.unstable_advanceTime(5); // 0 -> 511251126 await act(() => {1127 const root = ReactNoop.createRoot();1128 root.render(1129 <React.Profiler id="test" onRender={callback}>1130 <ErrorBoundary>1131 <AdvanceTime byAmount={5} />1132 <ThrowsError />1133 </ErrorBoundary>1134 </React.Profiler>,1135 );1136 });11371138 expect(callback).toHaveBeenCalledTimes(1);11391140 // Callbacks bubble (reverse order).1141 const [mountCall] = callback.mock.calls;11421143 // The initial mount includes the ErrorBoundary's error state,1144 // But it also spends actual time rendering UI that fails and isn't included.1145 expect(mountCall[1]).toBe('mount');1146 // actual time includes: 2 (ErrorBoundary) + 5 (AdvanceTime) + 10 (ThrowsError)1147 // Then the re-render: 2 (ErrorBoundary) + 20 (AdvanceTime)1148 // We don't count the time spent in replaying the failed unit of work (ThrowsError)1149 expect(mountCall[2]).toBe(39);1150 // base time includes: 2 (ErrorBoundary) + 20 (AdvanceTime)1151 expect(mountCall[3]).toBe(22);1152 // start time1153 expect(mountCall[4]).toBe(44);1154 // commit time1155 expect(mountCall[5]).toBe(83);1156 });11571158 it('should reset the fiber stack correct after a "complete" phase error', async () => {1159 jest.resetModules();11601161 loadModules({1162 useNoopRenderer: true,1163 });11641165 // Simulate a renderer error during the "complete" phase.1166 // This mimics behavior like React Native's View/Text nesting validation.1167 ReactNoop.render(1168 <React.Profiler id="profiler" onRender={jest.fn()}>1169 <errorInCompletePhase>hi</errorInCompletePhase>1170 </React.Profiler>,1171 );1172 await waitForThrow('Error in host config.');11731174 // A similar case we've seen caused by an invariant in ReactDOM.1175 // It didn't reproduce without a host component inside.1176 ReactNoop.render(1177 <React.Profiler id="profiler" onRender={jest.fn()}>1178 <errorInCompletePhase>1179 <span>hi</span>1180 </errorInCompletePhase>1181 </React.Profiler>,1182 );1183 await waitForThrow('Error in host config.');11841185 // So long as the profiler timer's fiber stack is reset correctly,1186 // Subsequent renders should not error.1187 ReactNoop.render(1188 <React.Profiler id="profiler" onRender={jest.fn()}>1189 <span>hi</span>1190 </React.Profiler>,1191 );1192 await waitForAll([]);1193 });1194 });11951196 it('reflects the most recently rendered id value', async () => {1197 const callback = jest.fn();11981199 Scheduler.unstable_advanceTime(5); // 0 -> 512001201 const root = ReactNoop.createRoot();1202 await act(() => {1203 root.render(1204 <React.Profiler id="one" onRender={callback}>1205 <AdvanceTime byAmount={2} />1206 </React.Profiler>,1207 );1208 });12091210 expect(callback).toHaveBeenCalledTimes(1);12111212 Scheduler.unstable_advanceTime(20); // 7 -> 2712131214 await act(() => {1215 root.render(1216 <React.Profiler id="two" onRender={callback}>1217 <AdvanceTime byAmount={1} />1218 </React.Profiler>,1219 );1220 });12211222 expect(callback).toHaveBeenCalledTimes(2);12231224 const [mountCall, updateCall] = callback.mock.calls;12251226 expect(mountCall[0]).toBe('one');1227 expect(mountCall[1]).toBe('mount');1228 expect(mountCall[2]).toBe(2); // actual time1229 expect(mountCall[3]).toBe(2); // base time1230 expect(mountCall[4]).toBe(5); // start time12311232 expect(updateCall[0]).toBe('two');1233 expect(updateCall[1]).toBe('update');1234 expect(updateCall[2]).toBe(1); // actual time1235 expect(updateCall[3]).toBe(1); // base time1236 expect(updateCall[4]).toBe(27); // start time1237 });12381239 it('should not be called until after mutations', async () => {1240 let classComponentMounted = false;1241 const callback = jest.fn(1242 (id, phase, actualDuration, baseDuration, startTime, commitTime) => {1243 // Don't call this hook until after mutations1244 expect(classComponentMounted).toBe(true);1245 // But the commit time should reflect pre-mutation1246 expect(commitTime).toBe(2);1247 },1248 );12491250 class ClassComponent extends React.Component {1251 componentDidMount() {1252 Scheduler.unstable_advanceTime(5);1253 classComponentMounted = true;1254 }1255 render() {1256 Scheduler.unstable_advanceTime(2);1257 return null;1258 }1259 }1260 const root = ReactNoop.createRoot();1261 await act(() => {1262 root.render(1263 <React.Profiler id="test" onRender={callback}>1264 <ClassComponent />1265 </React.Profiler>,1266 );1267 });12681269 expect(callback).toHaveBeenCalledTimes(1);1270 });1271});12721273describe(`onCommit`, () => {1274 beforeEach(() => {1275 jest.resetModules();12761277 loadModules();1278 });12791280 it('should report time spent in layout effects and commit lifecycles', async () => {1281 const callback = jest.fn();12821283 const ComponentWithEffects = () => {1284 React.useLayoutEffect(() => {1285 Scheduler.unstable_advanceTime(10);1286 return () => {1287 Scheduler.unstable_advanceTime(100);1288 };1289 }, []);1290 React.useLayoutEffect(() => {1291 Scheduler.unstable_advanceTime(1000);1292 return () => {1293 Scheduler.unstable_advanceTime(10000);1294 };1295 });1296 React.useEffect(() => {1297 // This passive effect is here to verify that its time isn't reported.1298 Scheduler.unstable_advanceTime(5);1299 return () => {1300 Scheduler.unstable_advanceTime(7);1301 };1302 });1303 return null;1304 };13051306 class ComponentWithCommitHooks extends React.Component {1307 componentDidMount() {1308 Scheduler.unstable_advanceTime(100000);1309 }1310 componentDidUpdate() {1311 Scheduler.unstable_advanceTime(1000000);1312 }1313 render() {1314 return null;1315 }1316 }13171318 Scheduler.unstable_advanceTime(1);1319 const root = ReactNoop.createRoot();1320 await act(() => {1321 root.render(1322 <React.Profiler id="mount-test" onCommit={callback}>1323 <ComponentWithEffects />1324 <ComponentWithCommitHooks />1325 </React.Profiler>,1326 );1327 });13281329 expect(callback).toHaveBeenCalledTimes(1);13301331 let call = callback.mock.calls[0];13321333 expect(call).toHaveLength(4);1334 expect(call[0]).toBe('mount-test');1335 expect(call[1]).toBe('mount');1336 expect(call[2]).toBe(101010); // durations1337 expect(call[3]).toBe(1); // commit start time (before mutations or effects)13381339 Scheduler.unstable_advanceTime(1);13401341 await act(() => {1342 root.render(1343 <React.Profiler id="update-test" onCommit={callback}>1344 <ComponentWithEffects />1345 <ComponentWithCommitHooks />1346 </React.Profiler>,1347 );1348 });13491350 expect(callback).toHaveBeenCalledTimes(2);13511352 call = callback.mock.calls[1];13531354 expect(call).toHaveLength(4);1355 expect(call[0]).toBe('update-test');1356 expect(call[1]).toBe('update');1357 expect(call[2]).toBe(1011000); // durations1358 expect(call[3]).toBe(101017); // commit start time (before mutations or effects)13591360 Scheduler.unstable_advanceTime(1);13611362 await act(() => {1363 root.render(<React.Profiler id="unmount-test" onCommit={callback} />);1364 });13651366 expect(callback).toHaveBeenCalledTimes(3);13671368 call = callback.mock.calls[2];13691370 expect(call).toHaveLength(4);1371 expect(call[0]).toBe('unmount-test');1372 expect(call[1]).toBe('update');1373 expect(call[2]).toBe(10100); // durations1374 expect(call[3]).toBe(1112030); // commit start time (before mutations or effects)1375 });13761377 it('should report time spent in layout effects and commit lifecycles with cascading renders', async () => {1378 const callback = jest.fn();13791380 const ComponentWithEffects = ({shouldCascade}) => {1381 const [didCascade, setDidCascade] = React.useState(false);1382 Scheduler.unstable_advanceTime(100000000);1383 React.useLayoutEffect(() => {1384 if (shouldCascade && !didCascade) {1385 setDidCascade(true);1386 }1387 Scheduler.unstable_advanceTime(didCascade ? 30 : 10);1388 return () => {1389 Scheduler.unstable_advanceTime(100);1390 };1391 }, [didCascade, shouldCascade]);1392 return null;1393 };13941395 class ComponentWithCommitHooks extends React.Component {1396 state = {1397 didCascade: false,1398 };1399 componentDidMount() {1400 Scheduler.unstable_advanceTime(1000);1401 }1402 componentDidUpdate() {1403 Scheduler.unstable_advanceTime(10000);1404 if (this.props.shouldCascade && !this.state.didCascade) {1405 this.setState({didCascade: true});1406 }1407 }1408 render() {1409 Scheduler.unstable_advanceTime(1000000000);1410 return null;1411 }1412 }14131414 Scheduler.unstable_advanceTime(1);1415 const root = ReactNoop.createRoot();1416 await act(() => {1417 root.render(1418 <React.Profiler id="mount-test" onCommit={callback}>1419 <ComponentWithEffects shouldCascade={true} />1420 <ComponentWithCommitHooks />1421 </React.Profiler>,1422 );1423 });14241425 expect(callback).toHaveBeenCalledTimes(2);14261427 let call = callback.mock.calls[0];14281429 expect(call).toHaveLength(4);1430 expect(call[0]).toBe('mount-test');1431 expect(call[1]).toBe('mount');1432 expect(call[2]).toBe(1010); // durations1433 expect(call[3]).toBe(1100000001); // commit start time (before mutations or effects)14341435 call = callback.mock.calls[1];14361437 expect(call).toHaveLength(4);1438 expect(call[0]).toBe('mount-test');1439 expect(call[1]).toBe('nested-update');1440 expect(call[2]).toBe(130); // durations1441 expect(call[3]).toBe(1200001011); // commit start time (before mutations or effects)14421443 Scheduler.unstable_advanceTime(1);14441445 await act(() => {1446 root.render(1447 <React.Profiler id="update-test" onCommit={callback}>1448 <ComponentWithEffects />1449 <ComponentWithCommitHooks shouldCascade={true} />1450 </React.Profiler>,1451 );1452 });14531454 expect(callback).toHaveBeenCalledTimes(4);14551456 call = callback.mock.calls[2];14571458 expect(call).toHaveLength(4);1459 expect(call[0]).toBe('update-test');1460 expect(call[1]).toBe('update');1461 expect(call[2]).toBe(10130); // durations1462 expect(call[3]).toBe(2300001142); // commit start time (before mutations or effects)14631464 call = callback.mock.calls[3];14651466 expect(call).toHaveLength(4);1467 expect(call[0]).toBe('update-test');1468 expect(call[1]).toBe('nested-update');1469 expect(call[2]).toBe(10000); // durations1470 expect(call[3]).toBe(3300011272); // commit start time (before mutations or effects)1471 });14721473 it('should include time spent in ref callbacks', async () => {1474 const callback = jest.fn();14751476 const refSetter = ref => {1477 if (ref !== null) {1478 Scheduler.unstable_advanceTime(10);1479 } else {1480 Scheduler.unstable_advanceTime(100);1481 }1482 };14831484 class ClassComponent extends React.Component {1485 render() {1486 return null;1487 }1488 }14891490 const Component = () => {1491 Scheduler.unstable_advanceTime(1000);1492 return <ClassComponent ref={refSetter} />;1493 };14941495 Scheduler.unstable_advanceTime(1);14961497 const root = ReactNoop.createRoot();1498 await act(() => {1499 root.render(1500 <React.Profiler id="root" onCommit={callback}>1501 <Component />1502 </React.Profiler>,1503 );1504 });15051506 expect(callback).toHaveBeenCalledTimes(1);15071508 let call = callback.mock.calls[0];15091510 expect(call).toHaveLength(4);1511 expect(call[0]).toBe('root');1512 expect(call[1]).toBe('mount');1513 expect(call[2]).toBe(10); // durations1514 expect(call[3]).toBe(1001); // commit start time (before mutations or effects)15151516 callback.mockClear();15171518 await act(() => {1519 root.render(<React.Profiler id="root" onCommit={callback} />);1520 });15211522 expect(callback).toHaveBeenCalledTimes(1);15231524 call = callback.mock.calls[0];15251526 expect(call).toHaveLength(4);1527 expect(call[0]).toBe('root');1528 expect(call[1]).toBe('update');1529 expect(call[2]).toBe(100); // durations1530 expect(call[3]).toBe(1011); // commit start time (before mutations or effects)1531 });15321533 it('should bubble time spent in layout effects to higher profilers', async () => {1534 const callback = jest.fn();15351536 const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => {1537 const setCount = React.useState(0)[1];1538 if (setCountRef != null) {1539 setCountRef.current = setCount;1540 }1541 React.useLayoutEffect(() => {1542 Scheduler.unstable_advanceTime(duration);1543 return () => {1544 Scheduler.unstable_advanceTime(cleanupDuration);1545 };1546 });1547 Scheduler.unstable_advanceTime(1);1548 return null;1549 };15501551 const setCountRef = React.createRef(null);15521553 const root = ReactNoop.createRoot();1554 await act(() => {1555 root.render(1556 <React.Profiler id="root-mount" onCommit={callback}>1557 <React.Profiler id="a">1558 <ComponentWithEffects1559 duration={10}1560 cleanupDuration={100}1561 setCountRef={setCountRef}1562 />1563 </React.Profiler>1564 <React.Profiler id="b">1565 <ComponentWithEffects duration={1000} cleanupDuration={10000} />1566 </React.Profiler>1567 </React.Profiler>,1568 );1569 });15701571 expect(callback).toHaveBeenCalledTimes(1);15721573 let call = callback.mock.calls[0];15741575 expect(call).toHaveLength(4);1576 expect(call[0]).toBe('root-mount');1577 expect(call[1]).toBe('mount');1578 expect(call[2]).toBe(1010); // durations1579 expect(call[3]).toBe(2); // commit start time (before mutations or effects)15801581 await act(() => setCountRef.current(count => count + 1));15821583 expect(callback).toHaveBeenCalledTimes(2);15841585 call = callback.mock.calls[1];15861587 expect(call).toHaveLength(4);1588 expect(call[0]).toBe('root-mount');1589 expect(call[1]).toBe('update');1590 expect(call[2]).toBe(110); // durations1591 expect(call[3]).toBe(1013); // commit start time (before mutations or effects)15921593 await act(() => {1594 root.render(1595 <React.Profiler id="root-update" onCommit={callback}>1596 <React.Profiler id="b">1597 <ComponentWithEffects duration={1000} cleanupDuration={10000} />1598 </React.Profiler>1599 </React.Profiler>,1600 );1601 });16021603 expect(callback).toHaveBeenCalledTimes(3);16041605 call = callback.mock.calls[2];16061607 expect(call).toHaveLength(4);1608 expect(call[0]).toBe('root-update');1609 expect(call[1]).toBe('update');1610 expect(call[2]).toBe(11100); // durations1611 expect(call[3]).toBe(1124); // commit start time (before mutations or effects)1612 });16131614 it('should properly report time in layout effects even when there are errors', async () => {1615 const callback = jest.fn();16161617 class ErrorBoundary extends React.Component {1618 state = {error: null};1619 static getDerivedStateFromError(error) {1620 return {error};1621 }1622 render() {1623 return this.state.error === null1624 ? this.props.children1625 : this.props.fallback;1626 }1627 }16281629 const ComponentWithEffects = ({1630 cleanupDuration,1631 duration,1632 effectDuration,1633 shouldThrow,1634 }) => {1635 React.useLayoutEffect(() => {1636 Scheduler.unstable_advanceTime(effectDuration);1637 if (shouldThrow) {1638 throw Error('expected');1639 }1640 return () => {1641 Scheduler.unstable_advanceTime(cleanupDuration);1642 };1643 });1644 Scheduler.unstable_advanceTime(duration);1645 return null;1646 };16471648 Scheduler.unstable_advanceTime(1);16491650 // Test an error that happens during an effect16511652 const root = ReactNoop.createRoot();1653 await act(() => {1654 root.render(1655 <React.Profiler id="root" onCommit={callback}>1656 <ErrorBoundary1657 fallback={1658 <ComponentWithEffects1659 duration={10000000}1660 effectDuration={100000000}1661 cleanupDuration={1000000000}1662 />1663 }>1664 <ComponentWithEffects1665 duration={10}1666 effectDuration={100}1667 cleanupDuration={1000}1668 shouldThrow={true}1669 />1670 </ErrorBoundary>1671 <ComponentWithEffects1672 duration={10000}1673 effectDuration={100000}1674 cleanupDuration={1000000}1675 />1676 </React.Profiler>,1677 );1678 });16791680 expect(callback).toHaveBeenCalledTimes(2);16811682 let call = callback.mock.calls[0];16831684 // Initial render (with error)1685 expect(call).toHaveLength(4);1686 expect(call[0]).toBe('root');1687 expect(call[1]).toBe('mount');1688 expect(call[2]).toBe(100100); // durations1689 expect(call[3]).toBe(10011); // commit start time (before mutations or effects)16901691 call = callback.mock.calls[1];16921693 // Cleanup render from error boundary1694 expect(call).toHaveLength(4);1695 expect(call[0]).toBe('root');1696 expect(call[1]).toBe('nested-update');1697 expect(call[2]).toBe(100000000); // durations1698 expect(call[3]).toBe(10110111); // commit start time (before mutations or effects)1699 });17001701 it('should properly report time in layout effect cleanup functions even when there are errors', async () => {1702 const callback = jest.fn();17031704 class ErrorBoundary extends React.Component {1705 state = {error: null};1706 static getDerivedStateFromError(error) {1707 return {error};1708 }1709 render() {1710 return this.state.error === null1711 ? this.props.children1712 : this.props.fallback;1713 }1714 }17151716 const ComponentWithEffects = ({1717 cleanupDuration,1718 duration,1719 effectDuration,1720 shouldThrow = false,1721 }) => {1722 React.useLayoutEffect(() => {1723 Scheduler.unstable_advanceTime(effectDuration);1724 return () => {1725 Scheduler.unstable_advanceTime(cleanupDuration);1726 if (shouldThrow) {1727 throw Error('expected');1728 }1729 };1730 });1731 Scheduler.unstable_advanceTime(duration);1732 return null;1733 };17341735 Scheduler.unstable_advanceTime(1);17361737 const root = ReactNoop.createRoot();1738 await act(() => {1739 root.render(1740 <React.Profiler id="root" onCommit={callback}>1741 <ErrorBoundary1742 fallback={1743 <ComponentWithEffects1744 duration={10000000}1745 effectDuration={100000000}1746 cleanupDuration={1000000000}1747 />1748 }>1749 <ComponentWithEffects1750 duration={10}1751 effectDuration={100}1752 cleanupDuration={1000}1753 shouldThrow={true}1754 />1755 </ErrorBoundary>1756 <ComponentWithEffects1757 duration={10000}1758 effectDuration={100000}1759 cleanupDuration={1000000}1760 />1761 </React.Profiler>,1762 );1763 });17641765 expect(callback).toHaveBeenCalledTimes(1);17661767 let call = callback.mock.calls[0];17681769 // Initial render1770 expect(call).toHaveLength(4);1771 expect(call[0]).toBe('root');1772 expect(call[1]).toBe('mount');1773 expect(call[2]).toBe(100100); // durations1774 expect(call[3]).toBe(10011); // commit start time (before mutations or effects)17751776 callback.mockClear();17771778 // Test an error that happens during an cleanup function17791780 await act(() => {1781 root.render(1782 <React.Profiler id="root" onCommit={callback}>1783 <ErrorBoundary1784 fallback={1785 <ComponentWithEffects1786 duration={10000000}1787 effectDuration={100000000}1788 cleanupDuration={1000000000}1789 />1790 }>1791 <ComponentWithEffects1792 duration={10}1793 effectDuration={100}1794 cleanupDuration={1000}1795 shouldThrow={false}1796 />1797 </ErrorBoundary>1798 <ComponentWithEffects1799 duration={10000}1800 effectDuration={100000}1801 cleanupDuration={1000000}1802 />1803 </React.Profiler>,1804 );1805 });18061807 expect(callback).toHaveBeenCalledTimes(2);18081809 call = callback.mock.calls[0];18101811 // Update (that throws)1812 expect(call).toHaveLength(4);1813 expect(call[0]).toBe('root');1814 expect(call[1]).toBe('update');1815 expect(call[2]).toBe(1101100); // durations1816 expect(call[3]).toBe(120121); // commit start time (before mutations or effects)18171818 call = callback.mock.calls[1];18191820 // Cleanup render from error boundary1821 expect(call).toHaveLength(4);1822 expect(call[0]).toBe('root');1823 expect(call[1]).toBe('nested-update');1824 expect(call[2]).toBe(100001000); // durations1825 expect(call[3]).toBe(11221221); // commit start time (before mutations or effects)1826 });1827});18281829describe(`onPostCommit`, () => {1830 beforeEach(() => {1831 jest.resetModules();18321833 loadModules();1834 });18351836 it('should report time spent in passive effects', async () => {1837 const callback = jest.fn();18381839 const ComponentWithEffects = () => {1840 React.useLayoutEffect(() => {1841 // This layout effect is here to verify that its time isn't reported.1842 Scheduler.unstable_advanceTime(5);1843 return () => {1844 Scheduler.unstable_advanceTime(7);1845 };1846 });1847 React.useEffect(() => {1848 Scheduler.unstable_advanceTime(10);1849 return () => {1850 Scheduler.unstable_advanceTime(100);1851 };1852 }, []);1853 React.useEffect(() => {1854 Scheduler.unstable_advanceTime(1000);1855 return () => {1856 Scheduler.unstable_advanceTime(10000);1857 };1858 });1859 return null;1860 };18611862 Scheduler.unstable_advanceTime(1);18631864 const root = ReactNoop.createRoot();1865 await act(() => {1866 root.render(1867 <React.Profiler id="mount-test" onPostCommit={callback}>1868 <ComponentWithEffects />1869 </React.Profiler>,1870 );1871 });1872 await waitForAll([]);18731874 expect(callback).toHaveBeenCalledTimes(1);18751876 let call = callback.mock.calls[0];18771878 expect(call).toHaveLength(4);1879 expect(call[0]).toBe('mount-test');1880 expect(call[1]).toBe('mount');1881 expect(call[2]).toBe(1010); // durations1882 expect(call[3]).toBe(1); // commit start time (before mutations or effects)18831884 Scheduler.unstable_advanceTime(1);18851886 await act(() => {1887 root.render(1888 <React.Profiler id="update-test" onPostCommit={callback}>1889 <ComponentWithEffects />1890 </React.Profiler>,1891 );1892 });1893 await waitForAll([]);18941895 expect(callback).toHaveBeenCalledTimes(2);18961897 call = callback.mock.calls[1];18981899 expect(call).toHaveLength(4);1900 expect(call[0]).toBe('update-test');1901 expect(call[1]).toBe('update');1902 expect(call[2]).toBe(11000); // durations1903 expect(call[3]).toBe(1017); // commit start time (before mutations or effects)19041905 Scheduler.unstable_advanceTime(1);19061907 await act(() => {1908 root.render(<React.Profiler id="unmount-test" onPostCommit={callback} />);1909 });1910 await waitForAll([]);19111912 expect(callback).toHaveBeenCalledTimes(3);19131914 call = callback.mock.calls[2];19151916 expect(call).toHaveLength(4);1917 expect(call[0]).toBe('unmount-test');1918 expect(call[1]).toBe('update');1919 expect(call[2]).toBe(10100); // durations1920 expect(call[3]).toBe(12030); // commit start time (before mutations or effects)1921 });19221923 it('should report time spent in passive effects with cascading renders', async () => {1924 const callback = jest.fn();19251926 const ComponentWithEffects = () => {1927 const [didMount, setDidMount] = React.useState(false);1928 Scheduler.unstable_advanceTime(1000);1929 React.useEffect(() => {1930 if (!didMount) {1931 setDidMount(true);1932 }1933 Scheduler.unstable_advanceTime(didMount ? 30 : 10);1934 return () => {1935 Scheduler.unstable_advanceTime(100);1936 };1937 }, [didMount]);1938 return null;1939 };19401941 Scheduler.unstable_advanceTime(1);19421943 const root = ReactNoop.createRoot();1944 await act(() => {1945 root.render(1946 <React.Profiler id="mount-test" onPostCommit={callback}>1947 <ComponentWithEffects />1948 </React.Profiler>,1949 );1950 });19511952 expect(callback).toHaveBeenCalledTimes(2);19531954 let call = callback.mock.calls[0];19551956 expect(call).toHaveLength(4);1957 expect(call[0]).toBe('mount-test');1958 expect(call[1]).toBe('mount');1959 expect(call[2]).toBe(10); // durations1960 expect(call[3]).toBe(1001); // commit start time (before mutations or effects)19611962 call = callback.mock.calls[1];19631964 expect(call).toHaveLength(4);1965 expect(call[0]).toBe('mount-test');1966 expect(call[1]).toBe('update');1967 expect(call[2]).toBe(130); // durations1968 expect(call[3]).toBe(2011); // commit start time (before mutations or effects)1969 });19701971 it('should bubble time spent in effects to higher profilers', async () => {1972 const callback = jest.fn();19731974 const ComponentWithEffects = ({cleanupDuration, duration, setCountRef}) => {1975 const setCount = React.useState(0)[1];1976 if (setCountRef != null) {1977 setCountRef.current = setCount;1978 }1979 React.useEffect(() => {1980 Scheduler.unstable_advanceTime(duration);1981 return () => {1982 Scheduler.unstable_advanceTime(cleanupDuration);1983 };1984 });1985 Scheduler.unstable_advanceTime(1);1986 return null;1987 };19881989 const setCountRef = React.createRef(null);19901991 const root = ReactNoop.createRoot();1992 await act(() => {1993 root.render(1994 <React.Profiler id="root-mount" onPostCommit={callback}>1995 <React.Profiler id="a">1996 <ComponentWithEffects1997 duration={10}1998 cleanupDuration={100}1999 setCountRef={setCountRef}2000 />
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.