Fix interaction tracing for batched update mounts (#15567)
* Added failing test for act+interaction tracing * Mark pending interactions on root for legacy unbatched phase
Brian Vaughn committed
May 6, 2019 at 12:59 UTC
6da04b5d886b272e241178694e15ced22c5a2c05
4 files changed
+164
-1
package.json
+1
-1
@@ -87,7 +87,7 @@
87
"@mattiasbuelens/web-streams-polyfill": "0.1.0"
88
},
89
"devEngines": {
90
- "node": "8.x || 9.x || 10.x || 11.x"
90
+ "node": "8.x || 9.x || 10.x || 11.x || 12.x"
91
},
92
"jest": {
93
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+80
@@ -10,6 +10,7 @@
10
let React;
11
let ReactDOM;
12
let ReactTestUtils;
13
+let SchedulerTracing;
14
let act;
15
let container;
16
@@ -29,6 +30,7 @@ describe('ReactTestUtils.act()', () => {
30
React = require('react');
31
ReactDOM = require('react-dom');
32
ReactTestUtils = require('react-dom/test-utils');
33
+ SchedulerTracing = require('scheduler/tracing');
34
act = ReactTestUtils.act;
35
container = document.createElement('div');
36
document.body.appendChild(container);
@@ -423,4 +425,82 @@ describe('ReactTestUtils.act()', () => {
425
expect(el.innerHTML).toBe('5');
426
});
427
});
428
+
429
+ describe('interaction tracing', () => {
430
+ if (__DEV__) {
431
+ it('should correctly trace interactions for sync roots', () => {
432
+ let expectedInteraction;
433
+
434
+ const Component = jest.fn(() => {
435
+ expect(expectedInteraction).toBeDefined();
436
+
437
+ const interactions = SchedulerTracing.unstable_getCurrent();
438
+ expect(interactions.size).toBe(1);
439
+ expect(interactions).toContain(expectedInteraction);
440
+
441
+ return null;
442
+ });
443
+
444
+ act(() => {
445
+ SchedulerTracing.unstable_trace(
446
+ 'mount traced inside act',
447
+ performance.now(),
448
+ () => {
449
+ const interactions = SchedulerTracing.unstable_getCurrent();
450
+ expect(interactions.size).toBe(1);
451
+ expectedInteraction = Array.from(interactions)[0];
452
+
453
+ ReactDOM.render(<Component />, container);
454
+ },
455
+ );
456
+ });
457
+
458
+ act(() => {
459
+ SchedulerTracing.unstable_trace(
460
+ 'update traced inside act',
461
+ performance.now(),
462
+ () => {
463
+ const interactions = SchedulerTracing.unstable_getCurrent();
464
+ expect(interactions.size).toBe(1);
465
+ expectedInteraction = Array.from(interactions)[0];
466
+
467
+ ReactDOM.render(<Component />, container);
468
+ },
469
+ );
470
+ });
471
+
472
+ const secondContainer = document.createElement('div');
473
+
474
+ SchedulerTracing.unstable_trace(
475
+ 'mount traced outside act',
476
+ performance.now(),
477
+ () => {
478
+ act(() => {
479
+ const interactions = SchedulerTracing.unstable_getCurrent();
480
+ expect(interactions.size).toBe(1);
481
+ expectedInteraction = Array.from(interactions)[0];
482
+
483
+ ReactDOM.render(<Component />, secondContainer);
484
+ });
485
+ },
486
+ );
487
+
488
+ SchedulerTracing.unstable_trace(
489
+ 'update traced outside act',
490
+ performance.now(),
491
+ () => {
492
+ act(() => {
493
+ const interactions = SchedulerTracing.unstable_getCurrent();
494
+ expect(interactions.size).toBe(1);
495
+ expectedInteraction = Array.from(interactions)[0];
496
+
497
+ ReactDOM.render(<Component />, secondContainer);
498
+ });
499
+ },
500
+ );
501
+
502
+ expect(Component).toHaveBeenCalledTimes(4);
503
+ });
504
+ }
505
+ });
506
});
packages/react-dom/src/__tests__/ReactUpdates-test.js
+80
@@ -1618,4 +1618,84 @@ describe('ReactUpdates', () => {
1618
expect(container.textContent).toBe('1000');
1619
});
1620
}
1621
+
1622
+ if (__DEV__) {
1623
+ it('should properly trace interactions within batched udpates', () => {
1624
+ const SchedulerTracing = require('scheduler/tracing');
1625
+
1626
+ let expectedInteraction;
1627
+
1628
+ const container = document.createElement('div');
1629
+
1630
+ const Component = jest.fn(() => {
1631
+ expect(expectedInteraction).toBeDefined();
1632
+
1633
+ const interactions = SchedulerTracing.unstable_getCurrent();
1634
+ expect(interactions.size).toBe(1);
1635
+ expect(interactions).toContain(expectedInteraction);
1636
+
1637
+ return null;
1638
+ });
1639
+
1640
+ ReactDOM.unstable_batchedUpdates(() => {
1641
+ SchedulerTracing.unstable_trace(
1642
+ 'mount traced inside a batched update',
1643
+ 1,
1644
+ () => {
1645
+ const interactions = SchedulerTracing.unstable_getCurrent();
1646
+ expect(interactions.size).toBe(1);
1647
+ expectedInteraction = Array.from(interactions)[0];
1648
+
1649
+ ReactDOM.render(<Component />, container);
1650
+ },
1651
+ );
1652
+ });
1653
+
1654
+ ReactDOM.unstable_batchedUpdates(() => {
1655
+ SchedulerTracing.unstable_trace(
1656
+ 'update traced inside a batched update',
1657
+ 2,
1658
+ () => {
1659
+ const interactions = SchedulerTracing.unstable_getCurrent();
1660
+ expect(interactions.size).toBe(1);
1661
+ expectedInteraction = Array.from(interactions)[0];
1662
+
1663
+ ReactDOM.render(<Component />, container);
1664
+ },
1665
+ );
1666
+ });
1667
+
1668
+ const secondContainer = document.createElement('div');
1669
+
1670
+ SchedulerTracing.unstable_trace(
1671
+ 'mount traced outside a batched update',
1672
+ 3,
1673
+ () => {
1674
+ ReactDOM.unstable_batchedUpdates(() => {
1675
+ const interactions = SchedulerTracing.unstable_getCurrent();
1676
+ expect(interactions.size).toBe(1);
1677
+ expectedInteraction = Array.from(interactions)[0];
1678
+
1679
+ ReactDOM.render(<Component />, secondContainer);
1680
+ });
1681
+ },
1682
+ );
1683
+
1684
+ SchedulerTracing.unstable_trace(
1685
+ 'update traced outside a batched update',
1686
+ 4,
1687
+ () => {
1688
+ ReactDOM.unstable_batchedUpdates(() => {
1689
+ const interactions = SchedulerTracing.unstable_getCurrent();
1690
+ expect(interactions.size).toBe(1);
1691
+ expectedInteraction = Array.from(interactions)[0];
1692
+
1693
+ ReactDOM.render(<Component />, container);
1694
+ });
1695
+ },
1696
+ );
1697
+
1698
+ expect(Component).toHaveBeenCalledTimes(4);
1699
+ });
1700
+ }
1701
});
packages/react-reconciler/src/ReactFiberScheduler.js
+3
@@ -331,6 +331,9 @@ export function scheduleUpdateOnFiber(
331
332
if (expirationTime === Sync) {
333
if (workPhase === LegacyUnbatchedPhase) {
334
+ // Register pending interactions on the root to avoid losing traced interaction data.
335
+ schedulePendingInteraction(root, expirationTime);
336
+
337
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
338
// root inside of batchedUpdates should be synchronous, but layout updates
339
// should be deferred until the end of the batch.