diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
index 23ad7f657ba33..88aed0ee63250 100644
--- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
@@ -9,51 +9,25 @@
'use strict';
-import {normalizeCodeLocInfo} from './utils';
+import {
+ getLegacyRenderImplementation,
+ getModernRenderImplementation,
+ normalizeCodeLocInfo,
+} from './utils';
describe('Timeline profiler', () => {
let React;
- let ReactDOMClient;
let Scheduler;
- let renderHelper;
- let renderRootHelper;
let store;
- let unmountFns;
let utils;
- let waitFor;
- let waitForAll;
- let waitForPaint;
- let assertLog;
beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
- unmountFns = [];
- renderHelper = element => {
- const unmountFn = utils.legacyRender(element);
- unmountFns.push(unmountFn);
- return unmountFn;
- };
- renderRootHelper = element => {
- const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- root.render(element);
- const unmountFn = () => root.unmount();
- unmountFns.push(unmountFn);
- return unmountFn;
- };
-
React = require('react');
- ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
- const InternalTestUtils = require('internal-test-utils');
- waitFor = InternalTestUtils.waitFor;
- waitForAll = InternalTestUtils.waitForAll;
- waitForPaint = InternalTestUtils.waitForPaint;
- assertLog = InternalTestUtils.assertLog;
-
store = global.store;
});
@@ -135,18 +109,57 @@ describe('Timeline profiler', () => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);
- unmountFns.forEach(unmountFn => unmountFn());
-
setPerformanceMock(null);
});
- it('should mark sync render without suspends or state updates', () => {
- renderHelper(
);
+ describe('with legacy render', () => {
+ const {render: legacyRender} = getLegacyRenderImplementation();
+
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should mark sync render without suspends or state updates', () => {
+ legacyRender();
+
+ expect(clearedMarks).toMatchInlineSnapshot(`
+ [
+ "--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
+ xit('should mark sync render with suspense that resolves', async () => {
+ const fakeSuspensePromise = Promise.resolve(true);
+ function Example() {
+ throw fakeSuspensePromise;
+ }
+
+ legacyRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).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-",
@@ -159,173 +172,224 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
- it('should mark concurrent render without suspends or state updates', async () => {
- renderRootHelper();
+ clearPendingMarks();
- expect(clearedMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-32",
- ]
- `);
+ await fakeSuspensePromise;
+ expect(clearedMarks).toMatchInlineSnapshot(`
+ [
+ "--suspense-resolved-0-Example",
+ ]
+ `);
+ });
- clearPendingMarks();
+ // TODO(hoxyq): investigate why running this test with React 18 fails
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ xit('should mark sync render with suspense that rejects', async () => {
+ const fakeSuspensePromise = Promise.reject(new Error('error'));
+ function Example() {
+ throw fakeSuspensePromise;
+ }
- await waitForPaint([]);
+ legacyRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
- "--render-start-32",
+ "--schedule-render-2",
+ "--render-start-2",
+ "--component-render-start-Example",
+ "--component-render-stop",
+ "--suspense-suspend-0-Example-mount-2-",
"--render-stop",
- "--commit-start-32",
+ "--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-32",
+ "--layout-effects-start-2",
"--layout-effects-stop",
"--commit-stop",
]
`);
- });
-
- it('should mark render yields', async () => {
- function Bar() {
- Scheduler.log('Bar');
- return null;
- }
- function Foo() {
- Scheduler.log('Foo');
- return ;
- }
+ clearPendingMarks();
- React.startTransition(() => {
- renderRootHelper();
+ await expect(fakeSuspensePromise).rejects.toThrow();
+ expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
});
- await waitFor(['Foo']);
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should mark sync render that throws', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
- expect(clearedMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-128",
- "--render-start-128",
- "--component-render-start-Foo",
- "--component-render-stop",
- "--render-yield",
- ]
- `);
- });
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
+ }
+ render() {
+ if (this.state.error) {
+ return null;
+ }
+ return this.props.children;
+ }
+ }
- it('should mark sync render with suspense that resolves', async () => {
- const fakeSuspensePromise = Promise.resolve(true);
- function Example() {
- throw fakeSuspensePromise;
- }
+ function ExampleThatThrows() {
+ throw Error('Expected error');
+ }
- renderHelper(
-
-
- ,
- );
+ legacyRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
- "--schedule-render-2",
- "--render-start-2",
- "--component-render-start-Example",
+ "--schedule-render-1",
+ "--render-start-1",
+ "--component-render-start-ErrorBoundary",
"--component-render-stop",
- "--suspense-suspend-0-Example-mount-2-",
+ "--component-render-start-ExampleThatThrows",
+ "--component-render-start-ExampleThatThrows",
+ "--component-render-stop",
+ "--error-ExampleThatThrows-mount-Expected error",
"--render-stop",
- "--commit-start-2",
+ "--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-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",
+ "--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",
]
`);
+ });
+ });
- clearPendingMarks();
+ describe('with createRoot', () => {
+ let waitFor;
+ let waitForAll;
+ let waitForPaint;
+ let assertLog;
- await fakeSuspensePromise;
- expect(clearedMarks).toMatchInlineSnapshot(`
- [
- "--suspense-resolved-0-Example",
- ]
- `);
- });
+ beforeEach(() => {
+ const InternalTestUtils = require('internal-test-utils');
+ waitFor = InternalTestUtils.waitFor;
+ waitForAll = InternalTestUtils.waitForAll;
+ waitForPaint = InternalTestUtils.waitForPaint;
+ assertLog = InternalTestUtils.assertLog;
+ });
- it('should mark sync render with suspense that rejects', async () => {
- const fakeSuspensePromise = Promise.reject(new Error('error'));
- function Example() {
- throw fakeSuspensePromise;
- }
+ const {render: modernRender} = getModernRenderImplementation();
- renderHelper(
-
-
- ,
- );
+ it('should mark concurrent render without suspends or state updates', async () => {
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
- "--schedule-render-2",
- "--render-start-2",
- "--component-render-start-Example",
- "--component-render-stop",
- "--suspense-suspend-0-Example-mount-2-",
+ "--schedule-render-32",
+ ]
+ `);
+
+ clearPendingMarks();
+
+ await waitForPaint([]);
+
+ expect(clearedMarks).toMatchInlineSnapshot(`
+ [
+ "--render-start-32",
"--render-stop",
- "--commit-start-2",
+ "--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-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-start-32",
"--layout-effects-stop",
"--commit-stop",
]
`);
+ });
- clearPendingMarks();
+ it('should mark render yields', async () => {
+ function Bar() {
+ Scheduler.log('Bar');
+ return null;
+ }
- await expect(fakeSuspensePromise).rejects.toThrow();
- expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
- });
+ function Foo() {
+ Scheduler.log('Foo');
+ return ;
+ }
+
+ React.startTransition(() => {
+ modernRender();
+ });
- it('should mark concurrent render with suspense that resolves', async () => {
- let resolveFakePromise;
- const fakeSuspensePromise = new Promise(
- resolve => (resolveFakePromise = resolve),
- );
+ await waitFor(['Foo']);
- function Example() {
- throw fakeSuspensePromise;
- }
+ expect(clearedMarks).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;
+ }
- renderRootHelper(
-
-
- ,
- );
+ modernRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -344,43 +408,43 @@ describe('Timeline profiler', () => {
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await resolveFakePromise();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ await resolveFakePromise();
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--suspense-resolved-0-Example",
]
`);
- });
+ });
- it('should mark concurrent render with suspense that rejects', async () => {
- let rejectFakePromise;
- const fakeSuspensePromise = new Promise(
- (_, reject) => (rejectFakePromise = reject),
- );
+ it('should mark concurrent render with suspense that rejects', async () => {
+ let rejectFakePromise;
+ const fakeSuspensePromise = new Promise(
+ (_, reject) => (rejectFakePromise = reject),
+ );
- function Example() {
- throw fakeSuspensePromise;
- }
+ function Example() {
+ throw fakeSuspensePromise;
+ }
- renderRootHelper(
-
-
- ,
- );
+ modernRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -399,43 +463,43 @@ describe('Timeline profiler', () => {
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await expect(() => {
- rejectFakePromise(new Error('error'));
- return fakeSuspensePromise;
- }).rejects.toThrow();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ await expect(() => {
+ rejectFakePromise(new Error('error'));
+ return fakeSuspensePromise;
+ }).rejects.toThrow();
+ expect(clearedMarks).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;
+ 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;
+ }
}
- }
- renderRootHelper();
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -464,31 +528,31 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('should mark cascading class component force updates', async () => {
- class Example extends React.Component {
- componentDidMount() {
- this.forceUpdate();
- }
- render() {
- return null;
+ it('should mark cascading class component force updates', async () => {
+ class Example extends React.Component {
+ componentDidMount() {
+ this.forceUpdate();
+ }
+ render() {
+ return null;
+ }
}
- }
- renderRootHelper();
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -517,42 +581,42 @@ describe('Timeline profiler', () => {
"--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});
+ 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;
}
- return null;
}
- }
- renderRootHelper();
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
+ let errorMessage;
+ jest.spyOn(console, 'error').mockImplementation(message => {
+ errorMessage = message;
+ });
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(errorMessage).toContain(
+ 'Cannot update during an existing state transition',
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -570,43 +634,43 @@ describe('Timeline profiler', () => {
"--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;
+ 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;
+ }
}
- }
- renderRootHelper();
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
+ let errorMessage;
+ jest.spyOn(console, 'error').mockImplementation(message => {
+ errorMessage = message;
+ });
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(errorMessage).toContain(
+ 'Cannot update during an existing state transition',
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -624,30 +688,30 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('should mark cascading layout updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useLayoutEffect(() => {
- setDidMount(true);
- }, []);
- return didMount;
- }
+ it('should mark cascading layout updates', async () => {
+ function Example() {
+ const [didMount, setDidMount] = React.useState(false);
+ React.useLayoutEffect(() => {
+ setDidMount(true);
+ }, []);
+ return didMount;
+ }
- renderRootHelper();
+ modernRender();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-Example",
@@ -678,22 +742,22 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('should mark cascading passive updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useEffect(() => {
- setDidMount(true);
- }, []);
- return didMount;
- }
+ it('should mark cascading passive updates', async () => {
+ function Example() {
+ const [didMount, setDidMount] = React.useState(false);
+ React.useEffect(() => {
+ setDidMount(true);
+ }, []);
+ return didMount;
+ }
- renderRootHelper();
+ modernRender();
- await waitForAll([]);
+ await waitForAll([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
"--render-start-32",
@@ -727,22 +791,22 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('should mark render phase updates', async () => {
- function Example() {
- const [didRender, setDidRender] = React.useState(false);
- if (!didRender) {
- setDidRender(true);
+ it('should mark render phase updates', async () => {
+ function Example() {
+ const [didRender, setDidRender] = React.useState(false);
+ if (!didRender) {
+ setDidRender(true);
+ }
+ return didRender;
}
- return didRender;
- }
- renderRootHelper();
+ modernRender();
- await waitForAll([]);
+ await waitForAll([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
"--render-start-32",
@@ -761,108 +825,46 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('should mark sync render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
+ 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;
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
}
- return this.props.children;
- }
- }
-
- function ExampleThatThrows() {
- throw Error('Expected error');
- }
-
- renderHelper(
-
-
- ,
- );
-
- expect(clearedMarks).toMatchInlineSnapshot(`
- [
- "--schedule-render-2",
- "--render-start-2",
- "--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-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",
- "--schedule-state-update-2-ErrorBoundary",
- "--layout-effects-stop",
- "--commit-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-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 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;
+ render() {
+ if (this.state.error) {
+ return null;
+ }
+ return this.props.children;
}
- return this.props.children;
}
- }
- function ExampleThatThrows() {
- // eslint-disable-next-line no-throw-literal
- throw 'Expected error';
- }
+ function ExampleThatThrows() {
+ // eslint-disable-next-line no-throw-literal
+ throw 'Expected error';
+ }
- renderRootHelper(
-
-
- ,
- );
+ modernRender(
+
+
+ ,
+ );
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForPaint([]);
+ await waitForPaint([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--render-start-32",
"--component-render-start-ErrorBoundary",
@@ -903,53 +905,53 @@ describe('Timeline profiler', () => {
"--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');
- };
- }, []);
+ it('should mark passive and layout effects', async () => {
+ function ComponentWithEffects() {
+ React.useLayoutEffect(() => {
+ Scheduler.log('layout 1 mount');
+ return () => {
+ Scheduler.log('layout 1 unmount');
+ };
+ }, []);
- return null;
- }
+ 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 = renderRootHelper();
+ const unmount = modernRender();
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
+ await waitForPaint(['layout 1 mount', 'layout 2 mount']);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
"--render-start-32",
@@ -972,15 +974,15 @@ describe('Timeline profiler', () => {
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForAll([
- 'passive 1 mount',
- 'passive 2 mount',
- 'passive 3 mount',
- ]);
+ await waitForAll([
+ 'passive 1 mount',
+ 'passive 2 mount',
+ 'passive 3 mount',
+ ]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--passive-effects-start-32",
"--component-passive-effect-mount-start-ComponentWithEffects",
@@ -993,21 +995,21 @@ describe('Timeline profiler', () => {
]
`);
- clearPendingMarks();
+ clearPendingMarks();
- await waitForAll([]);
+ await waitForAll([]);
- unmount();
+ unmount();
- assertLog([
- 'layout 1 unmount',
- 'layout 2 unmount',
- 'passive 1 unmount',
- 'passive 2 unmount',
- 'passive 3 unmount',
- ]);
+ assertLog([
+ 'layout 1 unmount',
+ 'layout 2 unmount',
+ 'passive 1 unmount',
+ 'passive 2 unmount',
+ 'passive 3 unmount',
+ ]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-2",
"--render-start-2",
@@ -1035,61 +1037,84 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
+ });
});
describe('lane labels', () => {
- it('regression test SyncLane', () => {
- renderHelper();
+ describe('with legacy render', () => {
+ const {render: legacyRender} = getLegacyRenderImplementation();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('regression test SyncLane', () => {
+ legacyRender();
+
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
- "--schedule-render-2",
- "--render-start-2",
+ "--schedule-render-1",
+ "--render-start-1",
"--render-stop",
- "--commit-start-2",
+ "--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-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",
+ "--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",
]
`);
+ });
});
- it('regression test DefaultLane', () => {
- renderRootHelper();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ 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('regression test DefaultLane', () => {
+ modernRender();
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-render-32",
]
`);
- });
+ });
- it('regression test InputDiscreteLane', async () => {
- const targetRef = React.createRef(null);
+ it('regression test InputDiscreteLane', async () => {
+ const targetRef = React.createRef(null);
- function App() {
- const [count, setCount] = React.useState(0);
- const handleClick = () => {
- setCount(count + 1);
- };
- return ;
- }
+ function App() {
+ const [count, setCount] = React.useState(0);
+ const handleClick = () => {
+ setCount(count + 1);
+ };
+ return ;
+ }
- renderRootHelper();
- await waitForAll([]);
+ modernRender();
+ await waitForAll([]);
- clearedMarks.splice(0);
+ clearedMarks.splice(0);
- targetRef.current.click();
+ targetRef.current.click();
- // Wait a frame, for React to process the "click" update.
- await Promise.resolve();
+ // Wait a frame, for React to process the "click" update.
+ await Promise.resolve();
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-state-update-2-App",
"--render-start-2",
@@ -1107,29 +1132,29 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
- });
+ });
- it('regression test InputContinuousLane', async () => {
- const targetRef = React.createRef(null);
+ it('regression test InputContinuousLane', async () => {
+ const targetRef = React.createRef(null);
- function App() {
- const [count, setCount] = React.useState(0);
- const handleMouseOver = () => setCount(count + 1);
- return ;
- }
+ function App() {
+ const [count, setCount] = React.useState(0);
+ const handleMouseOver = () => setCount(count + 1);
+ return ;
+ }
- renderRootHelper();
- await waitForAll([]);
+ modernRender();
+ await waitForAll([]);
- clearedMarks.splice(0);
+ clearedMarks.splice(0);
- const event = document.createEvent('MouseEvents');
- event.initEvent('mouseover', true, true);
- dispatchAndSetCurrentEvent(targetRef.current, event);
+ const event = document.createEvent('MouseEvents');
+ event.initEvent('mouseover', true, true);
+ dispatchAndSetCurrentEvent(targetRef.current, event);
- await waitForAll([]);
+ await waitForAll([]);
- expect(clearedMarks).toMatchInlineSnapshot(`
+ expect(clearedMarks).toMatchInlineSnapshot(`
[
"--schedule-state-update-8-App",
"--render-start-8",
@@ -1147,6 +1172,7 @@ describe('Timeline profiler', () => {
"--commit-stop",
]
`);
+ });
});
});
});
@@ -1196,181 +1222,170 @@ describe('Timeline profiler', () => {
};
});
- afterEach(() => {
- unmountFns.forEach(unmountFn => unmountFn());
- });
-
describe('when profiling', () => {
beforeEach(() => {
utils.act(() => store.profilerStore.startProfiling());
});
- it('should mark sync render without suspends or state updates', () => {
- renderHelper();
+ describe('with legacy render', () => {
+ const {render: legacyRender} = getLegacyRenderImplementation();
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should mark sync render without suspends or state updates', () => {
+ legacyRender();
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
- "lanes": "0b0000000000000000000000000000010",
+ "lanes": "0b0000000000000000000000000000001",
"timestamp": 10,
"type": "schedule-render",
"warning": null,
},
]
`);
- });
+ });
- it('should mark concurrent render without suspends or state updates', () => {
- utils.act(() => renderRootHelper());
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should mark sync render that throws', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
+ }
+ render() {
+ Scheduler.unstable_advanceTime(10);
+ if (this.state.error) {
+ Scheduler.unstable_yieldValue('ErrorBoundary fallback');
+ return null;
+ }
+ Scheduler.unstable_yieldValue('ErrorBoundary render');
+ return this.props.children;
+ }
+ }
+
+ function ExampleThatThrows() {
+ Scheduler.unstable_yieldValue('ExampleThatThrows');
+ throw Error('Expected error');
+ }
+
+ legacyRender(
+
+
+ ,
+ );
+
+ expect(Scheduler.unstable_clearYields()).toEqual([
+ 'ErrorBoundary render',
+ 'ExampleThatThrows',
+ 'ExampleThatThrows',
+ 'ErrorBoundary fallback',
+ ]);
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
- "lanes": "0b0000000000000000000000000100000",
+ "componentName": "ErrorBoundary",
+ "duration": 10,
"timestamp": 10,
- "type": "schedule-render",
+ "type": "render",
+ "warning": null,
+ },
+ {
+ "componentName": "ExampleThatThrows",
+ "duration": 0,
+ "timestamp": 20,
+ "type": "render",
+ "warning": null,
+ },
+ {
+ "componentName": "ErrorBoundary",
+ "duration": 10,
+ "timestamp": 20,
+ "type": "render",
"warning": null,
},
]
`);
- });
-
- it('should mark concurrent render without suspends or state updates', () => {
- let updaterFn;
-
- function Example() {
- const setHigh = React.useState(0)[1];
- const setLow = React.useState(0)[1];
-
- updaterFn = () => {
- React.startTransition(() => {
- setLow(prevLow => prevLow + 1);
- });
- setHigh(prevHigh => prevHigh + 1);
- };
-
- Scheduler.unstable_advanceTime(10);
-
- return null;
- }
-
- utils.act(() => renderRootHelper());
- utils.act(() => store.profilerStore.stopProfiling());
- utils.act(() => store.profilerStore.startProfiling());
- utils.act(updaterFn);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
- "componentName": "Example",
- "componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000010000000",
+ "lanes": "0b0000000000000000000000000000001",
"timestamp": 10,
- "type": "schedule-state-update",
+ "type": "schedule-render",
"warning": null,
},
{
- "componentName": "Example",
+ "componentName": "ErrorBoundary",
"componentStack": "
- in Example (at **)",
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
+ in ErrorBoundary (at **)",
+ "lanes": "0b0000000000000000000000000000001",
+ "timestamp": 20,
"type": "schedule-state-update",
"warning": null,
},
]
`);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
[
{
- "componentName": "Example",
- "duration": 0,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "Example",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
+ "componentName": "ExampleThatThrows",
+ "message": "Expected error",
+ "phase": "mount",
+ "timestamp": 20,
+ "type": "thrown-error",
},
]
`);
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- });
-
- // @reactVersion >=18.2
- it('should mark render yields', async () => {
- function Bar() {
- Scheduler.log('Bar');
- return null;
- }
-
- function Foo() {
- Scheduler.log('Foo');
- return ;
- }
-
- React.startTransition(() => {
- renderRootHelper();
});
- // Do one step of work.
- await waitFor(['Foo']);
-
- // Finish flushing so React commits;
- // Unless we do this, the ProfilerStore won't collect Profiling data.
- await waitForAll(['Bar']);
-
- // Since we yielded, the batch should report two separate "render" chunks.
- const batch = getBatchOfWork(0);
- expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
- });
-
- it('should mark sync render with suspense that resolves', async () => {
- let resolveFn;
- let resolved = false;
- const suspensePromise = new Promise(resolve => {
- resolveFn = () => {
- resolved = true;
- resolve();
- };
- });
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should mark sync render with suspense that resolves', async () => {
+ let resolveFn;
+ let resolved = false;
+ const suspensePromise = new Promise(resolve => {
+ resolveFn = () => {
+ resolved = true;
+ resolve();
+ };
+ });
- function Example() {
- Scheduler.log(resolved ? 'resolved' : 'suspended');
- if (!resolved) {
- throw suspensePromise;
+ function Example() {
+ Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
+ if (!resolved) {
+ throw suspensePromise;
+ }
+ return null;
}
- return null;
- }
- renderHelper(
-
-
- ,
- );
+ legacyRender(
+
+
+ ,
+ );
- assertLog(['suspended']);
+ expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
- Scheduler.unstable_advanceTime(10);
- resolveFn();
- await suspensePromise;
+ Scheduler.unstable_advanceTime(10);
+ resolveFn();
+ await suspensePromise;
- await waitForAll(['resolved']);
+ await Scheduler.unstable_flushAllWithoutAsserting();
+ expect(Scheduler.unstable_clearYields()).toEqual(['resolved']);
- const timelineData = stopProfilingAndGetTimelineData();
+ const timelineData = stopProfilingAndGetTimelineData();
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
+ // Verify the Suspense event and duration was recorded.
+ expect(timelineData.suspenseEvents).toHaveLength(1);
+ const suspenseEvent = timelineData.suspenseEvents[0];
+ expect(suspenseEvent).toMatchInlineSnapshot(`
{
"componentName": "Example",
"depth": 0,
@@ -1385,50 +1400,50 @@ describe('Timeline profiler', () => {
}
`);
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
- });
-
- // @reactVersion >=18.2
- it('should mark sync render with suspense that rejects', async () => {
- let rejectFn;
- let rejected = false;
- const suspensePromise = new Promise((resolve, reject) => {
- rejectFn = () => {
- rejected = true;
- reject(new Error('error'));
- };
+ // There should be two batches of renders: Suspeneded and resolved.
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toHaveLength(2);
});
- function Example() {
- Scheduler.log(rejected ? 'rejected' : 'suspended');
- if (!rejected) {
- throw suspensePromise;
+ // @reactVersion = 18.2
+ it('should mark sync render with suspense that rejects', async () => {
+ let rejectFn;
+ let rejected = false;
+ const suspensePromise = new Promise((resolve, reject) => {
+ rejectFn = () => {
+ rejected = true;
+ reject(new Error('error'));
+ };
+ });
+
+ function Example() {
+ Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
+ if (!rejected) {
+ throw suspensePromise;
+ }
+ return null;
}
- return null;
- }
- renderHelper(
-
-
- ,
- );
+ legacyRender(
+
+
+ ,
+ );
- assertLog(['suspended']);
+ expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
- Scheduler.unstable_advanceTime(10);
- rejectFn();
- await expect(suspensePromise).rejects.toThrow();
+ Scheduler.unstable_advanceTime(10);
+ rejectFn();
+ await expect(suspensePromise).rejects.toThrow();
- assertLog(['rejected']);
+ expect(Scheduler.unstable_clearYields()).toEqual(['rejected']);
- const timelineData = stopProfilingAndGetTimelineData();
+ const timelineData = stopProfilingAndGetTimelineData();
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
+ // Verify the Suspense event and duration was recorded.
+ expect(timelineData.suspenseEvents).toHaveLength(1);
+ const suspenseEvent = timelineData.suspenseEvents[0];
+ expect(suspenseEvent).toMatchInlineSnapshot(`
{
"componentName": "Example",
"depth": 0,
@@ -1443,50 +1458,177 @@ describe('Timeline profiler', () => {
}
`);
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
+ // There should be two batches of renders: Suspeneded and resolved.
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toHaveLength(2);
+ });
});
- // @reactVersion >=18.2
- it('should mark concurrent render with suspense that resolves', async () => {
- let resolveFn;
- let resolved = false;
- const suspensePromise = new Promise(resolve => {
- resolveFn = () => {
- resolved = true;
- resolve();
- };
+ 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;
});
- function Example() {
- Scheduler.log(resolved ? 'resolved' : 'suspended');
- if (!resolved) {
- throw suspensePromise;
+ const {render: modernRender} = getModernRenderImplementation();
+
+ it('should mark concurrent render without suspends or state updates', () => {
+ utils.act(() => modernRender());
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ [
+ {
+ "lanes": "0b0000000000000000000000000100000",
+ "timestamp": 10,
+ "type": "schedule-render",
+ "warning": null,
+ },
+ ]
+ `);
+ });
+
+ it('should mark concurrent render without suspends or state updates', () => {
+ let updaterFn;
+
+ function Example() {
+ const setHigh = React.useState(0)[1];
+ const setLow = React.useState(0)[1];
+
+ updaterFn = () => {
+ React.startTransition(() => {
+ setLow(prevLow => prevLow + 1);
+ });
+ setHigh(prevHigh => prevHigh + 1);
+ };
+
+ Scheduler.unstable_advanceTime(10);
+
+ return null;
}
- return null;
- }
- renderRootHelper(
-
-
- ,
- );
+ utils.act(() => modernRender());
+ utils.act(() => store.profilerStore.stopProfiling());
+ utils.act(() => store.profilerStore.startProfiling());
+ utils.act(updaterFn);
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ [
+ {
+ "componentName": "Example",
+ "componentStack": "
+ in Example (at **)",
+ "lanes": "0b0000000000000000000000010000000",
+ "timestamp": 10,
+ "type": "schedule-state-update",
+ "warning": null,
+ },
+ {
+ "componentName": "Example",
+ "componentStack": "
+ in Example (at **)",
+ "lanes": "0b0000000000000000000000000100000",
+ "timestamp": 10,
+ "type": "schedule-state-update",
+ "warning": null,
+ },
+ ]
+ `);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ [
+ {
+ "componentName": "Example",
+ "duration": 0,
+ "timestamp": 10,
+ "type": "render",
+ "warning": null,
+ },
+ {
+ "componentName": "Example",
+ "duration": 10,
+ "timestamp": 10,
+ "type": "render",
+ "warning": null,
+ },
+ ]
+ `);
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ });
- await waitForAll(['suspended']);
+ it('should mark render yields', async () => {
+ function Bar() {
+ Scheduler.log('Bar');
+ return null;
+ }
+
+ function Foo() {
+ Scheduler.log('Foo');
+ return ;
+ }
- Scheduler.unstable_advanceTime(10);
- resolveFn();
- await suspensePromise;
+ React.startTransition(() => {
+ modernRender();
+ });
- await waitForAll(['resolved']);
+ // Do one step of work.
+ await waitFor(['Foo']);
- const timelineData = stopProfilingAndGetTimelineData();
+ // Finish flushing so React commits;
+ // Unless we do this, the ProfilerStore won't collect Profiling data.
+ await waitForAll(['Bar']);
+
+ // Since we yielded, the batch should report two separate "render" chunks.
+ const batch = getBatchOfWork(0);
+ expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
+ });
+
+ it('should mark concurrent render with suspense that resolves', async () => {
+ let resolveFn;
+ let resolved = false;
+ const suspensePromise = new Promise(resolve => {
+ resolveFn = () => {
+ resolved = true;
+ resolve();
+ };
+ });
+
+ function Example() {
+ Scheduler.log(resolved ? 'resolved' : 'suspended');
+ if (!resolved) {
+ throw suspensePromise;
+ }
+ return null;
+ }
+
+ modernRender(
+
+
+ ,
+ );
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
+ await waitForAll(['suspended']);
+
+ Scheduler.unstable_advanceTime(10);
+ resolveFn();
+ await suspensePromise;
+
+ await waitForAll(['resolved']);
+
+ const timelineData = stopProfilingAndGetTimelineData();
+
+ // Verify the Suspense event and duration was recorded.
+ expect(timelineData.suspenseEvents).toHaveLength(1);
+ const suspenseEvent = timelineData.suspenseEvents[0];
+ expect(suspenseEvent).toMatchInlineSnapshot(`
{
"componentName": "Example",
"depth": 0,
@@ -1501,50 +1643,49 @@ describe('Timeline profiler', () => {
}
`);
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
- });
-
- // @reactVersion >=18.2
- it('should mark concurrent render with suspense that rejects', async () => {
- let rejectFn;
- let rejected = false;
- const suspensePromise = new Promise((resolve, reject) => {
- rejectFn = () => {
- rejected = true;
- reject(new Error('error'));
- };
+ // There should be two batches of renders: Suspeneded and resolved.
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toHaveLength(2);
});
- function Example() {
- Scheduler.log(rejected ? 'rejected' : 'suspended');
- if (!rejected) {
- throw suspensePromise;
+ it('should mark concurrent render with suspense that rejects', async () => {
+ let rejectFn;
+ let rejected = false;
+ const suspensePromise = new Promise((resolve, reject) => {
+ rejectFn = () => {
+ rejected = true;
+ reject(new Error('error'));
+ };
+ });
+
+ function Example() {
+ Scheduler.log(rejected ? 'rejected' : 'suspended');
+ if (!rejected) {
+ throw suspensePromise;
+ }
+ return null;
}
- return null;
- }
- renderRootHelper(
-
-
- ,
- );
+ modernRender(
+
+
+ ,
+ );
- await waitForAll(['suspended']);
+ await waitForAll(['suspended']);
- Scheduler.unstable_advanceTime(10);
- rejectFn();
- await expect(suspensePromise).rejects.toThrow();
+ Scheduler.unstable_advanceTime(10);
+ rejectFn();
+ await expect(suspensePromise).rejects.toThrow();
- await waitForAll(['rejected']);
+ await waitForAll(['rejected']);
- const timelineData = stopProfilingAndGetTimelineData();
+ const timelineData = stopProfilingAndGetTimelineData();
- // Verify the Suspense event and duration was recorded.
- expect(timelineData.suspenseEvents).toHaveLength(1);
- const suspenseEvent = timelineData.suspenseEvents[0];
- expect(suspenseEvent).toMatchInlineSnapshot(`
+ // Verify the Suspense event and duration was recorded.
+ expect(timelineData.suspenseEvents).toHaveLength(1);
+ const suspenseEvent = timelineData.suspenseEvents[0];
+ expect(suspenseEvent).toMatchInlineSnapshot(`
{
"componentName": "Example",
"depth": 0,
@@ -1559,31 +1700,31 @@ describe('Timeline profiler', () => {
}
`);
- // There should be two batches of renders: Suspeneded and resolved.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toHaveLength(2);
- });
+ // There should be two batches of renders: Suspeneded and resolved.
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toHaveLength(2);
+ });
- it('should mark cascading class component state updates', async () => {
- class Example extends React.Component {
- state = {didMount: false};
- componentDidMount() {
- this.setState({didMount: true});
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(this.state.didMount ? 'update' : 'mount');
- return null;
+ it('should mark cascading class component state updates', async () => {
+ class Example extends React.Component {
+ state = {didMount: false};
+ componentDidMount() {
+ this.setState({didMount: true});
+ }
+ render() {
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(this.state.didMount ? 'update' : 'mount');
+ return null;
+ }
}
- }
- renderRootHelper();
+ modernRender();
- await waitForPaint(['mount', 'update']);
+ await waitForPaint(['mount', 'update']);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1601,7 +1742,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1620,29 +1761,29 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark cascading class component force updates', async () => {
- let forced = false;
- class Example extends React.Component {
- componentDidMount() {
- forced = true;
- this.forceUpdate();
- }
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(forced ? 'force update' : 'mount');
- return null;
+ it('should mark cascading class component force updates', async () => {
+ let forced = false;
+ class Example extends React.Component {
+ componentDidMount() {
+ forced = true;
+ this.forceUpdate();
+ }
+ render() {
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(forced ? 'force update' : 'mount');
+ return null;
+ }
}
- }
- renderRootHelper();
+ modernRender();
- await waitForPaint(['mount', 'force update']);
+ await waitForPaint(['mount', 'force update']);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1660,7 +1801,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1677,40 +1818,40 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- 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});
+ 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});
+ }
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(
+ this.state.didRender ? 'second render' : 'first render',
+ );
+ return null;
}
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(
- this.state.didRender ? 'second render' : 'first render',
- );
- return null;
}
- }
-
- renderRootHelper();
-
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
- await waitForAll(['first render', 'second render']);
+ modernRender();
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
+ let errorMessage;
+ jest.spyOn(console, 'error').mockImplementation(message => {
+ errorMessage = message;
+ });
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ await waitForAll(['first render', 'second render']);
+
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(errorMessage).toContain(
+ 'Cannot update during an existing state transition',
+ );
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1728,7 +1869,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1747,39 +1888,39 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark render phase force updates for class component', async () => {
- let forced = false;
- class Example extends React.Component {
- render() {
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(forced ? 'force update' : 'render');
- if (!forced) {
- forced = true;
- this.forceUpdate();
+ it('should mark render phase force updates for class component', async () => {
+ let forced = false;
+ class Example extends React.Component {
+ render() {
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(forced ? 'force update' : 'render');
+ if (!forced) {
+ forced = true;
+ this.forceUpdate();
+ }
+ return null;
}
- return null;
}
- }
- renderRootHelper();
+ modernRender();
- let errorMessage;
- jest.spyOn(console, 'error').mockImplementation(message => {
- errorMessage = message;
- });
+ let errorMessage;
+ jest.spyOn(console, 'error').mockImplementation(message => {
+ errorMessage = message;
+ });
- await waitForAll(['render', 'force update']);
+ await waitForAll(['render', 'force update']);
- expect(console.error).toHaveBeenCalledTimes(1);
- expect(errorMessage).toContain(
- 'Cannot update during an existing state transition',
- );
+ expect(console.error).toHaveBeenCalledTimes(1);
+ expect(errorMessage).toContain(
+ 'Cannot update during an existing state transition',
+ );
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1797,7 +1938,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1814,27 +1955,27 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark cascading layout updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useLayoutEffect(() => {
- Scheduler.unstable_advanceTime(1);
- setDidMount(true);
- }, []);
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(didMount ? 'update' : 'mount');
- return didMount;
- }
+ it('should mark cascading layout updates', async () => {
+ function Example() {
+ const [didMount, setDidMount] = React.useState(false);
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_advanceTime(1);
+ setDidMount(true);
+ }, []);
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(didMount ? 'update' : 'mount');
+ return didMount;
+ }
- renderRootHelper();
+ modernRender();
- await waitForAll(['mount', 'update']);
+ await waitForAll(['mount', 'update']);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1859,7 +2000,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1878,26 +2019,26 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark cascading passive updates', async () => {
- function Example() {
- const [didMount, setDidMount] = React.useState(false);
- React.useEffect(() => {
- Scheduler.unstable_advanceTime(1);
- setDidMount(true);
- }, []);
- Scheduler.unstable_advanceTime(10);
- Scheduler.log(didMount ? 'update' : 'mount');
- return didMount;
- }
+ it('should mark cascading passive updates', async () => {
+ function Example() {
+ const [didMount, setDidMount] = React.useState(false);
+ React.useEffect(() => {
+ Scheduler.unstable_advanceTime(1);
+ setDidMount(true);
+ }, []);
+ Scheduler.unstable_advanceTime(10);
+ Scheduler.log(didMount ? 'update' : 'mount');
+ return didMount;
+ }
- renderRootHelper();
- await waitForAll(['mount', 'update']);
+ modernRender();
+ await waitForAll(['mount', 'update']);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1922,7 +2063,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1941,26 +2082,26 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark render phase updates', async () => {
- function Example() {
- const [didRender, setDidRender] = React.useState(false);
- Scheduler.unstable_advanceTime(10);
- if (!didRender) {
- setDidRender(true);
+ it('should mark render phase updates', async () => {
+ function Example() {
+ const [didRender, setDidRender] = React.useState(false);
+ Scheduler.unstable_advanceTime(10);
+ if (!didRender) {
+ setDidRender(true);
+ }
+ Scheduler.log(didRender ? 'update' : 'mount');
+ return didRender;
}
- Scheduler.log(didRender ? 'update' : 'mount');
- return didRender;
- }
- renderRootHelper();
- await waitForAll(['mount', 'update']);
+ modernRender();
+ await waitForAll(['mount', 'update']);
- const timelineData = stopProfilingAndGetTimelineData();
- // Render phase updates should be retried as part of the same batch.
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ // Render phase updates should be retried as part of the same batch.
+ expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "Example",
@@ -1971,7 +2112,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -1990,146 +2131,51 @@ describe('Timeline profiler', () => {
},
]
`);
- });
+ });
- it('should mark sync render that throws', async () => {
- jest.spyOn(console, 'error').mockImplementation(() => {});
+ 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() {
- Scheduler.unstable_advanceTime(10);
- if (this.state.error) {
- Scheduler.log('ErrorBoundary fallback');
- return null;
+ class ErrorBoundary extends React.Component {
+ state = {error: null};
+ componentDidCatch(error) {
+ this.setState({error});
}
- Scheduler.log('ErrorBoundary render');
- return this.props.children;
- }
- }
-
- function ExampleThatThrows() {
- Scheduler.log('ExampleThatThrows');
- throw Error('Expected error');
- }
-
- renderHelper(
-
-
- ,
- );
-
- assertLog([
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ExampleThatThrows',
- 'ErrorBoundary fallback',
- ]);
-
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 10,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ExampleThatThrows",
- "duration": 0,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "duration": 10,
- "timestamp": 20,
- "type": "render",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
- [
- {
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 10,
- "type": "schedule-render",
- "warning": null,
- },
- {
- "componentName": "ErrorBoundary",
- "componentStack": "
- in ErrorBoundary (at **)",
- "lanes": "0b0000000000000000000000000000010",
- "timestamp": 20,
- "type": "schedule-state-update",
- "warning": null,
- },
- ]
- `);
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
- [
- {
- "componentName": "ExampleThatThrows",
- "message": "Expected error",
- "phase": "mount",
- "timestamp": 20,
- "type": "thrown-error",
- },
- ]
- `);
- });
-
- 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() {
- Scheduler.unstable_advanceTime(10);
- if (this.state.error) {
- Scheduler.log('ErrorBoundary fallback');
- return null;
+ render() {
+ Scheduler.unstable_advanceTime(10);
+ if (this.state.error) {
+ Scheduler.log('ErrorBoundary fallback');
+ return null;
+ }
+ Scheduler.log('ErrorBoundary render');
+ return this.props.children;
}
- Scheduler.log('ErrorBoundary render');
- return this.props.children;
}
- }
-
- function ExampleThatThrows() {
- Scheduler.log('ExampleThatThrows');
- // eslint-disable-next-line no-throw-literal
- throw 'Expected error';
- }
-
- renderRootHelper(
-
-
- ,
- );
- await waitForAll([
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ExampleThatThrows',
- 'ErrorBoundary render',
- 'ExampleThatThrows',
- 'ExampleThatThrows',
- 'ErrorBoundary fallback',
- ]);
+ function ExampleThatThrows() {
+ Scheduler.log('ExampleThatThrows');
+ // eslint-disable-next-line no-throw-literal
+ throw 'Expected error';
+ }
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ modernRender(
+
+
+ ,
+ );
+
+ await waitForAll([
+ 'ErrorBoundary render',
+ 'ExampleThatThrows',
+ 'ExampleThatThrows',
+ 'ErrorBoundary render',
+ 'ExampleThatThrows',
+ 'ExampleThatThrows',
+ 'ErrorBoundary fallback',
+ ]);
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "ErrorBoundary",
@@ -2168,7 +2214,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -2187,7 +2233,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
+ expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
[
{
"componentName": "ExampleThatThrows",
@@ -2205,72 +2251,72 @@ describe('Timeline profiler', () => {
},
]
`);
- });
-
- 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');
- };
- }, []);
+ 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;
- }
+ return null;
+ }
- const unmount = renderRootHelper();
+ const unmount = modernRender();
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
+ await waitForPaint(['layout 1 mount', 'layout 2 mount']);
- await waitForAll([
- 'passive 1 mount',
- 'passive 2 mount',
- 'passive 3 mount',
- ]);
+ await waitForAll([
+ 'passive 1 mount',
+ 'passive 2 mount',
+ 'passive 3 mount',
+ ]);
- await waitForAll([]);
+ await waitForAll([]);
- unmount();
+ unmount();
- assertLog([
- 'layout 1 unmount',
- 'layout 2 unmount',
- 'passive 1 unmount',
- 'passive 2 unmount',
- 'passive 3 unmount',
- ]);
+ assertLog([
+ 'layout 1 unmount',
+ 'layout 2 unmount',
+ 'passive 1 unmount',
+ 'passive 2 unmount',
+ 'passive 3 unmount',
+ ]);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
[
{
"componentName": "ComponentWithEffects",
@@ -2351,7 +2397,7 @@ describe('Timeline profiler', () => {
},
]
`);
- expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
+ expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
Map {
1 => [
{
@@ -2439,33 +2485,33 @@ describe('Timeline profiler', () => {
],
}
`);
- });
+ });
- it('should generate component stacks for state update', async () => {
- function CommponentWithChildren({initialRender}) {
- Scheduler.log('Render ComponentWithChildren');
- return ;
- }
+ it('should generate component stacks for state update', async () => {
+ function CommponentWithChildren({initialRender}) {
+ Scheduler.log('Render ComponentWithChildren');
+ return ;
+ }
- function Child({initialRender}) {
- const [didRender, setDidRender] = React.useState(initialRender);
- if (!didRender) {
- setDidRender(true);
+ function Child({initialRender}) {
+ const [didRender, setDidRender] = React.useState(initialRender);
+ if (!didRender) {
+ setDidRender(true);
+ }
+ Scheduler.log('Render Child');
+ return null;
}
- Scheduler.log('Render Child');
- return null;
- }
- renderRootHelper();
+ modernRender();
- await waitForAll([
- 'Render ComponentWithChildren',
- 'Render Child',
- 'Render Child',
- ]);
+ await waitForAll([
+ 'Render ComponentWithChildren',
+ 'Render Child',
+ 'Render Child',
+ ]);
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
[
{
"lanes": "0b0000000000000000000000000100000",
@@ -2485,16 +2531,22 @@ describe('Timeline profiler', () => {
},
]
`);
+ });
});
});
describe('when not profiling', () => {
- // @reactVersion >=18.0
- it('should not log any marks', () => {
- renderHelper();
+ describe('with legacy render', () => {
+ const {render: legacyRender} = getLegacyRenderImplementation();
- const timelineData = stopProfilingAndGetTimelineData();
- expect(timelineData).toBeNull();
+ // @reactVersion <= 18.2
+ // @reactVersion >= 18.0
+ it('should not log any marks', () => {
+ legacyRender();
+
+ const timelineData = stopProfilingAndGetTimelineData();
+ expect(timelineData).toBeNull();
+ });
});
});
});