1
-/**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-'use strict';
11
-
12
-import {
13
- getLegacyRenderImplementation,
14
- getModernRenderImplementation,
15
- normalizeCodeLocInfo,
16
-} from './utils';
17
-
18
-let React = require('react');
19
-let Scheduler;
20
-let store;
21
-let utils;
22
-
23
-// This flag is on experimental which disables timeline profiler.
24
-const enableComponentPerformanceTrack =
25
- React.version.startsWith('19') && React.version.includes('experimental');
26
-
27
-describe('Timeline profiler', () => {
28
- if (enableComponentPerformanceTrack) {
29
- test('no tests', () => {});
30
- // Ignore all tests.
31
- return;
32
- }
33
-
34
- beforeEach(() => {
35
- utils = require('./utils');
36
- utils.beforeEachProfiling();
37
-
38
- React = require('react');
39
- Scheduler = require('scheduler');
40
-
41
- store = global.store;
42
- });
43
-
44
- afterEach(() => {
45
- jest.restoreAllMocks();
46
- });
47
-
48
- describe('User Timing API', () => {
49
- let currentlyNotClearedMarks;
50
- let registeredMarks;
51
- let featureDetectionMarkName = null;
52
- let setPerformanceMock;
53
-
54
- function createUserTimingPolyfill() {
55
- featureDetectionMarkName = null;
56
-
57
- currentlyNotClearedMarks = [];
58
- registeredMarks = [];
59
-
60
- // Remove file-system specific bits or version-specific bits of information from the module range marks.
61
- function filterMarkData(markName) {
62
- if (markName.startsWith('--react-internal-module-start')) {
63
- return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
64
- } else if (markName.startsWith('--react-internal-module-stop')) {
65
- return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
66
- } else if (markName.startsWith('--react-version')) {
67
- return '--react-version-<filtered-version>';
68
- } else {
69
- return markName;
70
- }
71
- }
72
-
73
- // This is not a true polyfill, but it gives us enough to capture marks.
74
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
75
- return {
76
- clearMarks(markName) {
77
- markName = filterMarkData(markName);
78
-
79
- currentlyNotClearedMarks = currentlyNotClearedMarks.filter(
80
- mark => mark !== markName,
81
- );
82
- },
83
- mark(markName, markOptions) {
84
- markName = filterMarkData(markName);
85
-
86
- if (featureDetectionMarkName === null) {
87
- featureDetectionMarkName = markName;
88
- }
89
-
90
- registeredMarks.push(markName);
91
- currentlyNotClearedMarks.push(markName);
92
-
93
- if (markOptions != null) {
94
- // This is triggers the feature detection.
95
- markOptions.startTime++;
96
- }
97
- },
98
- };
99
- }
100
-
101
- function eraseRegisteredMarks() {
102
- registeredMarks.splice(0);
103
- }
104
-
105
- function dispatchAndSetCurrentEvent(element, event) {
106
- try {
107
- window.event = event;
108
- element.dispatchEvent(event);
109
- } finally {
110
- window.event = undefined;
111
- }
112
- }
113
-
114
- beforeEach(() => {
115
- setPerformanceMock =
116
- require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
117
- setPerformanceMock(createUserTimingPolyfill());
118
- });
119
-
120
- afterEach(() => {
121
- // Verify all logged marks also get cleared.
122
- expect(currentlyNotClearedMarks).toHaveLength(0);
123
-
124
- setPerformanceMock(null);
125
- });
126
-
127
- describe('with legacy render', () => {
128
- const {render: legacyRender} = getLegacyRenderImplementation();
129
-
130
- // @reactVersion <= 18.2
131
- // @reactVersion >= 18.0
132
- it('should mark sync render without suspends or state updates', () => {
133
- utils.act(() => store.profilerStore.startProfiling());
134
- legacyRender(<div />);
135
- utils.act(() => store.profilerStore.stopProfiling());
136
-
137
- expect(registeredMarks).toMatchInlineSnapshot(`
138
- [
139
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
140
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
141
- "--schedule-render-1",
142
- "--render-start-1",
143
- "--render-stop",
144
- "--commit-start-1",
145
- "--react-version-<filtered-version>",
146
- "--profiler-version-1",
147
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
148
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
149
- "--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",
150
- "--layout-effects-start-1",
151
- "--layout-effects-stop",
152
- "--commit-stop",
153
- ]
154
- `);
155
- });
156
-
157
- // TODO(hoxyq): investigate why running this test with React 18 fails
158
- // @reactVersion <= 18.2
159
- // @reactVersion >= 18.0
160
- // eslint-disable-next-line jest/no-disabled-tests
161
- it.skip('should mark sync render with suspense that resolves', async () => {
162
- const fakeSuspensePromise = Promise.resolve(true);
163
- function Example() {
164
- throw fakeSuspensePromise;
165
- }
166
-
167
- legacyRender(
168
- <React.Suspense fallback={null}>
169
- <Example />
170
- </React.Suspense>,
171
- );
172
-
173
- expect(registeredMarks).toMatchInlineSnapshot(`
174
- [
175
- "--schedule-render-2",
176
- "--render-start-2",
177
- "--component-render-start-Example",
178
- "--component-render-stop",
179
- "--suspense-suspend-0-Example-mount-2-",
180
- "--render-stop",
181
- "--commit-start-2",
182
- "--react-version-<filtered-version>",
183
- "--profiler-version-1",
184
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
185
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
186
- "--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",
187
- "--layout-effects-start-2",
188
- "--layout-effects-stop",
189
- "--commit-stop",
190
- ]
191
- `);
192
-
193
- eraseRegisteredMarks();
194
-
195
- await fakeSuspensePromise;
196
- expect(registeredMarks).toMatchInlineSnapshot(`
197
- [
198
- "--suspense-resolved-0-Example",
199
- ]
200
- `);
201
- });
202
-
203
- // TODO(hoxyq): investigate why running this test with React 18 fails
204
- // @reactVersion <= 18.2
205
- // @reactVersion >= 18.0
206
- // eslint-disable-next-line jest/no-disabled-tests
207
- it.skip('should mark sync render with suspense that rejects', async () => {
208
- const fakeSuspensePromise = Promise.reject(new Error('error'));
209
- function Example() {
210
- throw fakeSuspensePromise;
211
- }
212
-
213
- legacyRender(
214
- <React.Suspense fallback={null}>
215
- <Example />
216
- </React.Suspense>,
217
- );
218
-
219
- expect(registeredMarks).toMatchInlineSnapshot(`
220
- [
221
- "--schedule-render-2",
222
- "--render-start-2",
223
- "--component-render-start-Example",
224
- "--component-render-stop",
225
- "--suspense-suspend-0-Example-mount-2-",
226
- "--render-stop",
227
- "--commit-start-2",
228
- "--react-version-<filtered-version>",
229
- "--profiler-version-1",
230
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
231
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
232
- "--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",
233
- "--layout-effects-start-2",
234
- "--layout-effects-stop",
235
- "--commit-stop",
236
- ]
237
- `);
238
-
239
- eraseRegisteredMarks();
240
-
241
- await expect(fakeSuspensePromise).rejects.toThrow();
242
- expect(registeredMarks).toContain(`--suspense-rejected-0-Example`);
243
- });
244
-
245
- // @reactVersion <= 18.2
246
- // @reactVersion >= 18.0
247
- it('should mark sync render that throws', async () => {
248
- jest.spyOn(console, 'error').mockImplementation(() => {});
249
-
250
- class ErrorBoundary extends React.Component {
251
- state = {error: null};
252
- componentDidCatch(error) {
253
- this.setState({error});
254
- }
255
- render() {
256
- if (this.state.error) {
257
- return null;
258
- }
259
- return this.props.children;
260
- }
261
- }
262
-
263
- function ExampleThatThrows() {
264
- throw Error('Expected error');
265
- }
266
-
267
- utils.act(() => store.profilerStore.startProfiling());
268
- legacyRender(
269
- <ErrorBoundary>
270
- <ExampleThatThrows />
271
- </ErrorBoundary>,
272
- );
273
- utils.act(() => store.profilerStore.stopProfiling());
274
-
275
- expect(registeredMarks).toMatchInlineSnapshot(`
276
- [
277
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
278
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
279
- "--schedule-render-1",
280
- "--render-start-1",
281
- "--component-render-start-ErrorBoundary",
282
- "--component-render-stop",
283
- "--component-render-start-ExampleThatThrows",
284
- "--component-render-start-ExampleThatThrows",
285
- "--component-render-stop",
286
- "--error-ExampleThatThrows-mount-Expected error",
287
- "--render-stop",
288
- "--commit-start-1",
289
- "--react-version-<filtered-version>",
290
- "--profiler-version-1",
291
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
292
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
293
- "--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",
294
- "--layout-effects-start-1",
295
- "--schedule-state-update-1-ErrorBoundary",
296
- "--layout-effects-stop",
297
- "--commit-stop",
298
- "--render-start-1",
299
- "--component-render-start-ErrorBoundary",
300
- "--component-render-stop",
301
- "--render-stop",
302
- "--commit-start-1",
303
- "--react-version-<filtered-version>",
304
- "--profiler-version-1",
305
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
306
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
307
- "--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",
308
- "--commit-stop",
309
- ]
310
- `);
311
- });
312
- });
313
-
314
- describe('with createRoot', () => {
315
- let waitFor;
316
- let waitForAll;
317
- let waitForPaint;
318
- let assertLog;
319
-
320
- beforeEach(() => {
321
- const InternalTestUtils = require('internal-test-utils');
322
- waitFor = InternalTestUtils.waitFor;
323
- waitForAll = InternalTestUtils.waitForAll;
324
- waitForPaint = InternalTestUtils.waitForPaint;
325
- assertLog = InternalTestUtils.assertLog;
326
- });
327
-
328
- const {render: modernRender} = getModernRenderImplementation();
329
-
330
- it('should mark concurrent render without suspends or state updates', async () => {
331
- modernRender(<div />);
332
-
333
- expect(registeredMarks).toMatchInlineSnapshot(`
334
- [
335
- "--schedule-render-32",
336
- ]
337
- `);
338
-
339
- eraseRegisteredMarks();
340
-
341
- await waitForPaint([]);
342
-
343
- expect(registeredMarks).toMatchInlineSnapshot(`
344
- [
345
- "--render-start-32",
346
- "--render-stop",
347
- "--commit-start-32",
348
- "--react-version-<filtered-version>",
349
- "--profiler-version-1",
350
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
351
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
352
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
353
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
354
- "--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",
355
- "--layout-effects-start-32",
356
- "--layout-effects-stop",
357
- "--commit-stop",
358
- ]
359
- `);
360
- });
361
-
362
- it('should mark render yields', async () => {
363
- function Bar() {
364
- Scheduler.log('Bar');
365
- return null;
366
- }
367
-
368
- function Foo() {
369
- Scheduler.log('Foo');
370
- return <Bar />;
371
- }
372
-
373
- React.startTransition(() => {
374
- modernRender(<Foo />);
375
- });
376
-
377
- await waitFor(['Foo']);
378
-
379
- expect(registeredMarks).toMatchInlineSnapshot(`
380
- [
381
- "--schedule-render-128",
382
- "--render-start-128",
383
- "--component-render-start-Foo",
384
- "--component-render-stop",
385
- "--render-yield",
386
- ]
387
- `);
388
- });
389
-
390
- it('should mark concurrent render with suspense that resolves', async () => {
391
- let resolveFakePromise;
392
- const fakeSuspensePromise = new Promise(
393
- resolve => (resolveFakePromise = resolve),
394
- );
395
-
396
- function Example() {
397
- throw fakeSuspensePromise;
398
- }
399
-
400
- modernRender(
401
- <React.Suspense fallback={null}>
402
- <Example />
403
- </React.Suspense>,
404
- );
405
-
406
- expect(registeredMarks).toMatchInlineSnapshot(`
407
- [
408
- "--schedule-render-32",
409
- ]
410
- `);
411
-
412
- eraseRegisteredMarks();
413
-
414
- await waitForPaint([]);
415
-
416
- expect(registeredMarks).toMatchInlineSnapshot(`
417
- [
418
- "--render-start-32",
419
- "--component-render-start-Example",
420
- "--component-render-stop",
421
- "--suspense-suspend-0-Example-mount-32-",
422
- "--render-stop",
423
- "--commit-start-32",
424
- "--react-version-<filtered-version>",
425
- "--profiler-version-1",
426
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
427
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
428
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
429
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
430
- "--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",
431
- "--layout-effects-start-32",
432
- "--layout-effects-stop",
433
- "--commit-stop",
434
- ]
435
- `);
436
-
437
- eraseRegisteredMarks();
438
-
439
- await resolveFakePromise();
440
- expect(registeredMarks).toMatchInlineSnapshot(`
441
- [
442
- "--suspense-resolved-0-Example",
443
- ]
444
- `);
445
- });
446
-
447
- it('should mark concurrent render with suspense that rejects', async () => {
448
- let rejectFakePromise;
449
- const fakeSuspensePromise = new Promise(
450
- (_, reject) => (rejectFakePromise = reject),
451
- );
452
-
453
- function Example() {
454
- throw fakeSuspensePromise;
455
- }
456
-
457
- modernRender(
458
- <React.Suspense fallback={null}>
459
- <Example />
460
- </React.Suspense>,
461
- );
462
-
463
- expect(registeredMarks).toMatchInlineSnapshot(`
464
- [
465
- "--schedule-render-32",
466
- ]
467
- `);
468
-
469
- eraseRegisteredMarks();
470
-
471
- await waitForPaint([]);
472
-
473
- expect(registeredMarks).toMatchInlineSnapshot(`
474
- [
475
- "--render-start-32",
476
- "--component-render-start-Example",
477
- "--component-render-stop",
478
- "--suspense-suspend-0-Example-mount-32-",
479
- "--render-stop",
480
- "--commit-start-32",
481
- "--react-version-<filtered-version>",
482
- "--profiler-version-1",
483
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
484
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
485
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
486
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
487
- "--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",
488
- "--layout-effects-start-32",
489
- "--layout-effects-stop",
490
- "--commit-stop",
491
- ]
492
- `);
493
-
494
- eraseRegisteredMarks();
495
-
496
- await expect(() => {
497
- rejectFakePromise(new Error('error'));
498
- return fakeSuspensePromise;
499
- }).rejects.toThrow();
500
- expect(registeredMarks).toMatchInlineSnapshot(`
501
- [
502
- "--suspense-rejected-0-Example",
503
- ]
504
- `);
505
- });
506
-
507
- it('should mark cascading class component state updates', async () => {
508
- class Example extends React.Component {
509
- state = {didMount: false};
510
- componentDidMount() {
511
- this.setState({didMount: true});
512
- }
513
- render() {
514
- return null;
515
- }
516
- }
517
-
518
- modernRender(<Example />);
519
-
520
- expect(registeredMarks).toMatchInlineSnapshot(`
521
- [
522
- "--schedule-render-32",
523
- ]
524
- `);
525
-
526
- eraseRegisteredMarks();
527
-
528
- await waitForPaint([]);
529
-
530
- expect(registeredMarks).toMatchInlineSnapshot(`
531
- [
532
- "--render-start-32",
533
- "--component-render-start-Example",
534
- "--component-render-stop",
535
- "--render-stop",
536
- "--commit-start-32",
537
- "--react-version-<filtered-version>",
538
- "--profiler-version-1",
539
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
540
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
541
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
542
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
543
- "--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",
544
- "--layout-effects-start-32",
545
- "--schedule-state-update-2-Example",
546
- "--layout-effects-stop",
547
- "--render-start-2",
548
- "--component-render-start-Example",
549
- "--component-render-stop",
550
- "--render-stop",
551
- "--commit-start-2",
552
- "--react-version-<filtered-version>",
553
- "--profiler-version-1",
554
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
555
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
556
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
557
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
558
- "--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",
559
- "--commit-stop",
560
- "--commit-stop",
561
- ]
562
- `);
563
- });
564
-
565
- it('should mark cascading class component force updates', async () => {
566
- class Example extends React.Component {
567
- componentDidMount() {
568
- this.forceUpdate();
569
- }
570
- render() {
571
- return null;
572
- }
573
- }
574
-
575
- modernRender(<Example />);
576
-
577
- expect(registeredMarks).toMatchInlineSnapshot(`
578
- [
579
- "--schedule-render-32",
580
- ]
581
- `);
582
-
583
- eraseRegisteredMarks();
584
-
585
- await waitForPaint([]);
586
-
587
- expect(registeredMarks).toMatchInlineSnapshot(`
588
- [
589
- "--render-start-32",
590
- "--component-render-start-Example",
591
- "--component-render-stop",
592
- "--render-stop",
593
- "--commit-start-32",
594
- "--react-version-<filtered-version>",
595
- "--profiler-version-1",
596
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
597
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
598
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
599
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
600
- "--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",
601
- "--layout-effects-start-32",
602
- "--schedule-forced-update-2-Example",
603
- "--layout-effects-stop",
604
- "--render-start-2",
605
- "--component-render-start-Example",
606
- "--component-render-stop",
607
- "--render-stop",
608
- "--commit-start-2",
609
- "--react-version-<filtered-version>",
610
- "--profiler-version-1",
611
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
612
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
613
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
614
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
615
- "--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",
616
- "--commit-stop",
617
- "--commit-stop",
618
- ]
619
- `);
620
- });
621
-
622
- it('should mark render phase state updates for class component', async () => {
623
- class Example extends React.Component {
624
- state = {didRender: false};
625
- render() {
626
- if (this.state.didRender === false) {
627
- this.setState({didRender: true});
628
- }
629
- return null;
630
- }
631
- }
632
-
633
- modernRender(<Example />);
634
-
635
- expect(registeredMarks).toMatchInlineSnapshot(`
636
- [
637
- "--schedule-render-32",
638
- ]
639
- `);
640
-
641
- eraseRegisteredMarks();
642
-
643
- let errorMessage;
644
- jest.spyOn(console, 'error').mockImplementation(message => {
645
- errorMessage = message;
646
- });
647
-
648
- await waitForPaint([]);
649
-
650
- expect(console.error).toHaveBeenCalledTimes(1);
651
- expect(errorMessage).toContain(
652
- 'Cannot update during an existing state transition',
653
- );
654
-
655
- expect(registeredMarks).toMatchInlineSnapshot(`
656
- [
657
- "--render-start-32",
658
- "--component-render-start-Example",
659
- "--schedule-state-update-32-Example",
660
- "--component-render-stop",
661
- "--render-stop",
662
- "--commit-start-32",
663
- "--react-version-<filtered-version>",
664
- "--profiler-version-1",
665
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
666
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
667
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
668
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
669
- "--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",
670
- "--layout-effects-start-32",
671
- "--layout-effects-stop",
672
- "--commit-stop",
673
- ]
674
- `);
675
- });
676
-
677
- it('should mark render phase force updates for class component', async () => {
678
- let forced = false;
679
- class Example extends React.Component {
680
- render() {
681
- if (!forced) {
682
- forced = true;
683
- this.forceUpdate();
684
- }
685
- return null;
686
- }
687
- }
688
-
689
- modernRender(<Example />);
690
-
691
- expect(registeredMarks).toMatchInlineSnapshot(`
692
- [
693
- "--schedule-render-32",
694
- ]
695
- `);
696
-
697
- eraseRegisteredMarks();
698
-
699
- let errorMessage;
700
- jest.spyOn(console, 'error').mockImplementation(message => {
701
- errorMessage = message;
702
- });
703
-
704
- await waitForPaint([]);
705
-
706
- expect(console.error).toHaveBeenCalledTimes(1);
707
- expect(errorMessage).toContain(
708
- 'Cannot update during an existing state transition',
709
- );
710
-
711
- expect(registeredMarks).toMatchInlineSnapshot(`
712
- [
713
- "--render-start-32",
714
- "--component-render-start-Example",
715
- "--schedule-forced-update-32-Example",
716
- "--component-render-stop",
717
- "--render-stop",
718
- "--commit-start-32",
719
- "--react-version-<filtered-version>",
720
- "--profiler-version-1",
721
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
722
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
723
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
724
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
725
- "--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",
726
- "--layout-effects-start-32",
727
- "--layout-effects-stop",
728
- "--commit-stop",
729
- ]
730
- `);
731
- });
732
-
733
- it('should mark cascading layout updates', async () => {
734
- function Example() {
735
- const [didMount, setDidMount] = React.useState(false);
736
- React.useLayoutEffect(() => {
737
- setDidMount(true);
738
- }, []);
739
- return didMount;
740
- }
741
-
742
- modernRender(<Example />);
743
-
744
- expect(registeredMarks).toMatchInlineSnapshot(`
745
- [
746
- "--schedule-render-32",
747
- ]
748
- `);
749
-
750
- eraseRegisteredMarks();
751
-
752
- await waitForPaint([]);
753
-
754
- expect(registeredMarks).toMatchInlineSnapshot(`
755
- [
756
- "--render-start-32",
757
- "--component-render-start-Example",
758
- "--component-render-stop",
759
- "--render-stop",
760
- "--commit-start-32",
761
- "--react-version-<filtered-version>",
762
- "--profiler-version-1",
763
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
764
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
765
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
766
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
767
- "--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",
768
- "--layout-effects-start-32",
769
- "--component-layout-effect-mount-start-Example",
770
- "--schedule-state-update-2-Example",
771
- "--component-layout-effect-mount-stop",
772
- "--layout-effects-stop",
773
- "--render-start-2",
774
- "--component-render-start-Example",
775
- "--component-render-stop",
776
- "--render-stop",
777
- "--commit-start-2",
778
- "--react-version-<filtered-version>",
779
- "--profiler-version-1",
780
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
781
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
782
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
783
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
784
- "--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",
785
- "--commit-stop",
786
- "--commit-stop",
787
- ]
788
- `);
789
- });
790
-
791
- it('should mark cascading passive updates', async () => {
792
- function Example() {
793
- const [didMount, setDidMount] = React.useState(false);
794
- React.useEffect(() => {
795
- setDidMount(true);
796
- }, []);
797
- return didMount;
798
- }
799
-
800
- modernRender(<Example />);
801
-
802
- await waitForAll([]);
803
-
804
- expect(registeredMarks).toMatchInlineSnapshot(`
805
- [
806
- "--schedule-render-32",
807
- "--render-start-32",
808
- "--component-render-start-Example",
809
- "--component-render-stop",
810
- "--render-stop",
811
- "--commit-start-32",
812
- "--react-version-<filtered-version>",
813
- "--profiler-version-1",
814
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
815
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
816
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
817
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
818
- "--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",
819
- "--layout-effects-start-32",
820
- "--layout-effects-stop",
821
- "--commit-stop",
822
- "--passive-effects-start-32",
823
- "--component-passive-effect-mount-start-Example",
824
- "--schedule-state-update-32-Example",
825
- "--component-passive-effect-mount-stop",
826
- "--passive-effects-stop",
827
- "--render-start-32",
828
- "--component-render-start-Example",
829
- "--component-render-stop",
830
- "--render-stop",
831
- "--commit-start-32",
832
- "--react-version-<filtered-version>",
833
- "--profiler-version-1",
834
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
835
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
836
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
837
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
838
- "--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",
839
- "--commit-stop",
840
- ]
841
- `);
842
- });
843
-
844
- it('should mark render phase updates', async () => {
845
- function Example() {
846
- const [didRender, setDidRender] = React.useState(false);
847
- if (!didRender) {
848
- setDidRender(true);
849
- }
850
- return didRender;
851
- }
852
-
853
- modernRender(<Example />);
854
-
855
- await waitForAll([]);
856
-
857
- expect(registeredMarks).toMatchInlineSnapshot(`
858
- [
859
- "--schedule-render-32",
860
- "--render-start-32",
861
- "--component-render-start-Example",
862
- "--schedule-state-update-32-Example",
863
- "--component-render-stop",
864
- "--render-stop",
865
- "--commit-start-32",
866
- "--react-version-<filtered-version>",
867
- "--profiler-version-1",
868
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
869
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
870
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
871
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
872
- "--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",
873
- "--layout-effects-start-32",
874
- "--layout-effects-stop",
875
- "--commit-stop",
876
- ]
877
- `);
878
- });
879
-
880
- it('should mark concurrent render that throws', async () => {
881
- jest.spyOn(console, 'error').mockImplementation(() => {});
882
-
883
- class ErrorBoundary extends React.Component {
884
- state = {error: null};
885
- componentDidCatch(error) {
886
- this.setState({error});
887
- }
888
- render() {
889
- if (this.state.error) {
890
- return null;
891
- }
892
- return this.props.children;
893
- }
894
- }
895
-
896
- function ExampleThatThrows() {
897
- // eslint-disable-next-line no-throw-literal
898
- throw 'Expected error';
899
- }
900
-
901
- modernRender(
902
- <ErrorBoundary>
903
- <ExampleThatThrows />
904
- </ErrorBoundary>,
905
- );
906
-
907
- expect(registeredMarks).toMatchInlineSnapshot(`
908
- [
909
- "--schedule-render-32",
910
- ]
911
- `);
912
-
913
- eraseRegisteredMarks();
914
-
915
- await waitForPaint([]);
916
-
917
- expect(registeredMarks).toMatchInlineSnapshot(`
918
- [
919
- "--render-start-32",
920
- "--component-render-start-ErrorBoundary",
921
- "--component-render-stop",
922
- "--component-render-start-ExampleThatThrows",
923
- "--component-render-stop",
924
- "--error-ExampleThatThrows-mount-Expected error",
925
- "--render-stop",
926
- "--render-start-32",
927
- "--component-render-start-ErrorBoundary",
928
- "--component-render-stop",
929
- "--component-render-start-ExampleThatThrows",
930
- "--component-render-stop",
931
- "--error-ExampleThatThrows-mount-Expected error",
932
- "--render-stop",
933
- "--commit-start-32",
934
- "--react-version-<filtered-version>",
935
- "--profiler-version-1",
936
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
937
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
938
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
939
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
940
- "--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",
941
- "--layout-effects-start-32",
942
- "--schedule-state-update-2-ErrorBoundary",
943
- "--layout-effects-stop",
944
- "--render-start-2",
945
- "--component-render-start-ErrorBoundary",
946
- "--component-render-stop",
947
- "--render-stop",
948
- "--commit-start-2",
949
- "--react-version-<filtered-version>",
950
- "--profiler-version-1",
951
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
952
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
953
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
954
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
955
- "--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",
956
- "--commit-stop",
957
- "--commit-stop",
958
- ]
959
- `);
960
- });
961
-
962
- it('should mark passive and layout effects', async () => {
963
- function ComponentWithEffects() {
964
- React.useLayoutEffect(() => {
965
- Scheduler.log('layout 1 mount');
966
- return () => {
967
- Scheduler.log('layout 1 unmount');
968
- };
969
- }, []);
970
-
971
- React.useEffect(() => {
972
- Scheduler.log('passive 1 mount');
973
- return () => {
974
- Scheduler.log('passive 1 unmount');
975
- };
976
- }, []);
977
-
978
- React.useLayoutEffect(() => {
979
- Scheduler.log('layout 2 mount');
980
- return () => {
981
- Scheduler.log('layout 2 unmount');
982
- };
983
- }, []);
984
-
985
- React.useEffect(() => {
986
- Scheduler.log('passive 2 mount');
987
- return () => {
988
- Scheduler.log('passive 2 unmount');
989
- };
990
- }, []);
991
-
992
- React.useEffect(() => {
993
- Scheduler.log('passive 3 mount');
994
- return () => {
995
- Scheduler.log('passive 3 unmount');
996
- };
997
- }, []);
998
-
999
- return null;
1000
- }
1001
-
1002
- const unmount = modernRender(<ComponentWithEffects />);
1003
-
1004
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
1005
-
1006
- expect(registeredMarks).toMatchInlineSnapshot(`
1007
- [
1008
- "--schedule-render-32",
1009
- "--render-start-32",
1010
- "--component-render-start-ComponentWithEffects",
1011
- "--component-render-stop",
1012
- "--render-stop",
1013
- "--commit-start-32",
1014
- "--react-version-<filtered-version>",
1015
- "--profiler-version-1",
1016
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1017
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1018
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1019
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1020
- "--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",
1021
- "--layout-effects-start-32",
1022
- "--component-layout-effect-mount-start-ComponentWithEffects",
1023
- "--component-layout-effect-mount-stop",
1024
- "--component-layout-effect-mount-start-ComponentWithEffects",
1025
- "--component-layout-effect-mount-stop",
1026
- "--layout-effects-stop",
1027
- "--commit-stop",
1028
- ]
1029
- `);
1030
-
1031
- eraseRegisteredMarks();
1032
-
1033
- await waitForAll([
1034
- 'passive 1 mount',
1035
- 'passive 2 mount',
1036
- 'passive 3 mount',
1037
- ]);
1038
-
1039
- expect(registeredMarks).toMatchInlineSnapshot(`
1040
- [
1041
- "--passive-effects-start-32",
1042
- "--component-passive-effect-mount-start-ComponentWithEffects",
1043
- "--component-passive-effect-mount-stop",
1044
- "--component-passive-effect-mount-start-ComponentWithEffects",
1045
- "--component-passive-effect-mount-stop",
1046
- "--component-passive-effect-mount-start-ComponentWithEffects",
1047
- "--component-passive-effect-mount-stop",
1048
- "--passive-effects-stop",
1049
- ]
1050
- `);
1051
-
1052
- eraseRegisteredMarks();
1053
-
1054
- await waitForAll([]);
1055
-
1056
- unmount();
1057
-
1058
- assertLog([
1059
- 'layout 1 unmount',
1060
- 'layout 2 unmount',
1061
- 'passive 1 unmount',
1062
- 'passive 2 unmount',
1063
- 'passive 3 unmount',
1064
- ]);
1065
-
1066
- expect(registeredMarks).toMatchInlineSnapshot(`
1067
- [
1068
- "--schedule-render-2",
1069
- "--render-start-2",
1070
- "--render-stop",
1071
- "--commit-start-2",
1072
- "--react-version-<filtered-version>",
1073
- "--profiler-version-1",
1074
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1075
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1076
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1077
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1078
- "--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",
1079
- "--component-layout-effect-unmount-start-ComponentWithEffects",
1080
- "--component-layout-effect-unmount-stop",
1081
- "--component-layout-effect-unmount-start-ComponentWithEffects",
1082
- "--component-layout-effect-unmount-stop",
1083
- "--layout-effects-start-2",
1084
- "--layout-effects-stop",
1085
- "--passive-effects-start-2",
1086
- "--component-passive-effect-unmount-start-ComponentWithEffects",
1087
- "--component-passive-effect-unmount-stop",
1088
- "--component-passive-effect-unmount-start-ComponentWithEffects",
1089
- "--component-passive-effect-unmount-stop",
1090
- "--component-passive-effect-unmount-start-ComponentWithEffects",
1091
- "--component-passive-effect-unmount-stop",
1092
- "--passive-effects-stop",
1093
- "--commit-stop",
1094
- ]
1095
- `);
1096
- });
1097
- });
1098
-
1099
- describe('lane labels', () => {
1100
- describe('with legacy render', () => {
1101
- const {render: legacyRender} = getLegacyRenderImplementation();
1102
-
1103
- // @reactVersion <= 18.2
1104
- // @reactVersion >= 18.0
1105
- it('regression test SyncLane', () => {
1106
- utils.act(() => store.profilerStore.startProfiling());
1107
- legacyRender(<div />);
1108
- utils.act(() => store.profilerStore.stopProfiling());
1109
-
1110
- expect(registeredMarks).toMatchInlineSnapshot(`
1111
- [
1112
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1113
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1114
- "--schedule-render-1",
1115
- "--render-start-1",
1116
- "--render-stop",
1117
- "--commit-start-1",
1118
- "--react-version-<filtered-version>",
1119
- "--profiler-version-1",
1120
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1121
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1122
- "--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",
1123
- "--layout-effects-start-1",
1124
- "--layout-effects-stop",
1125
- "--commit-stop",
1126
- ]
1127
- `);
1128
- });
1129
- });
1130
-
1131
- describe('with createRoot()', () => {
1132
- let waitForAll;
1133
-
1134
- beforeEach(() => {
1135
- const InternalTestUtils = require('internal-test-utils');
1136
- waitForAll = InternalTestUtils.waitForAll;
1137
- });
1138
-
1139
- const {render: modernRender} = getModernRenderImplementation();
1140
-
1141
- it('regression test DefaultLane', () => {
1142
- modernRender(<div />);
1143
- expect(registeredMarks).toMatchInlineSnapshot(`
1144
- [
1145
- "--schedule-render-32",
1146
- ]
1147
- `);
1148
- });
1149
-
1150
- it('regression test InputDiscreteLane', async () => {
1151
- const targetRef = React.createRef(null);
1152
-
1153
- function App() {
1154
- const [count, setCount] = React.useState(0);
1155
- const handleClick = () => {
1156
- setCount(count + 1);
1157
- };
1158
- return <button ref={targetRef} onClick={handleClick} />;
1159
- }
1160
-
1161
- modernRender(<App />);
1162
- await waitForAll([]);
1163
-
1164
- eraseRegisteredMarks();
1165
-
1166
- targetRef.current.click();
1167
-
1168
- // Wait a frame, for React to process the "click" update.
1169
- await Promise.resolve();
1170
-
1171
- expect(registeredMarks).toMatchInlineSnapshot(`
1172
- [
1173
- "--schedule-state-update-2-App",
1174
- "--render-start-2",
1175
- "--component-render-start-App",
1176
- "--component-render-stop",
1177
- "--render-stop",
1178
- "--commit-start-2",
1179
- "--react-version-<filtered-version>",
1180
- "--profiler-version-1",
1181
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1182
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1183
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1184
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1185
- "--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",
1186
- "--layout-effects-start-2",
1187
- "--layout-effects-stop",
1188
- "--commit-stop",
1189
- ]
1190
- `);
1191
- });
1192
-
1193
- it('regression test InputContinuousLane', async () => {
1194
- const targetRef = React.createRef(null);
1195
-
1196
- function App() {
1197
- const [count, setCount] = React.useState(0);
1198
- const handleMouseOver = () => setCount(count + 1);
1199
- return <div ref={targetRef} onMouseOver={handleMouseOver} />;
1200
- }
1201
-
1202
- modernRender(<App />);
1203
- await waitForAll([]);
1204
-
1205
- eraseRegisteredMarks();
1206
-
1207
- const event = document.createEvent('MouseEvents');
1208
- event.initEvent('mouseover', true, true);
1209
- dispatchAndSetCurrentEvent(targetRef.current, event);
1210
-
1211
- await waitForAll([]);
1212
-
1213
- expect(registeredMarks).toMatchInlineSnapshot(`
1214
- [
1215
- "--schedule-state-update-8-App",
1216
- "--render-start-8",
1217
- "--component-render-start-App",
1218
- "--component-render-stop",
1219
- "--render-stop",
1220
- "--commit-start-8",
1221
- "--react-version-<filtered-version>",
1222
- "--profiler-version-1",
1223
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1224
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1225
- "--react-internal-module-start- at filtered (<anonymous>:0:0)",
1226
- "--react-internal-module-stop- at filtered (<anonymous>:1:1)",
1227
- "--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",
1228
- "--layout-effects-start-8",
1229
- "--layout-effects-stop",
1230
- "--commit-stop",
1231
- ]
1232
- `);
1233
- });
1234
- });
1235
- });
1236
- });
1237
-
1238
- describe('DevTools hook (in memory)', () => {
1239
- let getBatchOfWork;
1240
- let stopProfilingAndGetTimelineData;
1241
-
1242
- beforeEach(() => {
1243
- getBatchOfWork = index => {
1244
- const timelineData = stopProfilingAndGetTimelineData();
1245
- if (timelineData) {
1246
- if (timelineData.batchUIDToMeasuresMap.size > index) {
1247
- return Array.from(timelineData.batchUIDToMeasuresMap.values())[
1248
- index
1249
- ];
1250
- }
1251
- }
1252
-
1253
- return null;
1254
- };
1255
-
1256
- stopProfilingAndGetTimelineData = () => {
1257
- utils.act(() => store.profilerStore.stopProfiling());
1258
-
1259
- const timelineData = store.profilerStore.profilingData?.timelineData;
1260
-
1261
- if (timelineData) {
1262
- expect(timelineData).toHaveLength(1);
1263
-
1264
- // normalize the location for component stack source
1265
- // for snapshot testing
1266
- timelineData.forEach(data => {
1267
- data.schedulingEvents.forEach(event => {
1268
- if (event.componentStack) {
1269
- event.componentStack = normalizeCodeLocInfo(
1270
- event.componentStack,
1271
- );
1272
- }
1273
- });
1274
- });
1275
-
1276
- return timelineData[0];
1277
- } else {
1278
- return null;
1279
- }
1280
- };
1281
- });
1282
-
1283
- describe('when profiling', () => {
1284
- describe('with legacy render', () => {
1285
- const {render: legacyRender} = getLegacyRenderImplementation();
1286
-
1287
- beforeEach(() => {
1288
- utils.act(() => store.profilerStore.startProfiling());
1289
- });
1290
-
1291
- // @reactVersion <= 18.2
1292
- // @reactVersion >= 18.0
1293
- it('should mark sync render without suspends or state updates', () => {
1294
- legacyRender(<div />);
1295
-
1296
- const timelineData = stopProfilingAndGetTimelineData();
1297
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1298
- [
1299
- {
1300
- "lanes": "0b0000000000000000000000000000001",
1301
- "timestamp": 10,
1302
- "type": "schedule-render",
1303
- "warning": null,
1304
- },
1305
- ]
1306
- `);
1307
- });
1308
-
1309
- // @reactVersion <= 18.2
1310
- // @reactVersion >= 18.0
1311
- it('should mark sync render that throws', async () => {
1312
- jest.spyOn(console, 'error').mockImplementation(() => {});
1313
-
1314
- class ErrorBoundary extends React.Component {
1315
- state = {error: null};
1316
- componentDidCatch(error) {
1317
- this.setState({error});
1318
- }
1319
- render() {
1320
- Scheduler.unstable_advanceTime(10);
1321
- if (this.state.error) {
1322
- Scheduler.unstable_yieldValue('ErrorBoundary fallback');
1323
- return null;
1324
- }
1325
- Scheduler.unstable_yieldValue('ErrorBoundary render');
1326
- return this.props.children;
1327
- }
1328
- }
1329
-
1330
- function ExampleThatThrows() {
1331
- Scheduler.unstable_yieldValue('ExampleThatThrows');
1332
- throw Error('Expected error');
1333
- }
1334
-
1335
- legacyRender(
1336
- <ErrorBoundary>
1337
- <ExampleThatThrows />
1338
- </ErrorBoundary>,
1339
- );
1340
-
1341
- expect(Scheduler.unstable_clearYields()).toEqual([
1342
- 'ErrorBoundary render',
1343
- 'ExampleThatThrows',
1344
- 'ExampleThatThrows',
1345
- 'ErrorBoundary fallback',
1346
- ]);
1347
-
1348
- const timelineData = stopProfilingAndGetTimelineData();
1349
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1350
- [
1351
- {
1352
- "componentName": "ErrorBoundary",
1353
- "duration": 10,
1354
- "timestamp": 10,
1355
- "type": "render",
1356
- "warning": null,
1357
- },
1358
- {
1359
- "componentName": "ExampleThatThrows",
1360
- "duration": 0,
1361
- "timestamp": 20,
1362
- "type": "render",
1363
- "warning": null,
1364
- },
1365
- {
1366
- "componentName": "ErrorBoundary",
1367
- "duration": 10,
1368
- "timestamp": 20,
1369
- "type": "render",
1370
- "warning": null,
1371
- },
1372
- ]
1373
- `);
1374
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1375
- [
1376
- {
1377
- "lanes": "0b0000000000000000000000000000001",
1378
- "timestamp": 10,
1379
- "type": "schedule-render",
1380
- "warning": null,
1381
- },
1382
- {
1383
- "componentName": "ErrorBoundary",
1384
- "componentStack": "
1385
- in ErrorBoundary (at **)",
1386
- "lanes": "0b0000000000000000000000000000001",
1387
- "timestamp": 20,
1388
- "type": "schedule-state-update",
1389
- "warning": null,
1390
- },
1391
- ]
1392
- `);
1393
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
1394
- [
1395
- {
1396
- "componentName": "ExampleThatThrows",
1397
- "message": "Expected error",
1398
- "phase": "mount",
1399
- "timestamp": 20,
1400
- "type": "thrown-error",
1401
- },
1402
- ]
1403
- `);
1404
- });
1405
-
1406
- // @reactVersion <= 18.2
1407
- // @reactVersion >= 18.0
1408
- it('should mark sync render with suspense that resolves', async () => {
1409
- let resolveFn;
1410
- let resolved = false;
1411
- const suspensePromise = new Promise(resolve => {
1412
- resolveFn = () => {
1413
- resolved = true;
1414
- resolve();
1415
- };
1416
- });
1417
-
1418
- function Example() {
1419
- Scheduler.unstable_yieldValue(resolved ? 'resolved' : 'suspended');
1420
- if (!resolved) {
1421
- throw suspensePromise;
1422
- }
1423
- return null;
1424
- }
1425
-
1426
- legacyRender(
1427
- <React.Suspense fallback={null}>
1428
- <Example />
1429
- </React.Suspense>,
1430
- );
1431
-
1432
- expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
1433
-
1434
- Scheduler.unstable_advanceTime(10);
1435
- resolveFn();
1436
- await suspensePromise;
1437
-
1438
- await Scheduler.unstable_flushAllWithoutAsserting();
1439
- expect(Scheduler.unstable_clearYields()).toEqual(['resolved']);
1440
-
1441
- const timelineData = stopProfilingAndGetTimelineData();
1442
-
1443
- // Verify the Suspense event and duration was recorded.
1444
- expect(timelineData.suspenseEvents).toHaveLength(1);
1445
- const suspenseEvent = timelineData.suspenseEvents[0];
1446
- expect(suspenseEvent).toMatchInlineSnapshot(`
1447
- {
1448
- "componentName": "Example",
1449
- "depth": 0,
1450
- "duration": 0,
1451
- "id": "0",
1452
- "phase": "mount",
1453
- "promiseName": "",
1454
- "resolution": "unresolved",
1455
- "timestamp": 10,
1456
- "type": "suspense",
1457
- "warning": null,
1458
- }
1459
- `);
1460
-
1461
- // There should be two batches of renders: Suspeneded and resolved.
1462
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1463
- expect(timelineData.componentMeasures).toHaveLength(2);
1464
- });
1465
-
1466
- // @reactVersion = 18.2
1467
- it('should mark sync render with suspense that rejects', async () => {
1468
- let rejectFn;
1469
- let rejected = false;
1470
- const suspensePromise = new Promise((resolve, reject) => {
1471
- rejectFn = () => {
1472
- rejected = true;
1473
- reject(new Error('error'));
1474
- };
1475
- });
1476
-
1477
- function Example() {
1478
- Scheduler.unstable_yieldValue(rejected ? 'rejected' : 'suspended');
1479
- if (!rejected) {
1480
- throw suspensePromise;
1481
- }
1482
- return null;
1483
- }
1484
-
1485
- legacyRender(
1486
- <React.Suspense fallback={null}>
1487
- <Example />
1488
- </React.Suspense>,
1489
- );
1490
-
1491
- expect(Scheduler.unstable_clearYields()).toEqual(['suspended']);
1492
-
1493
- Scheduler.unstable_advanceTime(10);
1494
- rejectFn();
1495
- await expect(suspensePromise).rejects.toThrow();
1496
-
1497
- expect(Scheduler.unstable_clearYields()).toEqual(['rejected']);
1498
-
1499
- const timelineData = stopProfilingAndGetTimelineData();
1500
-
1501
- // Verify the Suspense event and duration was recorded.
1502
- expect(timelineData.suspenseEvents).toHaveLength(1);
1503
- const suspenseEvent = timelineData.suspenseEvents[0];
1504
- expect(suspenseEvent).toMatchInlineSnapshot(`
1505
- {
1506
- "componentName": "Example",
1507
- "depth": 0,
1508
- "duration": 0,
1509
- "id": "0",
1510
- "phase": "mount",
1511
- "promiseName": "",
1512
- "resolution": "unresolved",
1513
- "timestamp": 10,
1514
- "type": "suspense",
1515
- "warning": null,
1516
- }
1517
- `);
1518
-
1519
- // There should be two batches of renders: Suspeneded and resolved.
1520
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1521
- expect(timelineData.componentMeasures).toHaveLength(2);
1522
- });
1523
- });
1524
-
1525
- describe('with createRoot()', () => {
1526
- let waitFor;
1527
- let waitForAll;
1528
- let waitForPaint;
1529
- let assertLog;
1530
-
1531
- beforeEach(() => {
1532
- const InternalTestUtils = require('internal-test-utils');
1533
- waitFor = InternalTestUtils.waitFor;
1534
- waitForAll = InternalTestUtils.waitForAll;
1535
- waitForPaint = InternalTestUtils.waitForPaint;
1536
- assertLog = InternalTestUtils.assertLog;
1537
- });
1538
-
1539
- const {render: modernRender} = getModernRenderImplementation();
1540
-
1541
- beforeEach(() => {
1542
- utils.act(() => store.profilerStore.startProfiling());
1543
- });
1544
-
1545
- it('should mark concurrent render without suspends or state updates', () => {
1546
- utils.act(() => modernRender(<div />));
1547
-
1548
- const timelineData = stopProfilingAndGetTimelineData();
1549
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1550
- [
1551
- {
1552
- "lanes": "0b0000000000000000000000000100000",
1553
- "timestamp": 10,
1554
- "type": "schedule-render",
1555
- "warning": null,
1556
- },
1557
- ]
1558
- `);
1559
- });
1560
-
1561
- it('should mark concurrent render without suspends with state updates', () => {
1562
- let updaterFn;
1563
-
1564
- function Example() {
1565
- const setHigh = React.useState(0)[1];
1566
- const setLow = React.useState(0)[1];
1567
-
1568
- updaterFn = () => {
1569
- React.startTransition(() => {
1570
- setLow(prevLow => prevLow + 1);
1571
- });
1572
- setHigh(prevHigh => prevHigh + 1);
1573
- };
1574
-
1575
- Scheduler.unstable_advanceTime(10);
1576
-
1577
- return null;
1578
- }
1579
-
1580
- utils.act(() => modernRender(<Example />));
1581
- utils.act(() => store.profilerStore.stopProfiling());
1582
- utils.act(() => store.profilerStore.startProfiling());
1583
- utils.act(updaterFn);
1584
-
1585
- const timelineData = stopProfilingAndGetTimelineData();
1586
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1587
- [
1588
- {
1589
- "componentName": "Example",
1590
- "componentStack": "
1591
- in Example (at **)",
1592
- "lanes": "0b0000000000000000000000010000000",
1593
- "timestamp": 10,
1594
- "type": "schedule-state-update",
1595
- "warning": null,
1596
- },
1597
- {
1598
- "componentName": "Example",
1599
- "componentStack": "
1600
- in Example (at **)",
1601
- "lanes": "0b0000000000000000000000000100000",
1602
- "timestamp": 10,
1603
- "type": "schedule-state-update",
1604
- "warning": null,
1605
- },
1606
- ]
1607
- `);
1608
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1609
- [
1610
- {
1611
- "componentName": "Example",
1612
- "duration": 0,
1613
- "timestamp": 10,
1614
- "type": "render",
1615
- "warning": null,
1616
- },
1617
- {
1618
- "componentName": "Example",
1619
- "duration": 10,
1620
- "timestamp": 10,
1621
- "type": "render",
1622
- "warning": null,
1623
- },
1624
- ]
1625
- `);
1626
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1627
- });
1628
-
1629
- it('should mark render yields', async () => {
1630
- function Bar() {
1631
- Scheduler.log('Bar');
1632
- return null;
1633
- }
1634
-
1635
- function Foo() {
1636
- Scheduler.log('Foo');
1637
- return <Bar />;
1638
- }
1639
-
1640
- React.startTransition(() => {
1641
- modernRender(<Foo />);
1642
- });
1643
-
1644
- // Do one step of work.
1645
- await waitFor(['Foo']);
1646
-
1647
- // Finish flushing so React commits;
1648
- // Unless we do this, the ProfilerStore won't collect Profiling data.
1649
- await waitForAll(['Bar']);
1650
-
1651
- // Since we yielded, the batch should report two separate "render" chunks.
1652
- const batch = getBatchOfWork(0);
1653
- expect(batch.filter(({type}) => type === 'render')).toHaveLength(2);
1654
- });
1655
-
1656
- it('should mark concurrent render with suspense that resolves', async () => {
1657
- let resolveFn;
1658
- let resolved = false;
1659
- const suspensePromise = new Promise(resolve => {
1660
- resolveFn = () => {
1661
- resolved = true;
1662
- resolve();
1663
- };
1664
- });
1665
-
1666
- function Example() {
1667
- Scheduler.log(resolved ? 'resolved' : 'suspended');
1668
- if (!resolved) {
1669
- throw suspensePromise;
1670
- }
1671
- return null;
1672
- }
1673
-
1674
- modernRender(
1675
- <React.Suspense fallback={null}>
1676
- <Example />
1677
- </React.Suspense>,
1678
- );
1679
-
1680
- await waitForAll([
1681
- 'suspended',
1682
- // pre-warming
1683
- 'suspended',
1684
- ]);
1685
-
1686
- Scheduler.unstable_advanceTime(10);
1687
- resolveFn();
1688
- await suspensePromise;
1689
-
1690
- await waitForAll(['resolved']);
1691
-
1692
- const timelineData = stopProfilingAndGetTimelineData();
1693
-
1694
- // Verify the Suspense event and duration was recorded.
1695
- expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
1696
- [
1697
- {
1698
- "componentName": "Example",
1699
- "depth": 0,
1700
- "duration": 10,
1701
- "id": "0",
1702
- "phase": "mount",
1703
- "promiseName": "",
1704
- "resolution": "resolved",
1705
- "timestamp": 10,
1706
- "type": "suspense",
1707
- "warning": null,
1708
- },
1709
- {
1710
- "componentName": "Example",
1711
- "depth": 0,
1712
- "duration": 10,
1713
- "id": "0",
1714
- "phase": "mount",
1715
- "promiseName": "",
1716
- "resolution": "resolved",
1717
- "timestamp": 10,
1718
- "type": "suspense",
1719
- "warning": null,
1720
- },
1721
- ]
1722
- `);
1723
-
1724
- // There should be two batches of renders: Suspeneded and resolved.
1725
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1726
- // An additional measure with pre-warming
1727
- expect(timelineData.componentMeasures).toHaveLength(3);
1728
- });
1729
-
1730
- it('should mark concurrent render with suspense that rejects', async () => {
1731
- let rejectFn;
1732
- let rejected = false;
1733
- const suspensePromise = new Promise((resolve, reject) => {
1734
- rejectFn = () => {
1735
- rejected = true;
1736
- reject(new Error('error'));
1737
- };
1738
- });
1739
-
1740
- function Example() {
1741
- Scheduler.log(rejected ? 'rejected' : 'suspended');
1742
- if (!rejected) {
1743
- throw suspensePromise;
1744
- }
1745
- return null;
1746
- }
1747
-
1748
- modernRender(
1749
- <React.Suspense fallback={null}>
1750
- <Example />
1751
- </React.Suspense>,
1752
- );
1753
-
1754
- await waitForAll(['suspended', 'suspended']);
1755
-
1756
- Scheduler.unstable_advanceTime(10);
1757
- rejectFn();
1758
- await expect(suspensePromise).rejects.toThrow();
1759
-
1760
- await waitForAll(['rejected']);
1761
-
1762
- const timelineData = stopProfilingAndGetTimelineData();
1763
-
1764
- // Verify the Suspense event and duration was recorded.
1765
- expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
1766
- [
1767
- {
1768
- "componentName": "Example",
1769
- "depth": 0,
1770
- "duration": 10,
1771
- "id": "0",
1772
- "phase": "mount",
1773
- "promiseName": "",
1774
- "resolution": "rejected",
1775
- "timestamp": 10,
1776
- "type": "suspense",
1777
- "warning": null,
1778
- },
1779
- {
1780
- "componentName": "Example",
1781
- "depth": 0,
1782
- "duration": 10,
1783
- "id": "0",
1784
- "phase": "mount",
1785
- "promiseName": "",
1786
- "resolution": "rejected",
1787
- "timestamp": 10,
1788
- "type": "suspense",
1789
- "warning": null,
1790
- },
1791
- ]
1792
- `);
1793
-
1794
- // There should be two batches of renders: Suspeneded and resolved.
1795
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1796
- // An additional measure with pre-warming
1797
- expect(timelineData.componentMeasures).toHaveLength(3);
1798
- });
1799
-
1800
- it('should mark cascading class component state updates', async () => {
1801
- class Example extends React.Component {
1802
- state = {didMount: false};
1803
- componentDidMount() {
1804
- this.setState({didMount: true});
1805
- }
1806
- render() {
1807
- Scheduler.unstable_advanceTime(10);
1808
- Scheduler.log(this.state.didMount ? 'update' : 'mount');
1809
- return null;
1810
- }
1811
- }
1812
-
1813
- modernRender(<Example />);
1814
-
1815
- await waitForPaint(['mount', 'update']);
1816
-
1817
- const timelineData = stopProfilingAndGetTimelineData();
1818
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1819
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1820
- [
1821
- {
1822
- "componentName": "Example",
1823
- "duration": 10,
1824
- "timestamp": 10,
1825
- "type": "render",
1826
- "warning": null,
1827
- },
1828
- {
1829
- "componentName": "Example",
1830
- "duration": 10,
1831
- "timestamp": 20,
1832
- "type": "render",
1833
- "warning": null,
1834
- },
1835
- ]
1836
- `);
1837
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1838
- [
1839
- {
1840
- "lanes": "0b0000000000000000000000000100000",
1841
- "timestamp": 10,
1842
- "type": "schedule-render",
1843
- "warning": null,
1844
- },
1845
- {
1846
- "componentName": "Example",
1847
- "componentStack": "
1848
- in Example (at **)",
1849
- "lanes": "0b0000000000000000000000000000010",
1850
- "timestamp": 20,
1851
- "type": "schedule-state-update",
1852
- "warning": null,
1853
- },
1854
- ]
1855
- `);
1856
- });
1857
-
1858
- it('should mark cascading class component force updates', async () => {
1859
- let forced = false;
1860
- class Example extends React.Component {
1861
- componentDidMount() {
1862
- forced = true;
1863
- this.forceUpdate();
1864
- }
1865
- render() {
1866
- Scheduler.unstable_advanceTime(10);
1867
- Scheduler.log(forced ? 'force update' : 'mount');
1868
- return null;
1869
- }
1870
- }
1871
-
1872
- modernRender(<Example />);
1873
-
1874
- await waitForPaint(['mount', 'force update']);
1875
-
1876
- const timelineData = stopProfilingAndGetTimelineData();
1877
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1878
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1879
- [
1880
- {
1881
- "componentName": "Example",
1882
- "duration": 10,
1883
- "timestamp": 10,
1884
- "type": "render",
1885
- "warning": null,
1886
- },
1887
- {
1888
- "componentName": "Example",
1889
- "duration": 10,
1890
- "timestamp": 20,
1891
- "type": "render",
1892
- "warning": null,
1893
- },
1894
- ]
1895
- `);
1896
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1897
- [
1898
- {
1899
- "lanes": "0b0000000000000000000000000100000",
1900
- "timestamp": 10,
1901
- "type": "schedule-render",
1902
- "warning": null,
1903
- },
1904
- {
1905
- "componentName": "Example",
1906
- "lanes": "0b0000000000000000000000000000010",
1907
- "timestamp": 20,
1908
- "type": "schedule-force-update",
1909
- "warning": null,
1910
- },
1911
- ]
1912
- `);
1913
- });
1914
-
1915
- it('should mark render phase state updates for class component', async () => {
1916
- class Example extends React.Component {
1917
- state = {didRender: false};
1918
- render() {
1919
- if (this.state.didRender === false) {
1920
- this.setState({didRender: true});
1921
- }
1922
- Scheduler.unstable_advanceTime(10);
1923
- Scheduler.log(
1924
- this.state.didRender ? 'second render' : 'first render',
1925
- );
1926
- return null;
1927
- }
1928
- }
1929
-
1930
- modernRender(<Example />);
1931
-
1932
- let errorMessage;
1933
- jest.spyOn(console, 'error').mockImplementation(message => {
1934
- errorMessage = message;
1935
- });
1936
-
1937
- await waitForAll(['first render', 'second render']);
1938
-
1939
- expect(console.error).toHaveBeenCalledTimes(1);
1940
- expect(errorMessage).toContain(
1941
- 'Cannot update during an existing state transition',
1942
- );
1943
-
1944
- const timelineData = stopProfilingAndGetTimelineData();
1945
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
1946
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
1947
- [
1948
- {
1949
- "componentName": "Example",
1950
- "duration": 10,
1951
- "timestamp": 10,
1952
- "type": "render",
1953
- "warning": null,
1954
- },
1955
- {
1956
- "componentName": "Example",
1957
- "duration": 10,
1958
- "timestamp": 20,
1959
- "type": "render",
1960
- "warning": null,
1961
- },
1962
- ]
1963
- `);
1964
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
1965
- [
1966
- {
1967
- "lanes": "0b0000000000000000000000000100000",
1968
- "timestamp": 10,
1969
- "type": "schedule-render",
1970
- "warning": null,
1971
- },
1972
- {
1973
- "componentName": "Example",
1974
- "componentStack": "
1975
- in Example (at **)",
1976
- "lanes": "0b0000000000000000000000000100000",
1977
- "timestamp": 10,
1978
- "type": "schedule-state-update",
1979
- "warning": null,
1980
- },
1981
- ]
1982
- `);
1983
- });
1984
-
1985
- it('should mark render phase force updates for class component', async () => {
1986
- let forced = false;
1987
- class Example extends React.Component {
1988
- render() {
1989
- Scheduler.unstable_advanceTime(10);
1990
- Scheduler.log(forced ? 'force update' : 'render');
1991
- if (!forced) {
1992
- forced = true;
1993
- this.forceUpdate();
1994
- }
1995
- return null;
1996
- }
1997
- }
1998
-
1999
- modernRender(<Example />);
2000
-
2001
- let errorMessage;
2002
- jest.spyOn(console, 'error').mockImplementation(message => {
2003
- errorMessage = message;
2004
- });
2005
-
2006
- await waitForAll(['render', 'force update']);
2007
-
2008
- expect(console.error).toHaveBeenCalledTimes(1);
2009
- expect(errorMessage).toContain(
2010
- 'Cannot update during an existing state transition',
2011
- );
2012
-
2013
- const timelineData = stopProfilingAndGetTimelineData();
2014
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2015
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2016
- [
2017
- {
2018
- "componentName": "Example",
2019
- "duration": 10,
2020
- "timestamp": 10,
2021
- "type": "render",
2022
- "warning": null,
2023
- },
2024
- {
2025
- "componentName": "Example",
2026
- "duration": 10,
2027
- "timestamp": 20,
2028
- "type": "render",
2029
- "warning": null,
2030
- },
2031
- ]
2032
- `);
2033
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2034
- [
2035
- {
2036
- "lanes": "0b0000000000000000000000000100000",
2037
- "timestamp": 10,
2038
- "type": "schedule-render",
2039
- "warning": null,
2040
- },
2041
- {
2042
- "componentName": "Example",
2043
- "lanes": "0b0000000000000000000000000100000",
2044
- "timestamp": 20,
2045
- "type": "schedule-force-update",
2046
- "warning": null,
2047
- },
2048
- ]
2049
- `);
2050
- });
2051
-
2052
- it('should mark cascading layout updates', async () => {
2053
- function Example() {
2054
- const [didMount, setDidMount] = React.useState(false);
2055
- React.useLayoutEffect(() => {
2056
- Scheduler.unstable_advanceTime(1);
2057
- setDidMount(true);
2058
- }, []);
2059
- Scheduler.unstable_advanceTime(10);
2060
- Scheduler.log(didMount ? 'update' : 'mount');
2061
- return didMount;
2062
- }
2063
-
2064
- modernRender(<Example />);
2065
-
2066
- await waitForAll(['mount', 'update']);
2067
-
2068
- const timelineData = stopProfilingAndGetTimelineData();
2069
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2070
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2071
- [
2072
- {
2073
- "componentName": "Example",
2074
- "duration": 10,
2075
- "timestamp": 10,
2076
- "type": "render",
2077
- "warning": null,
2078
- },
2079
- {
2080
- "componentName": "Example",
2081
- "duration": 1,
2082
- "timestamp": 20,
2083
- "type": "layout-effect-mount",
2084
- "warning": null,
2085
- },
2086
- {
2087
- "componentName": "Example",
2088
- "duration": 10,
2089
- "timestamp": 21,
2090
- "type": "render",
2091
- "warning": null,
2092
- },
2093
- ]
2094
- `);
2095
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2096
- [
2097
- {
2098
- "lanes": "0b0000000000000000000000000100000",
2099
- "timestamp": 10,
2100
- "type": "schedule-render",
2101
- "warning": null,
2102
- },
2103
- {
2104
- "componentName": "Example",
2105
- "componentStack": "
2106
- in Example (at **)",
2107
- "lanes": "0b0000000000000000000000000000010",
2108
- "timestamp": 21,
2109
- "type": "schedule-state-update",
2110
- "warning": null,
2111
- },
2112
- ]
2113
- `);
2114
- });
2115
-
2116
- it('should mark cascading passive updates', async () => {
2117
- function Example() {
2118
- const [didMount, setDidMount] = React.useState(false);
2119
- React.useEffect(() => {
2120
- Scheduler.unstable_advanceTime(1);
2121
- setDidMount(true);
2122
- }, []);
2123
- Scheduler.unstable_advanceTime(10);
2124
- Scheduler.log(didMount ? 'update' : 'mount');
2125
- return didMount;
2126
- }
2127
-
2128
- modernRender(<Example />);
2129
- await waitForAll(['mount', 'update']);
2130
-
2131
- const timelineData = stopProfilingAndGetTimelineData();
2132
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
2133
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2134
- [
2135
- {
2136
- "componentName": "Example",
2137
- "duration": 10,
2138
- "timestamp": 10,
2139
- "type": "render",
2140
- "warning": null,
2141
- },
2142
- {
2143
- "componentName": "Example",
2144
- "duration": 1,
2145
- "timestamp": 20,
2146
- "type": "passive-effect-mount",
2147
- "warning": null,
2148
- },
2149
- {
2150
- "componentName": "Example",
2151
- "duration": 10,
2152
- "timestamp": 21,
2153
- "type": "render",
2154
- "warning": null,
2155
- },
2156
- ]
2157
- `);
2158
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2159
- [
2160
- {
2161
- "lanes": "0b0000000000000000000000000100000",
2162
- "timestamp": 10,
2163
- "type": "schedule-render",
2164
- "warning": null,
2165
- },
2166
- {
2167
- "componentName": "Example",
2168
- "componentStack": "
2169
- in Example (at **)",
2170
- "lanes": "0b0000000000000000000000000100000",
2171
- "timestamp": 21,
2172
- "type": "schedule-state-update",
2173
- "warning": null,
2174
- },
2175
- ]
2176
- `);
2177
- });
2178
-
2179
- it('should mark render phase updates', async () => {
2180
- function Example() {
2181
- const [didRender, setDidRender] = React.useState(false);
2182
- Scheduler.unstable_advanceTime(10);
2183
- if (!didRender) {
2184
- setDidRender(true);
2185
- }
2186
- Scheduler.log(didRender ? 'update' : 'mount');
2187
- return didRender;
2188
- }
2189
-
2190
- modernRender(<Example />);
2191
- await waitForAll(['mount', 'update']);
2192
-
2193
- const timelineData = stopProfilingAndGetTimelineData();
2194
- // Render phase updates should be retried as part of the same batch.
2195
- expect(timelineData.batchUIDToMeasuresMap.size).toBe(1);
2196
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2197
- [
2198
- {
2199
- "componentName": "Example",
2200
- "duration": 20,
2201
- "timestamp": 10,
2202
- "type": "render",
2203
- "warning": null,
2204
- },
2205
- ]
2206
- `);
2207
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2208
- [
2209
- {
2210
- "lanes": "0b0000000000000000000000000100000",
2211
- "timestamp": 10,
2212
- "type": "schedule-render",
2213
- "warning": null,
2214
- },
2215
- {
2216
- "componentName": "Example",
2217
- "componentStack": "
2218
- in Example (at **)",
2219
- "lanes": "0b0000000000000000000000000100000",
2220
- "timestamp": 20,
2221
- "type": "schedule-state-update",
2222
- "warning": null,
2223
- },
2224
- ]
2225
- `);
2226
- });
2227
-
2228
- it('should mark concurrent render that throws', async () => {
2229
- jest.spyOn(console, 'error').mockImplementation(() => {});
2230
-
2231
- class ErrorBoundary extends React.Component {
2232
- state = {error: null};
2233
- componentDidCatch(error) {
2234
- this.setState({error});
2235
- }
2236
- render() {
2237
- Scheduler.unstable_advanceTime(10);
2238
- if (this.state.error) {
2239
- Scheduler.log('ErrorBoundary fallback');
2240
- return null;
2241
- }
2242
- Scheduler.log('ErrorBoundary render');
2243
- return this.props.children;
2244
- }
2245
- }
2246
-
2247
- function ExampleThatThrows() {
2248
- Scheduler.log('ExampleThatThrows');
2249
- // eslint-disable-next-line no-throw-literal
2250
- throw 'Expected error';
2251
- }
2252
-
2253
- modernRender(
2254
- <ErrorBoundary>
2255
- <ExampleThatThrows />
2256
- </ErrorBoundary>,
2257
- );
2258
-
2259
- await waitForAll([
2260
- 'ErrorBoundary render',
2261
- 'ExampleThatThrows',
2262
- 'ErrorBoundary render',
2263
- 'ExampleThatThrows',
2264
- 'ErrorBoundary fallback',
2265
- ]);
2266
-
2267
- const timelineData = stopProfilingAndGetTimelineData();
2268
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2269
- [
2270
- {
2271
- "componentName": "ErrorBoundary",
2272
- "duration": 10,
2273
- "timestamp": 10,
2274
- "type": "render",
2275
- "warning": null,
2276
- },
2277
- {
2278
- "componentName": "ExampleThatThrows",
2279
- "duration": 0,
2280
- "timestamp": 20,
2281
- "type": "render",
2282
- "warning": null,
2283
- },
2284
- {
2285
- "componentName": "ErrorBoundary",
2286
- "duration": 10,
2287
- "timestamp": 20,
2288
- "type": "render",
2289
- "warning": null,
2290
- },
2291
- {
2292
- "componentName": "ExampleThatThrows",
2293
- "duration": 0,
2294
- "timestamp": 30,
2295
- "type": "render",
2296
- "warning": null,
2297
- },
2298
- {
2299
- "componentName": "ErrorBoundary",
2300
- "duration": 10,
2301
- "timestamp": 30,
2302
- "type": "render",
2303
- "warning": null,
2304
- },
2305
- ]
2306
- `);
2307
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2308
- [
2309
- {
2310
- "lanes": "0b0000000000000000000000000100000",
2311
- "timestamp": 10,
2312
- "type": "schedule-render",
2313
- "warning": null,
2314
- },
2315
- {
2316
- "componentName": "ErrorBoundary",
2317
- "componentStack": "
2318
- in ErrorBoundary (at **)",
2319
- "lanes": "0b0000000000000000000000000000010",
2320
- "timestamp": 30,
2321
- "type": "schedule-state-update",
2322
- "warning": null,
2323
- },
2324
- ]
2325
- `);
2326
- expect(timelineData.thrownErrors).toMatchInlineSnapshot(`
2327
- [
2328
- {
2329
- "componentName": "ExampleThatThrows",
2330
- "message": "Expected error",
2331
- "phase": "mount",
2332
- "timestamp": 20,
2333
- "type": "thrown-error",
2334
- },
2335
- {
2336
- "componentName": "ExampleThatThrows",
2337
- "message": "Expected error",
2338
- "phase": "mount",
2339
- "timestamp": 30,
2340
- "type": "thrown-error",
2341
- },
2342
- ]
2343
- `);
2344
- });
2345
-
2346
- it('should mark passive and layout effects', async () => {
2347
- function ComponentWithEffects() {
2348
- React.useLayoutEffect(() => {
2349
- Scheduler.log('layout 1 mount');
2350
- return () => {
2351
- Scheduler.log('layout 1 unmount');
2352
- };
2353
- }, []);
2354
-
2355
- React.useEffect(() => {
2356
- Scheduler.log('passive 1 mount');
2357
- return () => {
2358
- Scheduler.log('passive 1 unmount');
2359
- };
2360
- }, []);
2361
-
2362
- React.useLayoutEffect(() => {
2363
- Scheduler.log('layout 2 mount');
2364
- return () => {
2365
- Scheduler.log('layout 2 unmount');
2366
- };
2367
- }, []);
2368
-
2369
- React.useEffect(() => {
2370
- Scheduler.log('passive 2 mount');
2371
- return () => {
2372
- Scheduler.log('passive 2 unmount');
2373
- };
2374
- }, []);
2375
-
2376
- React.useEffect(() => {
2377
- Scheduler.log('passive 3 mount');
2378
- return () => {
2379
- Scheduler.log('passive 3 unmount');
2380
- };
2381
- }, []);
2382
-
2383
- return null;
2384
- }
2385
-
2386
- const unmount = modernRender(<ComponentWithEffects />);
2387
-
2388
- await waitForPaint(['layout 1 mount', 'layout 2 mount']);
2389
-
2390
- await waitForAll([
2391
- 'passive 1 mount',
2392
- 'passive 2 mount',
2393
- 'passive 3 mount',
2394
- ]);
2395
-
2396
- await waitForAll([]);
2397
-
2398
- unmount();
2399
-
2400
- assertLog([
2401
- 'layout 1 unmount',
2402
- 'layout 2 unmount',
2403
- 'passive 1 unmount',
2404
- 'passive 2 unmount',
2405
- 'passive 3 unmount',
2406
- ]);
2407
-
2408
- const timelineData = stopProfilingAndGetTimelineData();
2409
- expect(timelineData.componentMeasures).toMatchInlineSnapshot(`
2410
- [
2411
- {
2412
- "componentName": "ComponentWithEffects",
2413
- "duration": 0,
2414
- "timestamp": 10,
2415
- "type": "render",
2416
- "warning": null,
2417
- },
2418
- {
2419
- "componentName": "ComponentWithEffects",
2420
- "duration": 0,
2421
- "timestamp": 10,
2422
- "type": "layout-effect-mount",
2423
- "warning": null,
2424
- },
2425
- {
2426
- "componentName": "ComponentWithEffects",
2427
- "duration": 0,
2428
- "timestamp": 10,
2429
- "type": "layout-effect-mount",
2430
- "warning": null,
2431
- },
2432
- {
2433
- "componentName": "ComponentWithEffects",
2434
- "duration": 0,
2435
- "timestamp": 10,
2436
- "type": "passive-effect-mount",
2437
- "warning": null,
2438
- },
2439
- {
2440
- "componentName": "ComponentWithEffects",
2441
- "duration": 0,
2442
- "timestamp": 10,
2443
- "type": "passive-effect-mount",
2444
- "warning": null,
2445
- },
2446
- {
2447
- "componentName": "ComponentWithEffects",
2448
- "duration": 0,
2449
- "timestamp": 10,
2450
- "type": "passive-effect-mount",
2451
- "warning": null,
2452
- },
2453
- {
2454
- "componentName": "ComponentWithEffects",
2455
- "duration": 0,
2456
- "timestamp": 10,
2457
- "type": "layout-effect-unmount",
2458
- "warning": null,
2459
- },
2460
- {
2461
- "componentName": "ComponentWithEffects",
2462
- "duration": 0,
2463
- "timestamp": 10,
2464
- "type": "layout-effect-unmount",
2465
- "warning": null,
2466
- },
2467
- {
2468
- "componentName": "ComponentWithEffects",
2469
- "duration": 0,
2470
- "timestamp": 10,
2471
- "type": "passive-effect-unmount",
2472
- "warning": null,
2473
- },
2474
- {
2475
- "componentName": "ComponentWithEffects",
2476
- "duration": 0,
2477
- "timestamp": 10,
2478
- "type": "passive-effect-unmount",
2479
- "warning": null,
2480
- },
2481
- {
2482
- "componentName": "ComponentWithEffects",
2483
- "duration": 0,
2484
- "timestamp": 10,
2485
- "type": "passive-effect-unmount",
2486
- "warning": null,
2487
- },
2488
- ]
2489
- `);
2490
- expect(timelineData.batchUIDToMeasuresMap).toMatchInlineSnapshot(`
2491
- Map {
2492
- 1 => [
2493
- {
2494
- "batchUID": 1,
2495
- "depth": 0,
2496
- "duration": 0,
2497
- "lanes": "0b0000000000000000000000000100000",
2498
- "timestamp": 10,
2499
- "type": "render-idle",
2500
- },
2501
- {
2502
- "batchUID": 1,
2503
- "depth": 0,
2504
- "duration": 0,
2505
- "lanes": "0b0000000000000000000000000100000",
2506
- "timestamp": 10,
2507
- "type": "render",
2508
- },
2509
- {
2510
- "batchUID": 1,
2511
- "depth": 0,
2512
- "duration": 0,
2513
- "lanes": "0b0000000000000000000000000100000",
2514
- "timestamp": 10,
2515
- "type": "commit",
2516
- },
2517
- {
2518
- "batchUID": 1,
2519
- "depth": 1,
2520
- "duration": 0,
2521
- "lanes": "0b0000000000000000000000000100000",
2522
- "timestamp": 10,
2523
- "type": "layout-effects",
2524
- },
2525
- {
2526
- "batchUID": 1,
2527
- "depth": 0,
2528
- "duration": 0,
2529
- "lanes": "0b0000000000000000000000000100000",
2530
- "timestamp": 10,
2531
- "type": "passive-effects",
2532
- },
2533
- ],
2534
- 2 => [
2535
- {
2536
- "batchUID": 2,
2537
- "depth": 0,
2538
- "duration": 0,
2539
- "lanes": "0b0000000000000000000000000000010",
2540
- "timestamp": 10,
2541
- "type": "render-idle",
2542
- },
2543
- {
2544
- "batchUID": 2,
2545
- "depth": 0,
2546
- "duration": 0,
2547
- "lanes": "0b0000000000000000000000000000010",
2548
- "timestamp": 10,
2549
- "type": "render",
2550
- },
2551
- {
2552
- "batchUID": 2,
2553
- "depth": 0,
2554
- "duration": 0,
2555
- "lanes": "0b0000000000000000000000000000010",
2556
- "timestamp": 10,
2557
- "type": "commit",
2558
- },
2559
- {
2560
- "batchUID": 2,
2561
- "depth": 1,
2562
- "duration": 0,
2563
- "lanes": "0b0000000000000000000000000000010",
2564
- "timestamp": 10,
2565
- "type": "layout-effects",
2566
- },
2567
- {
2568
- "batchUID": 2,
2569
- "depth": 1,
2570
- "duration": 0,
2571
- "lanes": "0b0000000000000000000000000000010",
2572
- "timestamp": 10,
2573
- "type": "passive-effects",
2574
- },
2575
- ],
2576
- }
2577
- `);
2578
- });
2579
-
2580
- it('should generate component stacks for state update', async () => {
2581
- function CommponentWithChildren({initialRender}) {
2582
- Scheduler.log('Render ComponentWithChildren');
2583
- return <Child initialRender={initialRender} />;
2584
- }
2585
-
2586
- function Child({initialRender}) {
2587
- const [didRender, setDidRender] = React.useState(initialRender);
2588
- if (!didRender) {
2589
- setDidRender(true);
2590
- }
2591
- Scheduler.log('Render Child');
2592
- return null;
2593
- }
2594
-
2595
- modernRender(<CommponentWithChildren initialRender={false} />);
2596
-
2597
- await waitForAll([
2598
- 'Render ComponentWithChildren',
2599
- 'Render Child',
2600
- 'Render Child',
2601
- ]);
2602
-
2603
- const timelineData = stopProfilingAndGetTimelineData();
2604
- expect(timelineData.schedulingEvents).toMatchInlineSnapshot(`
2605
- [
2606
- {
2607
- "lanes": "0b0000000000000000000000000100000",
2608
- "timestamp": 10,
2609
- "type": "schedule-render",
2610
- "warning": null,
2611
- },
2612
- {
2613
- "componentName": "Child",
2614
- "componentStack": "
2615
- in Child (at **)
2616
- in CommponentWithChildren (at **)",
2617
- "lanes": "0b0000000000000000000000000100000",
2618
- "timestamp": 10,
2619
- "type": "schedule-state-update",
2620
- "warning": null,
2621
- },
2622
- ]
2623
- `);
2624
- });
2625
- });
2626
- });
2627
-
2628
- describe('when not profiling', () => {
2629
- describe('with legacy render', () => {
2630
- const {render: legacyRender} = getLegacyRenderImplementation();
2631
-
2632
- // @reactVersion <= 18.2
2633
- // @reactVersion >= 18.0
2634
- it('should not log any marks', () => {
2635
- legacyRender(<div />);
2636
-
2637
- const timelineData = stopProfilingAndGetTimelineData();
2638
- expect(timelineData).toBeNull();
2639
- });
2640
- });
2641
- });
2642
- });
2643
-});