[test] add activity test with gSBU and enableViewTransition bugfix (#35555)
Related to https://github.com/facebook/react/pull/35548, `enableViewTransition` fixes a bug where `getSnapshotBeforeUpdate` was running in hidden trees when it shouldn't (`componentWillUpdate` won't run for hidden updates, and when it becomes visible it will be called with `componentWillMount`).
Ricky committed
Feb 4, 2026 at 14:37 UTC
087a34696f1eb484157b19aee1648bcbce51de64
1 file changed
+81
packages/react-reconciler/src/__tests__/Activity-test.js
+81
@@ -1527,6 +1527,87 @@ describe('Activity', () => {
1527
expect(root).toMatchRenderedOutput(<span prop={2} />);
1528
});
1529
1530
+ // @gate enableActivity
1531
+ it('getSnapshotBeforeUpdate does not run in hidden trees', async () => {
1532
+ let setState;
1533
+
1534
+ class Child extends React.Component {
1535
+ getSnapshotBeforeUpdate(prevProps) {
1536
+ const snapshot = `snapshot-${prevProps.value}-to-${this.props.value}`;
1537
+ Scheduler.log(`getSnapshotBeforeUpdate: ${snapshot}`);
1538
+ return snapshot;
1539
+ }
1540
+ componentDidUpdate(prevProps, prevState, snapshot) {
1541
+ Scheduler.log(`componentDidUpdate: ${snapshot}`);
1542
+ }
1543
+ componentDidMount() {
1544
+ Scheduler.log('componentDidMount');
1545
+ }
1546
+ componentWillUnmount() {
1547
+ Scheduler.log('componentWillUnmount');
1548
+ }
1549
+ render() {
1550
+ Scheduler.log(`render: ${this.props.value}`);
1551
+ return <span prop={this.props.value} />;
1552
+ }
1553
+ }
1554
+
1555
+ function Wrapper({show}) {
1556
+ const [value, _setState] = useState(1);
1557
+ setState = _setState;
1558
+ return (
1559
+ <Activity mode={show ? 'visible' : 'hidden'}>
1560
+ <Child value={value} />
1561
+ </Activity>
1562
+ );
1563
+ }
1564
+
1565
+ const root = ReactNoop.createRoot();
1566
+
1567
+ // Initial render
1568
+ await act(() => {
1569
+ root.render(<Wrapper show={true} />);
1570
+ });
1571
+ assertLog(['render: 1', 'componentDidMount']);
1572
+
1573
+ // Hide the Activity
1574
+ await act(() => {
1575
+ root.render(<Wrapper show={false} />);
1576
+ });
1577
+ assertLog([
1578
+ 'componentWillUnmount',
1579
+ 'render: 1',
1580
+ // Bugfix: snapshots for hidden trees should not need to be read.
1581
+ ...(gate('enableViewTransition')
1582
+ ? []
1583
+ : ['getSnapshotBeforeUpdate: snapshot-1-to-1']),
1584
+ ]);
1585
+
1586
+ // Trigger an update while hidden by calling setState
1587
+ await act(() => {
1588
+ setState(2);
1589
+ });
1590
+ assertLog([
1591
+ 'render: 2',
1592
+ ...(gate('enableViewTransition')
1593
+ ? []
1594
+ : ['getSnapshotBeforeUpdate: snapshot-1-to-2']),
1595
+ ]);
1596
+
1597
+ // This is treated as a new mount so the snapshot also shouldn't be read.
1598
+ await act(() => {
1599
+ root.render(<Wrapper show={true} />);
1600
+ });
1601
+ assertLog([
1602
+ 'render: 2',
1603
+ ...(gate('enableViewTransition')
1604
+ ? []
1605
+ : ['getSnapshotBeforeUpdate: snapshot-2-to-2']),
1606
+ 'componentDidMount',
1607
+ ]);
1608
+ });
1609
+
1610
+ // @gate enableActivity
1611
it('warns if you pass a hidden prop', async () => {
1612
function App() {
1613
return (