packages/react/src/__tests__/ReactProfiler-test.internal.js JAVASCRIPT 2,268 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,268.
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            />

Code quality findings 10

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (id === 'throw') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null ? (
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null ? (
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (ref !== null) {
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (setCountRef != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null
Use strict inequality (!==) to prevent type coercion bugs
info correctness loose-inequality
if (setCountRef != null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
return this.state.error === null

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.