/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ 'use strict'; import { getLegacyRenderImplementation, getModernRenderImplementation, normalizeCodeLocInfo, } from './utils'; let React = require('react'); let Scheduler; let store; let utils; // This flag is on experimental which disables timeline profiler. const enableComponentPerformanceTrack = React.version.startsWith('19') && React.version.includes('experimental'); describe('Timeline profiler', () => { if (enableComponentPerformanceTrack) { test('no tests', () => {}); // Ignore all tests. return; } beforeEach(() => { utils = require('./utils'); utils.beforeEachProfiling(); React = require('react'); Scheduler = require('scheduler'); store = global.store; }); afterEach(() => { jest.restoreAllMocks(); }); describe('User Timing API', () => { let currentlyNotClearedMarks; let registeredMarks; let featureDetectionMarkName = null; let setPerformanceMock; function createUserTimingPolyfill() { featureDetectionMarkName = null; currentlyNotClearedMarks = []; registeredMarks = []; // Remove file-system specific bits or version-specific bits of information from the module range marks. function filterMarkData(markName) { if (markName.startsWith('--react-internal-module-start')) { return '--react-internal-module-start- at filtered (:0:0)'; } else if (markName.startsWith('--react-internal-module-stop')) { return '--react-internal-module-stop- at filtered (:1:1)'; } else if (markName.startsWith('--react-version')) { return '--react-version-'; } else { return markName; } } // This is not a true polyfill, but it gives us enough to capture marks. // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API return { clearMarks(markName) { markName = filterMarkData(markName); currentlyNotClearedMarks = currentlyNotClearedMarks.filter( mark => mark !== markName, ); }, mark(markName, markOptions) { markName = filterMarkData(markName); if (featureDetectionMarkName === null) { featureDetectionMarkName = markName; } registeredMarks.push(markName); currentlyNotClearedMarks.push(markName); if (markOptions != null) { // This is triggers the feature detection. markOptions.startTime++; } }, }; } function eraseRegisteredMarks() { registeredMarks.splice(0); } function dispatchAndSetCurrentEvent(element, event) { try { window.event = event; element.dispatchEvent(event); } finally { window.event = undefined; } } beforeEach(() => { setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING; setPerformanceMock(createUserTimingPolyfill()); }); afterEach(() => { // Verify all logged marks also get cleared. expect(currentlyNotClearedMarks).toHaveLength(0); setPerformanceMock(null); }); describe('with legacy render', () => { const {render: legacyRender} = getLegacyRenderImplementation(); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('should mark sync render without suspends or state updates', () => { utils.act(() => store.profilerStore.startProfiling()); legacyRender(
); utils.act(() => store.profilerStore.stopProfiling()); expect(registeredMarks).toMatchInlineSnapshot(` [ "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--schedule-render-1", "--render-start-1", "--render-stop", "--commit-start-1", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--layout-effects-start-1", "--layout-effects-stop", "--commit-stop", ] `); }); // TODO(hoxyq): investigate why running this test with React 18 fails // @reactVersion <= 18.2 // @reactVersion >= 18.0 // eslint-disable-next-line jest/no-disabled-tests it.skip('should mark sync render with suspense that resolves', async () => { const fakeSuspensePromise = Promise.resolve(true); function Example() { throw fakeSuspensePromise; } legacyRender( , ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-2", "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--suspense-suspend-0-Example-mount-2-", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] `); eraseRegisteredMarks(); await fakeSuspensePromise; expect(registeredMarks).toMatchInlineSnapshot(` [ "--suspense-resolved-0-Example", ] `); }); // TODO(hoxyq): investigate why running this test with React 18 fails // @reactVersion <= 18.2 // @reactVersion >= 18.0 // eslint-disable-next-line jest/no-disabled-tests it.skip('should mark sync render with suspense that rejects', async () => { const fakeSuspensePromise = Promise.reject(new Error('error')); function Example() { throw fakeSuspensePromise; } legacyRender( , ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-2", "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--suspense-suspend-0-Example-mount-2-", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", ] `); eraseRegisteredMarks(); await expect(fakeSuspensePromise).rejects.toThrow(); expect(registeredMarks).toContain(`--suspense-rejected-0-Example`); }); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('should mark sync render that throws', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); class ErrorBoundary extends React.Component { state = {error: null}; componentDidCatch(error) { this.setState({error}); } render() { if (this.state.error) { return null; } return this.props.children; } } function ExampleThatThrows() { throw Error('Expected error'); } utils.act(() => store.profilerStore.startProfiling()); legacyRender( , ); utils.act(() => store.profilerStore.stopProfiling()); expect(registeredMarks).toMatchInlineSnapshot(` [ "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--schedule-render-1", "--render-start-1", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", "--component-render-start-ExampleThatThrows", "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", "--commit-start-1", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--layout-effects-start-1", "--schedule-state-update-1-ErrorBoundary", "--layout-effects-stop", "--commit-stop", "--render-start-1", "--component-render-start-ErrorBoundary", "--component-render-stop", "--render-stop", "--commit-start-1", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--commit-stop", ] `); }); }); describe('with createRoot', () => { let waitFor; let waitForAll; let waitForPaint; let assertLog; beforeEach(() => { const InternalTestUtils = require('internal-test-utils'); waitFor = InternalTestUtils.waitFor; waitForAll = InternalTestUtils.waitForAll; waitForPaint = InternalTestUtils.waitForPaint; assertLog = InternalTestUtils.assertLog; }); const {render: modernRender} = getModernRenderImplementation(); it('should mark concurrent render without suspends or state updates', async () => { modernRender(
); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); it('should mark render yields', async () => { function Bar() { Scheduler.log('Bar'); return null; } function Foo() { Scheduler.log('Foo'); return ; } React.startTransition(() => { modernRender(); }); await waitFor(['Foo']); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-128", "--render-start-128", "--component-render-start-Foo", "--component-render-stop", "--render-yield", ] `); }); it('should mark concurrent render with suspense that resolves', async () => { let resolveFakePromise; const fakeSuspensePromise = new Promise( resolve => (resolveFakePromise = resolve), ); function Example() { throw fakeSuspensePromise; } modernRender( , ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--suspense-suspend-0-Example-mount-32-", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); eraseRegisteredMarks(); await resolveFakePromise(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--suspense-resolved-0-Example", ] `); }); it('should mark concurrent render with suspense that rejects', async () => { let rejectFakePromise; const fakeSuspensePromise = new Promise( (_, reject) => (rejectFakePromise = reject), ); function Example() { throw fakeSuspensePromise; } modernRender( , ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--suspense-suspend-0-Example-mount-32-", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); eraseRegisteredMarks(); await expect(() => { rejectFakePromise(new Error('error')); return fakeSuspensePromise; }).rejects.toThrow(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--suspense-rejected-0-Example", ] `); }); it('should mark cascading class component state updates', async () => { class Example extends React.Component { state = {didMount: false}; componentDidMount() { this.setState({didMount: true}); } render() { return null; } } modernRender(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-state-update-2-Example", "--layout-effects-stop", "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] `); }); it('should mark cascading class component force updates', async () => { class Example extends React.Component { componentDidMount() { this.forceUpdate(); } render() { return null; } } modernRender(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-forced-update-2-Example", "--layout-effects-stop", "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] `); }); it('should mark render phase state updates for class component', async () => { class Example extends React.Component { state = {didRender: false}; render() { if (this.state.didRender === false) { this.setState({didRender: true}); } return null; } } modernRender(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); let errorMessage; jest.spyOn(console, 'error').mockImplementation(message => { errorMessage = message; }); await waitForPaint([]); expect(console.error).toHaveBeenCalledTimes(1); expect(errorMessage).toContain( 'Cannot update during an existing state transition', ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--schedule-state-update-32-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); it('should mark render phase force updates for class component', async () => { let forced = false; class Example extends React.Component { render() { if (!forced) { forced = true; this.forceUpdate(); } return null; } } modernRender(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); let errorMessage; jest.spyOn(console, 'error').mockImplementation(message => { errorMessage = message; }); await waitForPaint([]); expect(console.error).toHaveBeenCalledTimes(1); expect(errorMessage).toContain( 'Cannot update during an existing state transition', ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--schedule-forced-update-32-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); it('should mark cascading layout updates', async () => { function Example() { const [didMount, setDidMount] = React.useState(false); React.useLayoutEffect(() => { setDidMount(true); }, []); return didMount; } modernRender(); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--component-layout-effect-mount-start-Example", "--schedule-state-update-2-Example", "--component-layout-effect-mount-stop", "--layout-effects-stop", "--render-start-2", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] `); }); it('should mark cascading passive updates', async () => { function Example() { const [didMount, setDidMount] = React.useState(false); React.useEffect(() => { setDidMount(true); }, []); return didMount; } modernRender(); await waitForAll([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", "--passive-effects-start-32", "--component-passive-effect-mount-start-Example", "--schedule-state-update-32-Example", "--component-passive-effect-mount-stop", "--passive-effects-stop", "--render-start-32", "--component-render-start-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", ] `); }); it('should mark render phase updates', async () => { function Example() { const [didRender, setDidRender] = React.useState(false); if (!didRender) { setDidRender(true); } return didRender; } modernRender(); await waitForAll([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", "--render-start-32", "--component-render-start-Example", "--schedule-state-update-32-Example", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", ] `); }); it('should mark concurrent render that throws', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); class ErrorBoundary extends React.Component { state = {error: null}; componentDidCatch(error) { this.setState({error}); } render() { if (this.state.error) { return null; } return this.props.children; } } function ExampleThatThrows() { // eslint-disable-next-line no-throw-literal throw 'Expected error'; } modernRender( , ); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); eraseRegisteredMarks(); await waitForPaint([]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--render-start-32", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", "--render-start-32", "--component-render-start-ErrorBoundary", "--component-render-stop", "--component-render-start-ExampleThatThrows", "--component-render-stop", "--error-ExampleThatThrows-mount-Expected error", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-state-update-2-ErrorBoundary", "--layout-effects-stop", "--render-start-2", "--component-render-start-ErrorBoundary", "--component-render-stop", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] `); }); it('should mark passive and layout effects', async () => { function ComponentWithEffects() { React.useLayoutEffect(() => { Scheduler.log('layout 1 mount'); return () => { Scheduler.log('layout 1 unmount'); }; }, []); React.useEffect(() => { Scheduler.log('passive 1 mount'); return () => { Scheduler.log('passive 1 unmount'); }; }, []); React.useLayoutEffect(() => { Scheduler.log('layout 2 mount'); return () => { Scheduler.log('layout 2 unmount'); }; }, []); React.useEffect(() => { Scheduler.log('passive 2 mount'); return () => { Scheduler.log('passive 2 unmount'); }; }, []); React.useEffect(() => { Scheduler.log('passive 3 mount'); return () => { Scheduler.log('passive 3 unmount'); }; }, []); return null; } const unmount = modernRender(); await waitForPaint(['layout 1 mount', 'layout 2 mount']); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", "--render-start-32", "--component-render-start-ComponentWithEffects", "--component-render-stop", "--render-stop", "--commit-start-32", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--component-layout-effect-mount-start-ComponentWithEffects", "--component-layout-effect-mount-stop", "--component-layout-effect-mount-start-ComponentWithEffects", "--component-layout-effect-mount-stop", "--layout-effects-stop", "--commit-stop", ] `); eraseRegisteredMarks(); await waitForAll([ 'passive 1 mount', 'passive 2 mount', 'passive 3 mount', ]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--passive-effects-start-32", "--component-passive-effect-mount-start-ComponentWithEffects", "--component-passive-effect-mount-stop", "--component-passive-effect-mount-start-ComponentWithEffects", "--component-passive-effect-mount-stop", "--component-passive-effect-mount-start-ComponentWithEffects", "--component-passive-effect-mount-stop", "--passive-effects-stop", ] `); eraseRegisteredMarks(); await waitForAll([]); unmount(); assertLog([ 'layout 1 unmount', 'layout 2 unmount', 'passive 1 unmount', 'passive 2 unmount', 'passive 3 unmount', ]); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-2", "--render-start-2", "--render-stop", "--commit-start-2", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--component-layout-effect-unmount-start-ComponentWithEffects", "--component-layout-effect-unmount-stop", "--component-layout-effect-unmount-start-ComponentWithEffects", "--component-layout-effect-unmount-stop", "--layout-effects-start-2", "--layout-effects-stop", "--passive-effects-start-2", "--component-passive-effect-unmount-start-ComponentWithEffects", "--component-passive-effect-unmount-stop", "--component-passive-effect-unmount-start-ComponentWithEffects", "--component-passive-effect-unmount-stop", "--component-passive-effect-unmount-start-ComponentWithEffects", "--component-passive-effect-unmount-stop", "--passive-effects-stop", "--commit-stop", ] `); }); }); describe('lane labels', () => { describe('with legacy render', () => { const {render: legacyRender} = getLegacyRenderImplementation(); // @reactVersion <= 18.2 // @reactVersion >= 18.0 it('regression test SyncLane', () => { utils.act(() => store.profilerStore.startProfiling()); legacyRender(
); utils.act(() => store.profilerStore.stopProfiling()); expect(registeredMarks).toMatchInlineSnapshot(` [ "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--schedule-render-1", "--render-start-1", "--render-stop", "--commit-start-1", "--react-version-", "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", "--layout-effects-start-1", "--layout-effects-stop", "--commit-stop", ] `); }); }); describe('with createRoot()', () => { let waitForAll; beforeEach(() => { const InternalTestUtils = require('internal-test-utils'); waitForAll = InternalTestUtils.waitForAll; }); const {render: modernRender} = getModernRenderImplementation(); it('regression test DefaultLane', () => { modernRender(
); expect(registeredMarks).toMatchInlineSnapshot(` [ "--schedule-render-32", ] `); }); it('regression test InputDiscreteLane', async () => { const targetRef = React.createRef(null); function App() { const [count, setCount] = React.useState(0); const handleClick = () => { setCount(count + 1); }; return