@samitouri / QOS-React / commits / 1c02b9d2bd

[DOM] disable legacy mode behind flag (#28468)

Adds a flag to disable legacy mode. Currently this flag is used to cause legacy mode apis like render and hydrate to throw. This change also removes render, hydrate, unmountComponentAtNode, and unstable_renderSubtreeIntoContainer from the experiemntal entrypoint. Right now for Meta builds this flag is off (legacy mode is still supported). In OSS builds this flag matches __NEXT_MAJOR__ which means it currently is on in experiemental. This means that after merging legacy mode is effectively removed from experimental builds. While this is a breaking change, experimental builds are not stable and users can pin to older versions or update their use of react-dom to no longer use legacy mode APIs.

Josh Story committed Mar 4, 2024 at 08:19 UTC 1c02b9d2bdc18091cc6afec810fc1b361f00abdd
46 files changed +483 -187
packages/react-devtools-shared/src/__tests__/store-test.js
+5
@@ -227,6 +227,7 @@ describe('Store', () => {
227
228 // @reactVersion >= 18.0
229 // @reactVersion < 19
230 + // @gate !disableLegacyMode
231 it('should support mount and update operations for multiple roots (legacy render)', () => {
232 const Parent = ({count}) =>
233 new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -941,6 +942,7 @@ describe('Store', () => {
942
943 // @reactVersion >= 18.0
944 // @reactVersion < 19
945 + // @gate !disableLegacyMode
946 it('should support mount and update operations for multiple roots (legacy render)', () => {
947 const Parent = ({count}) =>
948 new Array(count).fill(true).map((_, index) => <Child key={index} />);
@@ -1469,6 +1471,7 @@ describe('Store', () => {
1471
1472 // @reactVersion >= 18.0
1473 // @reactVersion < 19
1474 + // @gate !disableLegacyMode
1475 it('detects and updates profiling support based on the attached roots (legacy render)', () => {
1476 const Component = () => null;
1477
@@ -1632,6 +1635,7 @@ describe('Store', () => {
1635
1636 // @reactVersion >= 18.0
1637 // @reactVersion < 19
1638 + // @gate !disableLegacyMode
1639 it('should support Lazy components (legacy render)', async () => {
1640 const container = document.createElement('div');
1641
@@ -1702,6 +1706,7 @@ describe('Store', () => {
1706
1707 // @reactVersion >= 18.0
1708 // @reactVersion < 19
1709 + // @gate !disableLegacyMode
1710 it('should support Lazy components that are unmounted before they finish loading (legacy render)', async () => {
1711 const container = document.createElement('div');
1712
packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
+1
@@ -36,6 +36,7 @@ describe('StoreStress (Legacy Mode)', () => {
36 // It renders different trees that should produce the same output.
37 // @reactVersion >= 16.9
38 // @reactVersion < 19
39 + // @gate !disableLegacyMode
40 it('should handle a stress test with different tree operations (Legacy Mode)', () => {
41 let setShowX;
42 const A = () => 'a';
packages/react-dom/index.experimental.js
-4
@@ -14,11 +14,7 @@ export {
14 hydrateRoot,
15 findDOMNode,
16 flushSync,
17 - hydrate,
18 - render,
19 - unmountComponentAtNode,
17 unstable_batchedUpdates,
21 - unstable_renderSubtreeIntoContainer,
18 unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
19 useFormStatus,
20 useFormState,
packages/react-dom/src/__tests__/ReactComponent-test.js
+2
@@ -26,6 +26,7 @@ describe('ReactComponent', () => {
26 act = require('internal-test-utils').act;
27 });
28
29 + // @gate !disableLegacyMode
30 it('should throw on invalid render targets in legacy roots', () => {
31 const container = document.createElement('div');
32 // jQuery objects are basically arrays; people often pass them in by mistake
@@ -455,6 +456,7 @@ describe('ReactComponent', () => {
456 /* eslint-enable indent */
457 });
458
459 + // @gate !disableLegacyMode
460 it('fires the callback after a component is rendered in legacy roots', () => {
461 const callback = jest.fn();
462 const container = document.createElement('div');
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+1
@@ -630,6 +630,7 @@ describe('ReactCompositeComponent-state', () => {
630 );
631 });
632
633 + // @gate !disableLegacyMode
634 it('Legacy mode should support setState in componentWillUnmount (#18851)', () => {
635 let subscription;
636 class A extends React.Component {
packages/react-dom/src/__tests__/ReactDOM-test.js
+2
@@ -164,6 +164,7 @@ describe('ReactDOM', () => {
164 expect(dog.className).toBe('bigdog');
165 });
166
167 + // @gate !disableLegacyMode
168 it('throws in render() if the mount callback in legacy roots is not a function', async () => {
169 function Foo() {
170 this.a = 1;
@@ -216,6 +217,7 @@ describe('ReactDOM', () => {
217 );
218 });
219
220 + // @gate !disableLegacyMode
221 it('throws in render() if the update callback in legacy roots is not a function', async () => {
222 function Foo() {
223 this.a = 1;
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+13 -9
@@ -332,7 +332,7 @@ describe('ReactDOMComponent', () => {
332 });
333 });
334
335 - it('throws with Temporal-like objects as style values', () => {
335 + it('throws with Temporal-like objects as style values', async () => {
336 class TemporalLike {
337 valueOf() {
338 // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -344,14 +344,17 @@ describe('ReactDOMComponent', () => {
344 }
345 }
346 const style = {fontSize: new TemporalLike()};
347 - const div = document.createElement('div');
348 - const test = () => ReactDOM.render(<span style={style} />, div);
349 - expect(() =>
350 - expect(test).toThrowError(new TypeError('prod message')),
351 - ).toErrorDev(
352 - 'Warning: The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
353 - ' This value must be coerced to a string before using it here.',
354 - );
347 + const root = ReactDOMClient.createRoot(document.createElement('div'));
348 + await expect(async () => {
349 + await expect(async () => {
350 + await act(() => {
351 + root.render(<span style={style} />);
352 + });
353 + }).toErrorDev(
354 + 'Warning: The provided `fontSize` CSS property is an unsupported type TemporalLike.' +
355 + ' This value must be coerced to a string before using it here.',
356 + );
357 + }).rejects.toThrowError(new TypeError('prod message'));
358 });
359
360 it('should update styles if initially null', async () => {
@@ -3688,6 +3691,7 @@ describe('ReactDOMComponent', () => {
3691 expect(typeof portalContainer.onclick).toBe('function');
3692 });
3693
3694 + // @gate !disableLegacyMode
3695 it('does not add onclick handler to the React root in legacy mode', () => {
3696 const container = document.createElement('div');
3697
packages/react-dom/src/__tests__/ReactDOMConsoleErrorReportingLegacy-test.js
+7
@@ -55,6 +55,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
55 });
56
57 describe('ReactDOM.render', () => {
58 + // @gate !disableLegacyMode
59 it('logs errors during event handlers', async () => {
60 spyOnDevAndProd(console, 'error');
61
@@ -156,6 +157,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
157 }
158 });
159
160 + // @gate !disableLegacyMode
161 it('logs render errors without an error boundary', async () => {
162 spyOnDevAndProd(console, 'error');
163
@@ -223,6 +225,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
225 }
226 });
227
228 + // @gate !disableLegacyMode
229 it('logs render errors with an error boundary', async () => {
230 spyOnDevAndProd(console, 'error');
231
@@ -295,6 +298,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
298 }
299 });
300
301 + // @gate !disableLegacyMode
302 it('logs layout effect errors without an error boundary', async () => {
303 spyOnDevAndProd(console, 'error');
304
@@ -365,6 +369,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
369 }
370 });
371
372 + // @gate !disableLegacyMode
373 it('logs layout effect errors with an error boundary', async () => {
374 spyOnDevAndProd(console, 'error');
375
@@ -440,6 +445,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
445 }
446 });
447
448 + // @gate !disableLegacyMode
449 it('logs passive effect errors without an error boundary', async () => {
450 spyOnDevAndProd(console, 'error');
451
@@ -511,6 +517,7 @@ describe('ReactDOMConsoleErrorReporting', () => {
517 }
518 });
519
520 + // @gate !disableLegacyMode
521 it('logs passive effect errors with an error boundary', async () => {
522 spyOnDevAndProd(console, 'error');
523
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+1
@@ -50,6 +50,7 @@ describe('ReactDOMFiberAsync', () => {
50 document.body.removeChild(container);
51 });
52
53 + // @gate !disableLegacyMode
54 it('renders synchronously by default in legacy mode', () => {
55 const ops = [];
56 ReactDOM.render(<div>Hi</div>, container, () => {
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+45
@@ -35,6 +35,7 @@ describe('ReactDOMHooks', () => {
35 document.body.removeChild(container);
36 });
37
38 + // @gate !disableLegacyMode
39 it('can ReactDOM.render() from useEffect', async () => {
40 const container2 = document.createElement('div');
41 const container3 = document.createElement('div');
@@ -76,6 +77,50 @@ describe('ReactDOMHooks', () => {
77 expect(container3.textContent).toBe('6');
78 });
79
80 + it('can render() from useEffect', async () => {
81 + const container2 = document.createElement('div');
82 + const container3 = document.createElement('div');
83 +
84 + const root1 = ReactDOMClient.createRoot(container);
85 + const root2 = ReactDOMClient.createRoot(container2);
86 + const root3 = ReactDOMClient.createRoot(container3);
87 +
88 + function Example1({n}) {
89 + React.useEffect(() => {
90 + root2.render(<Example2 n={n} />);
91 + });
92 + return 1 * n;
93 + }
94 +
95 + function Example2({n}) {
96 + React.useEffect(() => {
97 + root3.render(<Example3 n={n} />);
98 + });
99 + return 2 * n;
100 + }
101 +
102 + function Example3({n}) {
103 + return 3 * n;
104 + }
105 +
106 + await act(() => {
107 + root1.render(<Example1 n={1} />);
108 + });
109 + await waitForAll([]);
110 + expect(container.textContent).toBe('1');
111 + expect(container2.textContent).toBe('2');
112 + expect(container3.textContent).toBe('3');
113 +
114 + await act(() => {
115 + root1.render(<Example1 n={2} />);
116 + });
117 + await waitForAll([]);
118 + expect(container.textContent).toBe('2');
119 + expect(container2.textContent).toBe('4');
120 + expect(container3.textContent).toBe('6');
121 + });
122 +
123 + // @gate !disableLegacyMode
124 it('should not bail out when an update is scheduled from within an event handler', () => {
125 const {createRef, useCallback, useState} = React;
126
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+11 -13
@@ -733,7 +733,7 @@ describe('ReactDOMInput', () => {
733 expect(node.value).toBe('foobar');
734 });
735
736 - it('should throw for date inputs if `defaultValue` is an object where valueOf() throws', () => {
736 + it('should throw for date inputs if `defaultValue` is an object where valueOf() throws', async () => {
737 class TemporalLike {
738 valueOf() {
739 // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -744,19 +744,16 @@ describe('ReactDOMInput', () => {
744 return '2020-01-01';
745 }
746 }
747 - const legacyContainer = document.createElement('div');
748 - document.body.appendChild(legacyContainer);
749 - const test = () =>
750 - ReactDOM.render(
751 - <input defaultValue={new TemporalLike()} type="date" />,
752 - legacyContainer,
747 + await expect(async () => {
748 + await expect(async () => {
749 + await act(() => {
750 + root.render(<input defaultValue={new TemporalLike()} type="date" />);
751 + });
752 + }).toErrorDev(
753 + 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
754 + 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
755 );
754 - expect(() =>
755 - expect(test).toThrowError(new TypeError('prod message')),
756 - ).toErrorDev(
757 - 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
758 - 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
759 - );
756 + }).rejects.toThrowError(new TypeError('prod message'));
757 });
758
759 it('should throw for text inputs if `defaultValue` is an object where valueOf() throws', async () => {
@@ -1736,6 +1733,7 @@ describe('ReactDOMInput', () => {
1733 assertInputTrackingIsCurrent(container);
1734 });
1735
1736 + // @gate !disableLegacyMode
1737 it('should control radio buttons if the tree updates during render in legacy mode', async () => {
1738 container.remove();
1739 container = document.createElement('div');
packages/react-dom/src/__tests__/ReactDOMLegacyComponentTree-test.internal.js
+2
@@ -27,6 +27,7 @@ describe('ReactDOMComponentTree', () => {
27 container = null;
28 });
29
30 + // @gate !disableLegacyMode
31 it('finds instance of node that is attempted to be unmounted', () => {
32 const component = <div />;
33 const node = ReactDOM.render(<div>{component}</div>, container);
@@ -39,6 +40,7 @@ describe('ReactDOMComponentTree', () => {
40 );
41 });
42
43 + // @gate !disableLegacyMode
44 it('finds instance from node to stop rendering over other react rendered components', () => {
45 const component = (
46 <div>
packages/react-dom/src/__tests__/ReactDOMLegacyFiber-test.js
+42
@@ -27,6 +27,7 @@ describe('ReactDOMLegacyFiber', () => {
27 jest.restoreAllMocks();
28 });
29
30 + // @gate !disableLegacyMode
31 it('should render strings as children', () => {
32 const Box = ({value}) => <div>{value}</div>;
33
@@ -34,6 +35,7 @@ describe('ReactDOMLegacyFiber', () => {
35 expect(container.textContent).toEqual('foo');
36 });
37
38 + // @gate !disableLegacyMode
39 it('should render numbers as children', () => {
40 const Box = ({value}) => <div>{value}</div>;
41
@@ -42,6 +44,7 @@ describe('ReactDOMLegacyFiber', () => {
44 expect(container.textContent).toEqual('10');
45 });
46
47 + // @gate !disableLegacyMode
48 it('should be called a callback argument', () => {
49 // mounting phase
50 let called = false;
@@ -54,6 +57,7 @@ describe('ReactDOMLegacyFiber', () => {
57 expect(called).toEqual(true);
58 });
59
60 + // @gate !disableLegacyMode
61 it('should call a callback argument when the same element is re-rendered', () => {
62 class Foo extends React.Component {
63 render() {
@@ -75,6 +79,7 @@ describe('ReactDOMLegacyFiber', () => {
79 expect(called).toEqual(true);
80 });
81
82 + // @gate !disableLegacyMode
83 it('should render a component returning strings directly from render', () => {
84 const Text = ({value}) => value;
85
@@ -82,6 +87,7 @@ describe('ReactDOMLegacyFiber', () => {
87 expect(container.textContent).toEqual('foo');
88 });
89
90 + // @gate !disableLegacyMode
91 it('should render a component returning numbers directly from render', () => {
92 const Text = ({value}) => value;
93
@@ -90,6 +96,7 @@ describe('ReactDOMLegacyFiber', () => {
96 expect(container.textContent).toEqual('10');
97 });
98
99 + // @gate !disableLegacyMode
100 it('finds the DOM Text node of a string child', () => {
101 class Text extends React.Component {
102 render() {
@@ -109,6 +116,7 @@ describe('ReactDOMLegacyFiber', () => {
116 expect(textNode.nodeValue).toBe('foo');
117 });
118
119 + // @gate !disableLegacyMode
120 it('finds the first child when a component returns a fragment', () => {
121 class Fragment extends React.Component {
122 render() {
@@ -126,6 +134,7 @@ describe('ReactDOMLegacyFiber', () => {
134 expect(firstNode.tagName).toBe('DIV');
135 });
136
137 + // @gate !disableLegacyMode
138 it('finds the first child even when fragment is nested', () => {
139 class Wrapper extends React.Component {
140 render() {
@@ -154,6 +163,7 @@ describe('ReactDOMLegacyFiber', () => {
163 expect(firstNode.tagName).toBe('DIV');
164 });
165
166 + // @gate !disableLegacyMode
167 it('finds the first child even when first child renders null', () => {
168 class NullComponent extends React.Component {
169 render() {
@@ -177,6 +187,7 @@ describe('ReactDOMLegacyFiber', () => {
187 expect(firstNode.tagName).toBe('DIV');
188 });
189
190 + // @gate !disableLegacyMode
191 it('renders an empty fragment', () => {
192 const Div = () => <div />;
193 const EmptyFragment = () => <></>;
@@ -232,6 +243,7 @@ describe('ReactDOMLegacyFiber', () => {
243 expect(testContainer.innerHTML).toBe('');
244 };
245
246 + // @gate !disableLegacyMode
247 it('should render one portal', () => {
248 const portalContainer = document.createElement('div');
249
@@ -247,6 +259,7 @@ describe('ReactDOMLegacyFiber', () => {
259 expect(container.innerHTML).toBe('');
260 });
261
262 + // @gate !disableLegacyMode
263 it('should render many portals', () => {
264 const portalContainer1 = document.createElement('div');
265 const portalContainer2 = document.createElement('div');
@@ -347,6 +360,7 @@ describe('ReactDOMLegacyFiber', () => {
360 ]);
361 });
362
363 + // @gate !disableLegacyMode
364 it('should render nested portals', () => {
365 const portalContainer1 = document.createElement('div');
366 const portalContainer2 = document.createElement('div');
@@ -390,6 +404,7 @@ describe('ReactDOMLegacyFiber', () => {
404 expect(container.innerHTML).toBe('');
405 });
406
407 + // @gate !disableLegacyMode
408 it('should reconcile portal children', () => {
409 const portalContainer = document.createElement('div');
410
@@ -436,6 +451,7 @@ describe('ReactDOMLegacyFiber', () => {
451 expect(container.innerHTML).toBe('<div></div>');
452 });
453
454 + // @gate !disableLegacyMode
455 it('should unmount empty portal component wherever it appears', () => {
456 const portalContainer = document.createElement('div');
457
@@ -470,6 +486,7 @@ describe('ReactDOMLegacyFiber', () => {
486 expect(container.innerHTML).toBe('<div><div>parent</div></div>');
487 });
488
489 + // @gate !disableLegacyMode
490 it('should keep track of namespace across portals (simple)', () => {
491 assertNamespacesMatch(
492 <svg {...expectSVG}>
@@ -498,6 +515,7 @@ describe('ReactDOMLegacyFiber', () => {
515 );
516 });
517
518 + // @gate !disableLegacyMode
519 it('should keep track of namespace across portals (medium)', () => {
520 assertNamespacesMatch(
521 <svg {...expectSVG}>
@@ -562,6 +580,7 @@ describe('ReactDOMLegacyFiber', () => {
580 );
581 });
582
583 + // @gate !disableLegacyMode
584 it('should keep track of namespace across portals (complex)', () => {
585 assertNamespacesMatch(
586 <div {...expectHTML}>
@@ -636,6 +655,7 @@ describe('ReactDOMLegacyFiber', () => {
655 );
656 });
657
658 + // @gate !disableLegacyMode
659 it('should unwind namespaces on uncaught errors', () => {
660 function BrokenRender() {
661 throw new Error('Hello');
@@ -651,6 +671,7 @@ describe('ReactDOMLegacyFiber', () => {
671 assertNamespacesMatch(<div {...expectHTML} />);
672 });
673
674 + // @gate !disableLegacyMode
675 it('should unwind namespaces on caught errors', () => {
676 function BrokenRender() {
677 throw new Error('Hello');
@@ -684,6 +705,7 @@ describe('ReactDOMLegacyFiber', () => {
705 assertNamespacesMatch(<div {...expectHTML} />);
706 });
707
708 + // @gate !disableLegacyMode
709 it('should unwind namespaces on caught errors in a portal', () => {
710 function BrokenRender() {
711 throw new Error('Hello');
@@ -719,6 +741,7 @@ describe('ReactDOMLegacyFiber', () => {
741 });
742
743 // @gate !disableLegacyContext
744 + // @gate !disableLegacyMode
745 it('should pass portal context when rendering subtree elsewhere', () => {
746 const portalContainer = document.createElement('div');
747
@@ -754,6 +777,7 @@ describe('ReactDOMLegacyFiber', () => {
777 });
778
779 // @gate !disableLegacyContext
780 + // @gate !disableLegacyMode
781 it('should update portal context if it changes due to setState', () => {
782 const portalContainer = document.createElement('div');
783
@@ -799,6 +823,7 @@ describe('ReactDOMLegacyFiber', () => {
823 });
824
825 // @gate !disableLegacyContext
826 + // @gate !disableLegacyMode
827 it('should update portal context if it changes due to re-render', () => {
828 const portalContainer = document.createElement('div');
829
@@ -839,6 +864,7 @@ describe('ReactDOMLegacyFiber', () => {
864 expect(container.innerHTML).toBe('');
865 });
866
867 + // @gate !disableLegacyMode
868 it('findDOMNode should find dom element after expanding a fragment', () => {
869 class MyNode extends React.Component {
870 render() {
@@ -859,6 +885,7 @@ describe('ReactDOMLegacyFiber', () => {
885 expect(b.tagName).toBe('SPAN');
886 });
887
888 + // @gate !disableLegacyMode
889 it('should bubble events from the portal to the parent', () => {
890 const portalContainer = document.createElement('div');
891 document.body.appendChild(portalContainer);
@@ -890,6 +917,7 @@ describe('ReactDOMLegacyFiber', () => {
917 }
918 });
919
920 + // @gate !disableLegacyMode
921 it('should not onMouseLeave when staying in the portal', () => {
922 const portalContainer = document.createElement('div');
923 document.body.appendChild(portalContainer);
@@ -966,6 +994,7 @@ describe('ReactDOMLegacyFiber', () => {
994 });
995
996 // Regression test for https://github.com/facebook/react/issues/19562
997 + // @gate !disableLegacyMode
998 it('does not fire mouseEnter twice when relatedTarget is the root node', () => {
999 let ops = [];
1000 let target = null;
@@ -1016,6 +1045,7 @@ describe('ReactDOMLegacyFiber', () => {
1045 expect(ops).toEqual([]);
1046 });
1047
1048 + // @gate !disableLegacyMode
1049 it('listens to events that do not exist in the Portal subtree', () => {
1050 const onClick = jest.fn();
1051
@@ -1043,6 +1073,7 @@ describe('ReactDOMLegacyFiber', () => {
1073 }).toThrow('Target container is not a DOM element.');
1074 });
1075
1076 + // @gate !disableLegacyMode
1077 it('should warn for non-functional event listeners', () => {
1078 class Example extends React.Component {
1079 render() {
@@ -1056,6 +1087,7 @@ describe('ReactDOMLegacyFiber', () => {
1087 );
1088 });
1089
1090 + // @gate !disableLegacyMode
1091 it('should warn with a special message for `false` event listeners', () => {
1092 class Example extends React.Component {
1093 render() {
@@ -1071,6 +1103,7 @@ describe('ReactDOMLegacyFiber', () => {
1103 );
1104 });
1105
1106 + // @gate !disableLegacyMode
1107 it('should not update event handlers until commit', () => {
1108 spyOnDev(console, 'error');
1109
@@ -1168,6 +1201,7 @@ describe('ReactDOMLegacyFiber', () => {
1201 }
1202 });
1203
1204 + // @gate !disableLegacyMode
1205 it('should not crash encountering low-priority tree', () => {
1206 ReactDOM.render(
1207 <div hidden={true}>
@@ -1177,6 +1211,7 @@ describe('ReactDOMLegacyFiber', () => {
1211 );
1212 });
1213
1214 + // @gate !disableLegacyMode
1215 it('should not warn when rendering into an empty container', () => {
1216 ReactDOM.render(<div>foo</div>, container);
1217 expect(container.innerHTML).toBe('<div>foo</div>');
@@ -1186,6 +1221,7 @@ describe('ReactDOMLegacyFiber', () => {
1221 expect(container.innerHTML).toBe('<div>bar</div>');
1222 });
1223
1224 + // @gate !disableLegacyMode
1225 it('should warn when replacing a container which was manually updated outside of React', () => {
1226 // when not messing with the DOM outside of React
1227 ReactDOM.render(<div key="1">foo</div>, container);
@@ -1210,6 +1246,7 @@ describe('ReactDOMLegacyFiber', () => {
1246 }).toThrowError();
1247 });
1248
1249 + // @gate !disableLegacyMode
1250 it('should warn when doing an update to a container manually updated outside of React', () => {
1251 // when not messing with the DOM outside of React
1252 ReactDOM.render(<div>foo</div>, container);
@@ -1227,6 +1264,7 @@ describe('ReactDOMLegacyFiber', () => {
1264 );
1265 });
1266
1267 + // @gate !disableLegacyMode
1268 it('should warn when doing an update to a container manually cleared outside of React', () => {
1269 // when not messing with the DOM outside of React
1270 ReactDOM.render(<div>foo</div>, container);
@@ -1244,6 +1282,7 @@ describe('ReactDOMLegacyFiber', () => {
1282 );
1283 });
1284
1285 + // @gate !disableLegacyMode
1286 it('should render a text component with a text DOM node on the same document as the container', () => {
1287 // 1. Create a new document through the use of iframe
1288 // 2. Set up the spy to make asserts when a text component
@@ -1274,6 +1313,7 @@ describe('ReactDOMLegacyFiber', () => {
1313 expect(iframeContainer.appendChild).toHaveBeenCalledTimes(1);
1314 });
1315
1316 + // @gate !disableLegacyMode
1317 it('should mount into a document fragment', () => {
1318 const fragment = document.createDocumentFragment();
1319 ReactDOM.render(<div>foo</div>, fragment);
@@ -1283,6 +1323,7 @@ describe('ReactDOMLegacyFiber', () => {
1323 });
1324
1325 // Regression test for https://github.com/facebook/react/issues/12643#issuecomment-413727104
1326 + // @gate !disableLegacyMode
1327 it('should not diff memoized host components', () => {
1328 const inputRef = React.createRef();
1329 let didCallOnChange = false;
@@ -1341,6 +1382,7 @@ describe('ReactDOMLegacyFiber', () => {
1382 expect(didCallOnChange).toBe(true);
1383 });
1384
1385 + // @gate !disableLegacyMode
1386 it('unmounted legacy roots should never clear newer root content from a container', () => {
1387 const ref = React.createRef();
1388
packages/react-dom/src/__tests__/ReactDOMOption-test.js
+62 -63
@@ -13,7 +13,6 @@ describe('ReactDOMOption', () => {
13 let React;
14 let ReactDOMClient;
15 let ReactDOMServer;
16 - let ReactTestUtils;
16 let act;
17
18 beforeEach(() => {
@@ -21,41 +20,47 @@ describe('ReactDOMOption', () => {
20 React = require('react');
21 ReactDOMClient = require('react-dom/client');
22 ReactDOMServer = require('react-dom/server');
24 - ReactTestUtils = require('react-dom/test-utils');
23 act = require('internal-test-utils').act;
24 });
25
28 - it('should flatten children to a string', () => {
26 + async function renderIntoDocument(children) {
27 + const container = document.createElement('div');
28 + const root = ReactDOMClient.createRoot(container);
29 + await act(async () => root.render(children));
30 + return container;
31 + }
32 +
33 + it('should flatten children to a string', async () => {
34 const stub = (
35 <option>
36 {1} {'foo'}
37 </option>
38 );
34 - const node = ReactTestUtils.renderIntoDocument(stub);
39 + const container = await renderIntoDocument(stub);
40
36 - expect(node.innerHTML).toBe('1 foo');
41 + expect(container.firstChild.innerHTML).toBe('1 foo');
42 });
43
39 - it('should warn for invalid child tags', () => {
44 + it('should warn for invalid child tags', async () => {
45 const el = (
46 <option value="12">
47 {1} <div /> {2}
48 </option>
49 );
45 - let node;
46 - expect(() => {
47 - node = ReactTestUtils.renderIntoDocument(el);
50 + let container;
51 + await expect(async () => {
52 + container = await renderIntoDocument(el);
53 }).toErrorDev(
54 'In HTML, <div> cannot be a child of <option>.\n' +
55 'This will cause a hydration error.\n' +
56 ' in div (at **)\n' +
57 ' in option (at **)',
58 );
54 - expect(node.innerHTML).toBe('1 <div></div> 2');
55 - ReactTestUtils.renderIntoDocument(el);
59 + expect(container.firstChild.innerHTML).toBe('1 <div></div> 2');
60 + await renderIntoDocument(el);
61 });
62
58 - it('should warn for component child if no value prop is provided', () => {
63 + it('should warn for component child if no value prop is provided', async () => {
64 function Foo() {
65 return '2';
66 }
@@ -64,18 +69,18 @@ describe('ReactDOMOption', () => {
69 {1} <Foo /> {3}
70 </option>
71 );
67 - let node;
68 - expect(() => {
69 - node = ReactTestUtils.renderIntoDocument(el);
72 + let container;
73 + await expect(async () => {
74 + container = await renderIntoDocument(el);
75 }).toErrorDev(
76 'Cannot infer the option value of complex children. ' +
77 'Pass a `value` prop or use a plain string as children to <option>.',
78 );
74 - expect(node.innerHTML).toBe('1 2 3');
75 - ReactTestUtils.renderIntoDocument(el);
79 + expect(container.firstChild.innerHTML).toBe('1 2 3');
80 + await renderIntoDocument(el);
81 });
82
78 - it('should not warn for component child if value prop is provided', () => {
83 + it('should not warn for component child if value prop is provided', async () => {
84 function Foo() {
85 return '2';
86 }
@@ -84,12 +89,12 @@ describe('ReactDOMOption', () => {
89 {1} <Foo /> {3}
90 </option>
91 );
87 - const node = ReactTestUtils.renderIntoDocument(el);
88 - expect(node.innerHTML).toBe('1 2 3');
89 - ReactTestUtils.renderIntoDocument(el);
92 + const container = await renderIntoDocument(el);
93 + expect(container.firstChild.innerHTML).toBe('1 2 3');
94 + await renderIntoDocument(el);
95 });
96
92 - it('should ignore null/undefined/false children without warning', () => {
97 + it('should ignore null/undefined/false children without warning', async () => {
98 const stub = (
99 <option>
100 {1} {false}
@@ -98,38 +103,38 @@ describe('ReactDOMOption', () => {
103 {undefined} {2}
104 </option>
105 );
101 - const node = ReactTestUtils.renderIntoDocument(stub);
106 + const container = await renderIntoDocument(stub);
107
103 - expect(node.innerHTML).toBe('1 2');
108 + expect(container.firstChild.innerHTML).toBe('1 2');
109 });
110
106 - it('should throw on object children', () => {
107 - expect(() => {
108 - ReactTestUtils.renderIntoDocument(<option>{{}}</option>);
109 - }).toThrow('Objects are not valid as a React child');
110 - expect(() => {
111 - ReactTestUtils.renderIntoDocument(<option>{[{}]}</option>);
112 - }).toThrow('Objects are not valid as a React child');
113 - expect(() => {
114 - ReactTestUtils.renderIntoDocument(
111 + it('should throw on object children', async () => {
112 + await expect(async () =>
113 + renderIntoDocument(<option>{{}}</option>),
114 + ).rejects.toThrow('Objects are not valid as a React child');
115 + await expect(async () => {
116 + await renderIntoDocument(<option>{[{}]}</option>);
117 + }).rejects.toThrow('Objects are not valid as a React child');
118 + await expect(async () => {
119 + await renderIntoDocument(
120 <option>
121 {{}}
122 <span />
123 </option>,
124 );
120 - }).toThrow('Objects are not valid as a React child');
121 - expect(() => {
122 - ReactTestUtils.renderIntoDocument(
125 + }).rejects.toThrow('Objects are not valid as a React child');
126 + await expect(async () => {
127 + await renderIntoDocument(
128 <option>
129 {'1'}
130 {{}}
131 {2}
132 </option>,
133 );
129 - }).toThrow('Objects are not valid as a React child');
134 + }).rejects.toThrow('Objects are not valid as a React child');
135 });
136
132 - it('should support element-ish child', () => {
137 + it('should support element-ish child', async () => {
138 // This is similar to <fbt>.
139 // We don't toString it because you must instead provide a value prop.
140 const obj = {
@@ -145,51 +150,45 @@ describe('ReactDOMOption', () => {
150 },
151 };
152
148 - let node = ReactTestUtils.renderIntoDocument(
149 - <option value="a">{obj}</option>,
150 - );
151 - expect(node.innerHTML).toBe('hello');
153 + let container = await renderIntoDocument(<option value="a">{obj}</option>);
154 + expect(container.firstChild.innerHTML).toBe('hello');
155
153 - node = ReactTestUtils.renderIntoDocument(
154 - <option value="b">{[obj]}</option>,
155 - );
156 - expect(node.innerHTML).toBe('hello');
156 + container = await renderIntoDocument(<option value="b">{[obj]}</option>);
157 + expect(container.firstChild.innerHTML).toBe('hello');
158
158 - node = ReactTestUtils.renderIntoDocument(
159 - <option value={obj}>{obj}</option>,
160 - );
161 - expect(node.innerHTML).toBe('hello');
162 - expect(node.value).toBe('hello');
159 + container = await renderIntoDocument(<option value={obj}>{obj}</option>);
160 + expect(container.firstChild.innerHTML).toBe('hello');
161 + expect(container.firstChild.value).toBe('hello');
162
164 - node = ReactTestUtils.renderIntoDocument(
163 + container = await renderIntoDocument(
164 <option value={obj}>
165 {'1'}
166 {obj}
167 {2}
168 </option>,
169 );
171 - expect(node.innerHTML).toBe('1hello2');
172 - expect(node.value).toBe('hello');
170 + expect(container.firstChild.innerHTML).toBe('1hello2');
171 + expect(container.firstChild.value).toBe('hello');
172 });
173
174 // @gate enableBigIntSupport
176 - it('should support bigint values', () => {
177 - const node = ReactTestUtils.renderIntoDocument(<option>{5n}</option>);
178 - expect(node.innerHTML).toBe('5');
179 - expect(node.value).toBe('5');
175 + it('should support bigint values', async () => {
176 + const container = await renderIntoDocument(<option>{5n}</option>);
177 + expect(container.firstChild.innerHTML).toBe('5');
178 + expect(container.firstChild.value).toBe('5');
179 });
180
182 - it('should be able to use dangerouslySetInnerHTML on option', () => {
181 + it('should be able to use dangerouslySetInnerHTML on option', async () => {
182 const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
184 - let node;
185 - expect(() => {
186 - node = ReactTestUtils.renderIntoDocument(stub);
183 + let container;
184 + await expect(async () => {
185 + container = await renderIntoDocument(stub);
186 }).toErrorDev(
187 'Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.\n' +
188 ' in option (at **)',
189 );
190
192 - expect(node.innerHTML).toBe('foobar');
191 + expect(container.firstChild.innerHTML).toBe('foobar');
192 });
193
194 it('should set attribute for empty value', async () => {
packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+1
@@ -965,6 +965,7 @@ describe('ReactDOMSelect', () => {
965 expect(node.options[2].selected).toBe(false); // c
966 });
967
968 + // @gate !disableLegacyMode
969 it('should allow controlling `value` in a nested legacy render', async () => {
970 let selectNode;
971
packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
+4
@@ -477,6 +477,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
477 resetModules();
478 });
479
480 + // @gate !disableLegacyMode
481 it('legacy mode can explicitly ignore errors reconnecting different element types of children', () =>
482 expectMarkupMatch(
483 <div>
@@ -487,6 +488,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
488 </div>,
489 ));
490
491 + // @gate !disableLegacyMode
492 it('legacy mode can explicitly ignore reconnecting more children', () =>
493 expectMarkupMatch(
494 <div>
@@ -498,6 +500,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
500 </div>,
501 ));
502
503 + // @gate !disableLegacyMode
504 it('legacy mode can explicitly ignore reconnecting fewer children', () =>
505 expectMarkupMatch(
506 <div>
@@ -509,6 +512,7 @@ describe('ReactDOMServerIntegration (legacy)', () => {
512 </div>,
513 ));
514
515 + // @gate !disableLegacyMode
516 it('legacy mode can explicitly ignore reconnecting reordered children', () =>
517 expectMarkupMatch(
518 <div suppressHydrationWarning={true}>
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+1
@@ -718,6 +718,7 @@ describe('ReactDOMServerPartialHydration', () => {
718 expect(deleted.length).toBe(1);
719 });
720
721 + // @gate !disableLegacyMode
722 it('warns and replaces the boundary content in legacy mode', async () => {
723 let suspend = false;
724 let resolve;
packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js
+3
@@ -996,16 +996,19 @@ describe('ReactDOM HostSingleton', () => {
996 });
997
998 // https://github.com/facebook/react/issues/26128
999 + // @gate !disableLegacyMode
1000 it('(#26128) does not throw when rendering at body in legacy mode', async () => {
1001 ReactDOM.render(<div />, document.body);
1002 });
1003
1004 // https://github.com/facebook/react/issues/26128
1005 + // @gate !disableLegacyMode
1006 it('(#26128) does not throw when rendering at <html> in legacy mode', async () => {
1007 ReactDOM.render(<body />, document.documentElement);
1008 });
1009
1010 // https://github.com/facebook/react/issues/26128
1011 + // @gate !disableLegacyMode
1012 it('(#26128) does not throw when rendering at document in legacy mode', async () => {
1013 ReactDOM.render(<html />, document);
1014 });
packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
+3
@@ -100,6 +100,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
100 return text;
101 }
102
103 + // @gate !disableLegacyMode
104 it('hides and unhides timed out DOM elements in legacy roots', async () => {
105 const divs = [
106 React.createRef(null),
@@ -160,6 +161,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
161 expect(container.textContent).toEqual('ABC');
162 });
163
164 + // @gate !disableLegacyMode
165 it(
166 'in legacy roots, re-hides children if their display is updated ' +
167 'but the boundary is still showing the fallback',
@@ -213,6 +215,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
215 );
216
217 // Regression test for https://github.com/facebook/react/issues/14188
218 + // @gate !disableLegacyMode
219 it('can call findDOMNode() in a suspended component commit phase in legacy roots', async () => {
220 const log = [];
221 const Lazy = React.lazy(
packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
+9
@@ -25,6 +25,7 @@ describe('ReactLegacyCompositeComponent', () => {
25 act = require('internal-test-utils').act;
26 });
27
28 + // @gate !disableLegacyMode
29 it('should warn about `setState` in render in legacy mode', () => {
30 const container = document.createElement('div');
31
@@ -377,6 +378,7 @@ describe('ReactLegacyCompositeComponent', () => {
378 });
379
380 // @gate !disableLegacyContext
381 + // @gate !disableLegacyMode
382 it('unmasked context propagates through updates', () => {
383 class Leaf extends React.Component {
384 static contextTypes = {
@@ -441,6 +443,7 @@ describe('ReactLegacyCompositeComponent', () => {
443 });
444
445 // @gate !disableLegacyContext
446 + // @gate !disableLegacyMode
447 it('should trigger componentWillReceiveProps for context changes', () => {
448 let contextChanges = 0;
449 let propChanges = 0;
@@ -553,6 +556,7 @@ describe('ReactLegacyCompositeComponent', () => {
556 expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
557 });
558
559 + // @gate !disableLegacyMode
560 it('only renders once if updated in componentWillReceiveProps in legacy mode', () => {
561 let renders = 0;
562
@@ -581,6 +585,7 @@ describe('ReactLegacyCompositeComponent', () => {
585 expect(instance.state.updated).toBe(true);
586 });
587
588 + // @gate !disableLegacyMode
589 it('only renders once if updated in componentWillReceiveProps when batching in legacy mode', () => {
590 let renders = 0;
591
@@ -611,6 +616,7 @@ describe('ReactLegacyCompositeComponent', () => {
616 expect(instance.state.updated).toBe(true);
617 });
618
619 + // @gate !disableLegacyMode
620 it('should update refs if shouldComponentUpdate gives false in legacy mode', () => {
621 class Static extends React.Component {
622 shouldComponentUpdate() {
@@ -665,6 +671,7 @@ describe('ReactLegacyCompositeComponent', () => {
671 expect(ReactDOM.findDOMNode(comp.static1Ref.current).textContent).toBe('A');
672 });
673
674 + // @gate !disableLegacyMode
675 it('should allow access to findDOMNode in componentWillUnmount in legacy mode', () => {
676 let a = null;
677 let b = null;
@@ -693,6 +700,7 @@ describe('ReactLegacyCompositeComponent', () => {
700 });
701
702 // @gate !disableLegacyContext || !__DEV__
703 + // @gate !disableLegacyMode
704 it('context should be passed down from the parent', () => {
705 class Parent extends React.Component {
706 static childContextTypes = {
@@ -813,6 +821,7 @@ describe('ReactLegacyCompositeComponent', () => {
821 expect(moo.state.amIImmutable).toBe(undefined);
822 });
823
824 + // @gate !disableLegacyMode
825 it('should not warn about unmounting during unmounting in legacy mode', () => {
826 const container = document.createElement('div');
827 const layer = document.createElement('div');
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+42
@@ -588,6 +588,7 @@ describe('ReactLegacyErrorBoundaries', () => {
588 jest.restoreAllMocks();
589 });
590
591 + // @gate !disableLegacyMode
592 it('does not swallow exceptions on mounting without boundaries', () => {
593 let container = document.createElement('div');
594 expect(() => {
@@ -605,6 +606,7 @@ describe('ReactLegacyErrorBoundaries', () => {
606 }).toThrow('Hello');
607 });
608
609 + // @gate !disableLegacyMode
610 it('does not swallow exceptions on updating without boundaries', () => {
611 let container = document.createElement('div');
612 ReactDOM.render(<BrokenComponentWillUpdate />, container);
@@ -625,6 +627,7 @@ describe('ReactLegacyErrorBoundaries', () => {
627 }).toThrow('Hello');
628 });
629
630 + // @gate !disableLegacyMode
631 it('does not swallow exceptions on unmounting without boundaries', () => {
632 const container = document.createElement('div');
633 ReactDOM.render(<BrokenComponentWillUnmount />, container);
@@ -633,6 +636,7 @@ describe('ReactLegacyErrorBoundaries', () => {
636 }).toThrow('Hello');
637 });
638
639 + // @gate !disableLegacyMode
640 it('prevents errors from leaking into other roots', () => {
641 const container1 = document.createElement('div');
642 const container2 = document.createElement('div');
@@ -670,6 +674,7 @@ describe('ReactLegacyErrorBoundaries', () => {
674 expect(container3.firstChild).toBe(null);
675 });
676
677 + // @gate !disableLegacyMode
678 it('logs a single error using both error boundaries', () => {
679 const container = document.createElement('div');
680 spyOnDev(console, 'error');
@@ -715,6 +720,7 @@ describe('ReactLegacyErrorBoundaries', () => {
720 expect(log).toEqual(['BothErrorBoundaries componentWillUnmount']);
721 });
722
723 + // @gate !disableLegacyMode
724 it('renders an error state if child throws in render', () => {
725 const container = document.createElement('div');
726 ReactDOM.render(
@@ -745,6 +751,7 @@ describe('ReactLegacyErrorBoundaries', () => {
751 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
752 });
753
754 + // @gate !disableLegacyMode
755 it('renders an error state if child throws in constructor', () => {
756 const container = document.createElement('div');
757 ReactDOM.render(
@@ -773,6 +780,7 @@ describe('ReactLegacyErrorBoundaries', () => {
780 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
781 });
782
783 + // @gate !disableLegacyMode
784 it('renders an error state if child throws in componentWillMount', () => {
785 const container = document.createElement('div');
786 ReactDOM.render(
@@ -801,6 +809,7 @@ describe('ReactLegacyErrorBoundaries', () => {
809 });
810
811 // @gate !disableLegacyContext || !__DEV__
812 + // @gate !disableLegacyMode
813 it('renders an error state if context provider throws in componentWillMount', () => {
814 class BrokenComponentWillMountWithContext extends React.Component {
815 static childContextTypes = {foo: PropTypes.number};
@@ -826,6 +835,7 @@ describe('ReactLegacyErrorBoundaries', () => {
835 });
836
837 if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
838 + // @gate !disableLegacyMode
839 it('renders an error state if module-style context provider throws in componentWillMount', () => {
840 function BrokenComponentWillMountWithContext() {
841 return {
@@ -864,6 +874,7 @@ describe('ReactLegacyErrorBoundaries', () => {
874 });
875 }
876
877 + // @gate !disableLegacyMode
878 it('mounts the error message if mounting fails', () => {
879 function renderError(error) {
880 return <ErrorMessage message={error.message} />;
@@ -902,6 +913,7 @@ describe('ReactLegacyErrorBoundaries', () => {
913 ]);
914 });
915
916 + // @gate !disableLegacyMode
917 it('propagates errors on retry on mounting', () => {
918 const container = document.createElement('div');
919 ReactDOM.render(
@@ -946,6 +958,7 @@ describe('ReactLegacyErrorBoundaries', () => {
958 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
959 });
960
961 + // @gate !disableLegacyMode
962 it('propagates errors inside boundary during componentWillMount', () => {
963 const container = document.createElement('div');
964 ReactDOM.render(
@@ -974,6 +987,7 @@ describe('ReactLegacyErrorBoundaries', () => {
987 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
988 });
989
990 + // @gate !disableLegacyMode
991 it('propagates errors inside boundary while rendering error state', () => {
992 const container = document.createElement('div');
993 ReactDOM.render(
@@ -1017,6 +1031,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1031 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1032 });
1033
1034 + // @gate !disableLegacyMode
1035 it('does not call componentWillUnmount when aborting initial mount', () => {
1036 const container = document.createElement('div');
1037 ReactDOM.render(
@@ -1056,6 +1071,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1071 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1072 });
1073
1074 + // @gate !disableLegacyMode
1075 it('resets callback refs if mounting aborts', () => {
1076 function childRef(x) {
1077 log.push('Child ref is set to ' + x);
@@ -1100,6 +1116,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1116 ]);
1117 });
1118
1119 + // @gate !disableLegacyMode
1120 it('resets object refs if mounting aborts', () => {
1121 const childRef = React.createRef();
1122 const errorMessageRef = React.createRef();
@@ -1140,6 +1157,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1157 expect(errorMessageRef.current).toEqual(null);
1158 });
1159
1160 + // @gate !disableLegacyMode
1161 it('successfully mounts if no error occurs', () => {
1162 const container = document.createElement('div');
1163 ReactDOM.render(
@@ -1161,6 +1179,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1179 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1180 });
1181
1182 + // @gate !disableLegacyMode
1183 it('catches if child throws in constructor during update', () => {
1184 const container = document.createElement('div');
1185 ReactDOM.render(
@@ -1209,6 +1228,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1228 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1229 });
1230
1231 + // @gate !disableLegacyMode
1232 it('catches if child throws in componentWillMount during update', () => {
1233 const container = document.createElement('div');
1234 ReactDOM.render(
@@ -1258,6 +1278,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1278 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1279 });
1280
1281 + // @gate !disableLegacyMode
1282 it('catches if child throws in componentWillReceiveProps during update', () => {
1283 const container = document.createElement('div');
1284 ReactDOM.render(
@@ -1302,6 +1323,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1323 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1324 });
1325
1326 + // @gate !disableLegacyMode
1327 it('catches if child throws in componentWillUpdate during update', () => {
1328 const container = document.createElement('div');
1329 ReactDOM.render(
@@ -1347,6 +1369,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1369 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1370 });
1371
1372 + // @gate !disableLegacyMode
1373 it('catches if child throws in render during update', () => {
1374 const container = document.createElement('div');
1375 ReactDOM.render(
@@ -1396,6 +1419,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1419 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1420 });
1421
1422 + // @gate !disableLegacyMode
1423 it('keeps refs up-to-date during updates', () => {
1424 function child1Ref(x) {
1425 log.push('Child1 ref is set to ' + x);
@@ -1460,6 +1484,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1484 ]);
1485 });
1486
1487 + // @gate !disableLegacyMode
1488 it('recovers from componentWillUnmount errors on update', () => {
1489 const container = document.createElement('div');
1490 ReactDOM.render(
@@ -1516,6 +1541,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1541 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1542 });
1543
1544 + // @gate !disableLegacyMode
1545 it('recovers from nested componentWillUnmount errors on update', () => {
1546 const container = document.createElement('div');
1547 ReactDOM.render(
@@ -1577,6 +1603,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1603 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1604 });
1605
1606 + // @gate !disableLegacyMode
1607 it('picks the right boundary when handling unmounting errors', () => {
1608 function renderInnerError(error) {
1609 return <div>Caught an inner error: {error.message}.</div>;
@@ -1646,6 +1673,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1673 ]);
1674 });
1675
1676 + // @gate !disableLegacyMode
1677 it('can recover from error state', () => {
1678 const container = document.createElement('div');
1679 ReactDOM.render(
@@ -1694,6 +1722,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1722 ]);
1723 });
1724
1725 + // @gate !disableLegacyMode
1726 it('can update multiple times in error state', () => {
1727 const container = document.createElement('div');
1728 ReactDOM.render(
@@ -1718,6 +1747,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1747 ReactDOM.unmountComponentAtNode(container);
1748 });
1749
1750 + // @gate !disableLegacyMode
1751 it("doesn't get into inconsistent state during removals", () => {
1752 const container = document.createElement('div');
1753 ReactDOM.render(
@@ -1737,6 +1767,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1767 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1768 });
1769
1770 + // @gate !disableLegacyMode
1771 it("doesn't get into inconsistent state during additions", () => {
1772 const container = document.createElement('div');
1773 ReactDOM.render(<ErrorBoundary />, container);
@@ -1755,6 +1786,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1786 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1787 });
1788
1789 + // @gate !disableLegacyMode
1790 it("doesn't get into inconsistent state during reorders", () => {
1791 function getAMixOfNormalAndBrokenRenderElements() {
1792 const elements = [];
@@ -1803,6 +1835,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1835 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1836 });
1837
1838 + // @gate !disableLegacyMode
1839 it('catches errors originating downstream', () => {
1840 let fail = false;
1841 class Stateful extends React.Component {
@@ -1845,6 +1878,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1878 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1879 });
1880
1881 + // @gate !disableLegacyMode
1882 it('catches errors in componentDidMount', () => {
1883 const container = document.createElement('div');
1884 ReactDOM.render(
@@ -1904,6 +1938,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1938 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1939 });
1940
1941 + // @gate !disableLegacyMode
1942 it('catches errors in componentDidUpdate', () => {
1943 const container = document.createElement('div');
1944 ReactDOM.render(
@@ -1943,6 +1978,7 @@ describe('ReactLegacyErrorBoundaries', () => {
1978 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
1979 });
1980
1981 + // @gate !disableLegacyMode
1982 it('propagates errors inside boundary during componentDidMount', () => {
1983 const container = document.createElement('div');
1984 ReactDOM.render(
@@ -1980,6 +2016,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2016 expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
2017 });
2018
2019 + // @gate !disableLegacyMode
2020 it('calls componentDidCatch for each error that is captured', () => {
2021 function renderUnmountError(error) {
2022 return <div>Caught an unmounting error: {error.message}.</div>;
@@ -2081,6 +2118,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2118 ]);
2119 });
2120
2121 + // @gate !disableLegacyMode
2122 it('discards a bad root if the root component fails', () => {
2123 const X = null;
2124 const Y = undefined;
@@ -2112,6 +2150,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2150 expect(err2.message).toMatch(/got: undefined/);
2151 });
2152
2153 + // @gate !disableLegacyMode
2154 it('renders empty output if error boundary does not handle the error', () => {
2155 const container = document.createElement('div');
2156 expect(() => {
@@ -2146,6 +2185,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2185 expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']);
2186 });
2187
2188 + // @gate !disableLegacyMode
2189 it('passes first error when two errors happen in commit', () => {
2190 const errors = [];
2191 let caughtError;
@@ -2185,6 +2225,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2225 expect(caughtError.message).toBe('child sad');
2226 });
2227
2228 + // @gate !disableLegacyMode
2229 it('propagates uncaught error inside unbatched initial mount', () => {
2230 function Foo() {
2231 throw new Error('foo error');
@@ -2197,6 +2238,7 @@ describe('ReactLegacyErrorBoundaries', () => {
2238 }).toThrow('foo error');
2239 });
2240
2241 + // @gate !disableLegacyMode
2242 it('handles errors that occur in before-mutation commit hook', () => {
2243 const errors = [];
2244 let caughtError;
packages/react-dom/src/__tests__/ReactLegacyMount-test.js
+23
@@ -37,6 +37,7 @@ describe('ReactMount', () => {
37 });
38
39 describe('unmountComponentAtNode', () => {
40 + // @gate !disableLegacyMode
41 it('throws when given a non-node', () => {
42 const nodeArray = document.getElementsByTagName('div');
43 expect(() => {
@@ -44,6 +45,7 @@ describe('ReactMount', () => {
45 }).toThrowError('Target container is not a DOM element.');
46 });
47
48 + // @gate !disableLegacyMode
49 it('returns false on non-React containers', () => {
50 const d = document.createElement('div');
51 d.innerHTML = '<b>hellooo</b>';
@@ -51,6 +53,7 @@ describe('ReactMount', () => {
53 expect(d.textContent).toBe('hellooo');
54 });
55
56 + // @gate !disableLegacyMode
57 it('returns true on React containers', () => {
58 const d = document.createElement('div');
59 ReactDOM.render(<b>hellooo</b>, d);
@@ -60,6 +63,7 @@ describe('ReactMount', () => {
63 });
64 });
65
66 + // @gate !disableLegacyMode
67 it('warns when given a factory', () => {
68 class Component extends React.Component {
69 render() {
@@ -76,6 +80,7 @@ describe('ReactMount', () => {
80 );
81 });
82
83 + // @gate !disableLegacyMode
84 it('should render different components in same root', () => {
85 const container = document.createElement('container');
86 document.body.appendChild(container);
@@ -87,6 +92,7 @@ describe('ReactMount', () => {
92 expect(container.firstChild.nodeName).toBe('SPAN');
93 });
94
95 + // @gate !disableLegacyMode
96 it('should unmount and remount if the key changes', () => {
97 const container = document.createElement('container');
98
@@ -122,6 +128,7 @@ describe('ReactMount', () => {
128 expect(mockUnmount).toHaveBeenCalledTimes(1);
129 });
130
131 + // @gate !disableLegacyMode
132 it('should reuse markup if rendering to the same target twice', () => {
133 const container = document.createElement('container');
134 const instance1 = ReactDOM.render(<div />, container);
@@ -130,6 +137,7 @@ describe('ReactMount', () => {
137 expect(instance1 === instance2).toBe(true);
138 });
139
140 + // @gate !disableLegacyMode
141 it('does not warn if mounting into left padded rendered markup', () => {
142 const container = document.createElement('container');
143 container.innerHTML = ReactDOMServer.renderToString(<div />) + ' ';
@@ -138,6 +146,7 @@ describe('ReactMount', () => {
146 ReactDOM.hydrate(<div />, container);
147 });
148
149 + // @gate !disableLegacyMode
150 it('should warn if mounting into right padded rendered markup', () => {
151 const container = document.createElement('container');
152 container.innerHTML = ' ' + ReactDOMServer.renderToString(<div />);
@@ -147,6 +156,7 @@ describe('ReactMount', () => {
156 );
157 });
158
159 + // @gate !disableLegacyMode
160 it('should not warn if mounting into non-empty node', () => {
161 const container = document.createElement('container');
162 container.innerHTML = '<div></div>';
@@ -154,6 +164,7 @@ describe('ReactMount', () => {
164 ReactDOM.render(<div />, container);
165 });
166
167 + // @gate !disableLegacyMode
168 it('should warn when mounting into document.body', () => {
169 const iFrame = document.createElement('iframe');
170 document.body.appendChild(iFrame);
@@ -162,6 +173,7 @@ describe('ReactMount', () => {
173 ReactDOM.render(<div />, iFrame.contentDocument.body);
174 });
175
176 + // @gate !disableLegacyMode
177 it('should account for escaping on a checksum mismatch', () => {
178 const div = document.createElement('div');
179 const markup = ReactDOMServer.renderToString(
@@ -180,6 +192,7 @@ describe('ReactMount', () => {
192 );
193 });
194
195 + // @gate !disableLegacyMode
196 it('should warn if render removes React-rendered children', () => {
197 const container = document.createElement('container');
198
@@ -207,6 +220,7 @@ describe('ReactMount', () => {
220 );
221 });
222
223 + // @gate !disableLegacyMode
224 it('should warn if the unmounted node was rendered by another copy of React', () => {
225 jest.resetModules();
226 const ReactDOMOther = require('react-dom');
@@ -236,6 +250,7 @@ describe('ReactMount', () => {
250 ReactDOM.unmountComponentAtNode(container);
251 });
252
253 + // @gate !disableLegacyMode
254 it('passes the correct callback context', () => {
255 const container = document.createElement('div');
256 let calls = 0;
@@ -276,6 +291,7 @@ describe('ReactMount', () => {
291 expect(calls).toBe(5);
292 });
293
294 + // @gate !disableLegacyMode
295 it('initial mount of legacy root is sync inside batchedUpdates, as if it were wrapped in flushSync', () => {
296 const container1 = document.createElement('div');
297 const container2 = document.createElement('div');
@@ -322,6 +338,7 @@ describe('ReactMount', () => {
338 expect(mountPoint.nodeType).toBe(COMMENT_NODE);
339 });
340
341 + // @gate !disableLegacyMode
342 it('renders at a comment node', () => {
343 function Char(props) {
344 return props.children;
@@ -347,6 +364,7 @@ describe('ReactMount', () => {
364 });
365 });
366
367 + // @gate !disableLegacyMode
368 it('clears existing children with legacy API', async () => {
369 const container = document.createElement('div');
370 container.innerHTML = '<div>a</div><div>b</div>';
@@ -369,6 +387,7 @@ describe('ReactMount', () => {
387 expect(container.textContent).toEqual('dc');
388 });
389
390 + // @gate !disableLegacyMode
391 it('warns when rendering with legacy API into createRoot() container', async () => {
392 const container = document.createElement('div');
393 const root = ReactDOMClient.createRoot(container);
@@ -393,6 +412,7 @@ describe('ReactMount', () => {
412 expect(container.textContent).toEqual('Bye');
413 });
414
415 + // @gate !disableLegacyMode
416 it('callback passed to legacy hydrate() API', () => {
417 const container = document.createElement('div');
418 container.innerHTML = '<div>Hi</div>';
@@ -403,6 +423,7 @@ describe('ReactMount', () => {
423 assertLog(['callback']);
424 });
425
426 + // @gate !disableLegacyMode
427 it('warns when unmounting with legacy API (no previous content)', async () => {
428 const container = document.createElement('div');
429 const root = ReactDOMClient.createRoot(container);
@@ -430,6 +451,7 @@ describe('ReactMount', () => {
451 expect(container.textContent).toEqual('');
452 });
453
454 + // @gate !disableLegacyMode
455 it('warns when unmounting with legacy API (has previous content)', async () => {
456 const container = document.createElement('div');
457 // Currently createRoot().render() doesn't clear this.
@@ -458,6 +480,7 @@ describe('ReactMount', () => {
480 expect(container.textContent).toEqual('');
481 });
482
483 + // @gate !disableLegacyMode
484 it('warns when passing legacy container to createRoot()', () => {
485 const container = document.createElement('div');
486 ReactDOM.render(<div>Hi</div>, container);
packages/react-dom/src/__tests__/ReactLegacyRootWarnings-test.js
+2
@@ -13,6 +13,7 @@ describe('ReactDOMRoot', () => {
13 jest.restoreAllMocks();
14 });
15
16 + // @gate !disableLegacyMode
17 test('deprecation warning for ReactDOM.render', () => {
18 spyOnDev(console, 'error');
19
@@ -26,6 +27,7 @@ describe('ReactDOMRoot', () => {
27 }
28 });
29
30 + // @gate !disableLegacyMode
31 test('deprecation warning for ReactDOM.hydrate', () => {
32 spyOnDev(console, 'error');
33
packages/react-dom/src/__tests__/ReactLegacyUpdates-test.js
+39
@@ -31,6 +31,7 @@ describe('ReactLegacyUpdates', () => {
31 assertLog = InternalTestUtils.assertLog;
32 });
33
34 + // @gate !disableLegacyMode
35 it('should batch state when updating state twice', () => {
36 let updateCount = 0;
37
@@ -60,6 +61,7 @@ describe('ReactLegacyUpdates', () => {
61 expect(updateCount).toBe(1);
62 });
63
64 + // @gate !disableLegacyMode
65 it('should batch state when updating two different state keys', () => {
66 let updateCount = 0;
67
@@ -92,6 +94,7 @@ describe('ReactLegacyUpdates', () => {
94 expect(updateCount).toBe(1);
95 });
96
97 + // @gate !disableLegacyMode
98 it('should batch state and props together', () => {
99 let updateCount = 0;
100
@@ -125,6 +128,7 @@ describe('ReactLegacyUpdates', () => {
128 expect(updateCount).toBe(1);
129 });
130
131 + // @gate !disableLegacyMode
132 it('should batch parent/child state updates together', () => {
133 let parentUpdateCount = 0;
134
@@ -179,6 +183,7 @@ describe('ReactLegacyUpdates', () => {
183 expect(childUpdateCount).toBe(1);
184 });
185
186 + // @gate !disableLegacyMode
187 it('should batch child/parent state updates together', () => {
188 let parentUpdateCount = 0;
189
@@ -235,6 +240,7 @@ describe('ReactLegacyUpdates', () => {
240 expect(childUpdateCount).toBe(1);
241 });
242
243 + // @gate !disableLegacyMode
244 it('should support chained state updates', () => {
245 let updateCount = 0;
246
@@ -274,6 +280,7 @@ describe('ReactLegacyUpdates', () => {
280 expect(updateCount).toBe(2);
281 });
282
283 + // @gate !disableLegacyMode
284 it('should batch forceUpdate together', () => {
285 let shouldUpdateCount = 0;
286 let updateCount = 0;
@@ -316,6 +323,7 @@ describe('ReactLegacyUpdates', () => {
323 expect(updateCount).toBe(1);
324 });
325
326 + // @gate !disableLegacyMode
327 it('should update children even if parent blocks updates', () => {
328 let parentRenderCount = 0;
329 let childRenderCount = 0;
@@ -364,6 +372,7 @@ describe('ReactLegacyUpdates', () => {
372 expect(childRenderCount).toBe(2);
373 });
374
375 + // @gate !disableLegacyMode
376 it('should not reconcile children passed via props', () => {
377 let numMiddleRenders = 0;
378 let numBottomRenders = 0;
@@ -401,6 +410,7 @@ describe('ReactLegacyUpdates', () => {
410 expect(numBottomRenders).toBe(1);
411 });
412
413 + // @gate !disableLegacyMode
414 it('should flow updates correctly', () => {
415 let willUpdates = [];
416 let didUpdates = [];
@@ -530,6 +540,7 @@ describe('ReactLegacyUpdates', () => {
540 );
541 });
542
543 + // @gate !disableLegacyMode
544 it('should queue mount-ready handlers across different roots', () => {
545 // We'll define two components A and B, then update both of them. When A's
546 // componentDidUpdate handlers is called, B's DOM should already have been
@@ -579,6 +590,7 @@ describe('ReactLegacyUpdates', () => {
590 expect(aUpdated).toBe(true);
591 });
592
593 + // @gate !disableLegacyMode
594 it('should flush updates in the correct order', () => {
595 const updates = [];
596
@@ -661,6 +673,7 @@ describe('ReactLegacyUpdates', () => {
673 /* eslint-enable indent */
674 });
675
676 + // @gate !disableLegacyMode
677 it('should flush updates in the correct order across roots', () => {
678 const instances = [];
679 const updates = [];
@@ -699,6 +712,7 @@ describe('ReactLegacyUpdates', () => {
712 expect(updates).toEqual([0, 1, 2, 0, 1, 2]);
713 });
714
715 + // @gate !disableLegacyMode
716 it('should queue nested updates', () => {
717 // See https://github.com/facebook/react/issues/1147
718
@@ -752,6 +766,7 @@ describe('ReactLegacyUpdates', () => {
766 expect(ReactDOM.findDOMNode(x).textContent).toBe('1');
767 });
768
769 + // @gate !disableLegacyMode
770 it('should queue updates from during mount', () => {
771 // See https://github.com/facebook/react/issues/1353
772 let a;
@@ -791,6 +806,7 @@ describe('ReactLegacyUpdates', () => {
806 expect(ReactDOM.findDOMNode(a).textContent).toBe('A1');
807 });
808
809 + // @gate !disableLegacyMode
810 it('calls componentWillReceiveProps setState callback properly', () => {
811 let callbackCount = 0;
812
@@ -817,6 +833,7 @@ describe('ReactLegacyUpdates', () => {
833 expect(callbackCount).toBe(1);
834 });
835
836 + // @gate !disableLegacyMode
837 it('does not call render after a component as been deleted', () => {
838 let renderCount = 0;
839 let componentB = null;
@@ -854,6 +871,7 @@ describe('ReactLegacyUpdates', () => {
871 expect(renderCount).toBe(1);
872 });
873
874 + // @gate !disableLegacyMode
875 it('throws in setState if the update callback is not a function', () => {
876 function Foo() {
877 this.a = 1;
@@ -897,6 +915,7 @@ describe('ReactLegacyUpdates', () => {
915 );
916 });
917
918 + // @gate !disableLegacyMode
919 it('throws in forceUpdate if the update callback is not a function', () => {
920 function Foo() {
921 this.a = 1;
@@ -940,6 +959,7 @@ describe('ReactLegacyUpdates', () => {
959 );
960 });
961
962 + // @gate !disableLegacyMode
963 it('does not update one component twice in a batch (#2410)', () => {
964 class Parent extends React.Component {
965 childRef = React.createRef();
@@ -992,6 +1012,7 @@ describe('ReactLegacyUpdates', () => {
1012 });
1013 });
1014
1015 + // @gate !disableLegacyMode
1016 it('does not update one component twice in a batch (#6371)', () => {
1017 let callbacks = [];
1018 function emitChange() {
@@ -1049,6 +1070,7 @@ describe('ReactLegacyUpdates', () => {
1070 expect(result).toEqual(42);
1071 });
1072
1073 + // @gate !disableLegacyMode
1074 it('unmounts and remounts a root in the same batch', () => {
1075 const container = document.createElement('div');
1076 ReactDOM.render(<span>a</span>, container);
@@ -1059,6 +1081,7 @@ describe('ReactLegacyUpdates', () => {
1081 expect(container.textContent).toBe('b');
1082 });
1083
1084 + // @gate !disableLegacyMode
1085 it('handles reentrant mounting in synchronous mode', () => {
1086 let mounts = 0;
1087 class Editor extends React.Component {
@@ -1096,6 +1119,7 @@ describe('ReactLegacyUpdates', () => {
1119 expect(mounts).toBe(1);
1120 });
1121
1122 + // @gate !disableLegacyMode
1123 it('mounts and unmounts are sync even in a batch', () => {
1124 const ops = [];
1125 const container = document.createElement('div');
@@ -1108,6 +1132,7 @@ describe('ReactLegacyUpdates', () => {
1132 expect(ops).toEqual(['Hello', '']);
1133 });
1134
1135 + // @gate !disableLegacyMode
1136 it(
1137 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1138 'should both flush in the immediately subsequent commit',
@@ -1151,6 +1176,7 @@ describe('ReactLegacyUpdates', () => {
1176 },
1177 );
1178
1179 + // @gate !disableLegacyMode
1180 it(
1181 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1182 '(on a sibling) should both flush in the immediately subsequent commit',
@@ -1222,6 +1248,7 @@ describe('ReactLegacyUpdates', () => {
1248 },
1249 );
1250
1251 + // @gate !disableLegacyMode
1252 it('uses correct base state for setState inside render phase', () => {
1253 const ops = [];
1254
@@ -1245,6 +1272,7 @@ describe('ReactLegacyUpdates', () => {
1272 expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
1273 });
1274
1275 + // @gate !disableLegacyMode
1276 it('does not re-render if state update is null', () => {
1277 const container = document.createElement('div');
1278
@@ -1265,6 +1293,7 @@ describe('ReactLegacyUpdates', () => {
1293 });
1294
1295 // Will change once we switch to async by default
1296 + // @gate !disableLegacyMode
1297 it('synchronously renders hidden subtrees', () => {
1298 const container = document.createElement('div');
1299 let ops = [];
@@ -1301,6 +1330,7 @@ describe('ReactLegacyUpdates', () => {
1330 expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1331 });
1332
1333 + // @gate !disableLegacyMode
1334 it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1335 class Foo extends React.Component {
1336 componentDidMount() {
@@ -1325,6 +1355,7 @@ describe('ReactLegacyUpdates', () => {
1355 ReactDOM.render(<Foo />, container);
1356 });
1357
1358 + // @gate !disableLegacyMode
1359 it('resets the update counter for unrelated updates', () => {
1360 const container = document.createElement('div');
1361 const ref = React.createRef();
@@ -1365,6 +1396,7 @@ describe('ReactLegacyUpdates', () => {
1396 expect(ref.current).toBe(null);
1397 });
1398
1399 + // @gate !disableLegacyMode
1400 it('does not fall into an infinite update loop', () => {
1401 class NonTerminating extends React.Component {
1402 state = {step: 0};
@@ -1390,6 +1422,7 @@ describe('ReactLegacyUpdates', () => {
1422 }).toThrow('Maximum');
1423 });
1424
1425 + // @gate !disableLegacyMode
1426 it('does not fall into an infinite update loop with useLayoutEffect', () => {
1427 function NonTerminating() {
1428 const [step, setStep] = React.useState(0);
@@ -1405,6 +1438,7 @@ describe('ReactLegacyUpdates', () => {
1438 }).toThrow('Maximum');
1439 });
1440
1441 + // @gate !disableLegacyMode
1442 it('can recover after falling into an infinite update loop', () => {
1443 class NonTerminating extends React.Component {
1444 state = {step: 0};
@@ -1445,6 +1479,7 @@ describe('ReactLegacyUpdates', () => {
1479 expect(container.textContent).toBe('1');
1480 });
1481
1482 + // @gate !disableLegacyMode
1483 it('does not fall into mutually recursive infinite update loop with same container', () => {
1484 // Note: this test would fail if there were two or more different roots.
1485
@@ -1472,6 +1507,7 @@ describe('ReactLegacyUpdates', () => {
1507 }).toThrow('Maximum');
1508 });
1509
1510 + // @gate !disableLegacyMode
1511 it('does not fall into an infinite error loop', () => {
1512 function BadRender() {
1513 throw new Error('error');
@@ -1505,6 +1541,7 @@ describe('ReactLegacyUpdates', () => {
1541 }).toThrow('Maximum');
1542 });
1543
1544 + // @gate !disableLegacyMode
1545 it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
1546 const subscribers = [];
1547
@@ -1540,6 +1577,7 @@ describe('ReactLegacyUpdates', () => {
1577
1578 // TODO: Replace this branch with @gate pragmas
1579 if (__DEV__) {
1580 + // @gate !disableLegacyMode
1581 it('can have nested updates if they do not cross the limit', async () => {
1582 let _setStep;
1583 const LIMIT = 50;
@@ -1567,6 +1605,7 @@ describe('ReactLegacyUpdates', () => {
1605 expect(container.textContent).toBe('50');
1606 });
1607
1608 + // @gate !disableLegacyMode
1609 it('can have many updates inside useEffect without triggering a warning', async () => {
1610 function Terminating() {
1611 const [step, setStep] = React.useState(0);
packages/react-dom/src/__tests__/ReactMountDestruction-test.js
+2
@@ -49,6 +49,7 @@ describe('ReactMount', () => {
49 });
50 });
51
52 + // @gate !disableLegacyMode
53 it('should warn when unmounting a non-container root node', () => {
54 const mainContainerDiv = document.createElement('div');
55
@@ -71,6 +72,7 @@ describe('ReactMount', () => {
72 );
73 });
74
75 + // @gate !disableLegacyMode
76 it('should warn when unmounting a non-container, non-root node', () => {
77 const mainContainerDiv = document.createElement('div');
78
packages/react-dom/src/__tests__/ReactRenderDocument-test.js
+1
@@ -373,6 +373,7 @@ describe('rendering React components at document', () => {
373 expect(testDocument.body.innerHTML).toBe('Hello world');
374 });
375
376 + // @gate !disableLegacyMode
377 it('supports findDOMNode on full-page components in legacy mode', () => {
378 const tree = (
379 <html>
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+3
@@ -505,6 +505,7 @@ describe('ReactDOMServerHydration', () => {
505 await act(() => root.render(<div />));
506 });
507
508 + // @gate !disableLegacyMode
509 it('Suspense + hydration in legacy mode', () => {
510 const element = document.createElement('div');
511 element.innerHTML = '<div><div>Hello World</div></div>';
@@ -530,6 +531,7 @@ describe('ReactDOMServerHydration', () => {
531 expect(element.innerHTML).toBe('<div><div>Hello World</div></div>');
532 });
533
534 + // @gate !disableLegacyMode
535 it('Suspense + hydration in legacy mode (at root)', () => {
536 const element = document.createElement('div');
537 element.innerHTML = '<div>Hello World</div>';
@@ -550,6 +552,7 @@ describe('ReactDOMServerHydration', () => {
552 );
553 });
554
555 + // @gate !disableLegacyMode
556 it('Suspense + hydration in legacy mode with no fallback', () => {
557 const element = document.createElement('div');
558 element.innerHTML = '<div>Hello World</div>';
packages/react-dom/src/__tests__/findDOMNode-test.js
+6
@@ -19,6 +19,7 @@ describe('findDOMNode', () => {
19 expect(ReactDOM.findDOMNode(null)).toBe(null);
20 });
21
22 + // @gate !disableLegacyMode
23 it('findDOMNode should find dom element', () => {
24 class MyNode extends React.Component {
25 render() {
@@ -37,6 +38,7 @@ describe('findDOMNode', () => {
38 expect(mySameDiv).toBe(myDiv);
39 });
40
41 + // @gate !disableLegacyMode
42 it('findDOMNode should find dom element after an update from null', () => {
43 function Bar({flag}) {
44 if (flag) {
@@ -69,6 +71,7 @@ describe('findDOMNode', () => {
71 }).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
72 });
73
74 + // @gate !disableLegacyMode
75 it('findDOMNode should reject unmounted objects with render func', () => {
76 class Foo extends React.Component {
77 render() {
@@ -85,6 +88,7 @@ describe('findDOMNode', () => {
88 );
89 });
90
91 + // @gate !disableLegacyMode
92 it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
93 class Bar extends React.Component {
94 UNSAFE_componentWillMount() {
@@ -98,6 +102,7 @@ describe('findDOMNode', () => {
102 expect(() => ReactTestUtils.renderIntoDocument(<Bar />)).not.toThrow();
103 });
104
105 + // @gate !disableLegacyMode
106 it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
107 let parent = undefined;
108 let child = undefined;
@@ -129,6 +134,7 @@ describe('findDOMNode', () => {
134 expect(match).toBe(child);
135 });
136
137 + // @gate !disableLegacyMode
138 it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
139 let parent = undefined;
140 let child = undefined;
packages/react-dom/src/__tests__/refsLegacy-test.js
+1
@@ -19,6 +19,7 @@ describe('root level refs with legacy APIs', () => {
19 ReactDOM = require('react-dom');
20 });
21
22 + // @gate !disableLegacyMode
23 it('attaches and detaches root refs', () => {
24 let inst = null;
25
packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js
+7 -39
@@ -20,6 +20,7 @@ const renderSubtreeIntoContainer =
20
21 describe('renderSubtreeIntoContainer', () => {
22 // @gate !disableLegacyContext
23 + // @gate !disableLegacyMode
24 it('should pass context when rendering subtree elsewhere', () => {
25 const portal = document.createElement('div');
26
@@ -63,46 +64,8 @@ describe('renderSubtreeIntoContainer', () => {
64 expect(portal.firstChild.innerHTML).toBe('bar');
65 });
66
66 - it('should throw if parentComponent is invalid', () => {
67 - const portal = document.createElement('div');
68 -
69 - class Component extends React.Component {
70 - static contextTypes = {
71 - foo: PropTypes.string.isRequired,
72 - };
73 -
74 - render() {
75 - return <div>{this.context.foo}</div>;
76 - }
77 - }
78 -
79 - // ESLint is confused here and thinks Parent is unused, presumably because
80 - // it is only used inside of the class body?
81 - // eslint-disable-next-line no-unused-vars
82 - class Parent extends React.Component {
83 - static childContextTypes = {
84 - foo: PropTypes.string.isRequired,
85 - };
86 -
87 - getChildContext() {
88 - return {
89 - foo: 'bar',
90 - };
91 - }
92 -
93 - render() {
94 - return null;
95 - }
96 -
97 - componentDidMount() {
98 - expect(function () {
99 - renderSubtreeIntoContainer(<Parent />, <Component />, portal);
100 - }).toThrowError('parentComponentmust be a valid React Component');
101 - }
102 - }
103 - });
104 -
67 // @gate !disableLegacyContext
68 + // @gate !disableLegacyMode
69 it('should update context if it changes due to setState', async () => {
70 const container = document.createElement('div');
71 document.body.appendChild(container);
@@ -171,6 +134,7 @@ describe('renderSubtreeIntoContainer', () => {
134 });
135
136 // @gate !disableLegacyContext
137 + // @gate !disableLegacyMode
138 it('should update context if it changes due to re-render', async () => {
139 const container = document.createElement('div');
140 document.body.appendChild(container);
@@ -232,6 +196,7 @@ describe('renderSubtreeIntoContainer', () => {
196 expect(portal.firstChild.innerHTML).toBe('changed-changed');
197 });
198
199 + // @gate !disableLegacyMode
200 it('should render portal with non-context-provider parent', async () => {
201 const container = document.createElement('div');
202 document.body.appendChild(container);
@@ -259,6 +224,7 @@ describe('renderSubtreeIntoContainer', () => {
224 });
225
226 // @gate !disableLegacyContext
227 + // @gate !disableLegacyMode
228 it('should get context through non-context-provider parent', async () => {
229 const container = document.createElement('div');
230 document.body.appendChild(container);
@@ -306,6 +272,7 @@ describe('renderSubtreeIntoContainer', () => {
272 });
273
274 // @gate !disableLegacyContext
275 + // @gate !disableLegacyMode
276 it('should get context through middle non-context-provider layer', async () => {
277 const container = document.createElement('div');
278 document.body.appendChild(container);
@@ -360,6 +327,7 @@ describe('renderSubtreeIntoContainer', () => {
327 expect(portal2.textContent).toBe('foo');
328 });
329
330 + // @gate !disableLegacyMode
331 it('legacy test: fails gracefully when mixing React 15 and 16', () => {
332 class C extends React.Component {
333 render() {
packages/react-dom/src/client/ReactDOMLegacy.js
+25
@@ -14,6 +14,7 @@ import type {
14 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
15 import type {ReactNodeList} from 'shared/ReactTypes';
16
17 +import {disableLegacyMode} from 'shared/ReactFeatureFlags';
18 import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
19 import {
20 getInstanceFromNode,
@@ -264,6 +265,14 @@ export function hydrate(
265 container: Container,
266 callback: ?Function,
267 ): React$Component<any, any> | PublicInstance | null {
268 + if (disableLegacyMode) {
269 + if (__DEV__) {
270 + console.error(
271 + 'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot instead',
272 + );
273 + }
274 + throw new Error('ReactDOM: Unsupported Legacy Mode API.');
275 + }
276 if (__DEV__) {
277 console.error(
278 'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
@@ -304,6 +313,14 @@ export function render(
313 container: Container,
314 callback: ?Function,
315 ): React$Component<any, any> | PublicInstance | null {
316 + if (disableLegacyMode) {
317 + if (__DEV__) {
318 + console.error(
319 + 'ReactDOM.render is no longer supported in React 18. Use createRoot instead.',
320 + );
321 + }
322 + throw new Error('ReactDOM: Unsupported Legacy Mode API.');
323 + }
324 if (__DEV__) {
325 console.error(
326 'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
@@ -344,6 +361,14 @@ export function unstable_renderSubtreeIntoContainer(
361 containerNode: Container,
362 callback: ?Function,
363 ): React$Component<any, any> | PublicInstance | null {
364 + if (disableLegacyMode) {
365 + if (__DEV__) {
366 + console.error(
367 + 'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported in React 18. Consider using a portal instead.',
368 + );
369 + }
370 + throw new Error('ReactDOM: Unsupported Legacy Mode API.');
371 + }
372 if (__DEV__) {
373 console.error(
374 'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js
+68 -46
@@ -433,7 +433,7 @@ describe('ChangeEventPlugin', () => {
433 expect(called2).toBe(1);
434 });
435
436 - it('should deduplicate input value change events', () => {
436 + it('should deduplicate input value change events', async () => {
437 let called = 0;
438
439 function cb(e) {
@@ -441,59 +441,81 @@ describe('ChangeEventPlugin', () => {
441 expect(e.type).toBe('change');
442 }
443
444 - let input;
445 - ['text', 'number', 'range'].forEach(type => {
444 + const inputTypes = ['text', 'number', 'range'];
445 + while (inputTypes.length) {
446 + const type = inputTypes.pop();
447 called = 0;
447 - input = ReactDOM.render(<input type={type} onChange={cb} />, container);
448 - // Should be ignored (no change):
449 - input.dispatchEvent(
450 - new Event('change', {bubbles: true, cancelable: true}),
451 - );
452 - setUntrackedValue.call(input, '42');
453 - input.dispatchEvent(
454 - new Event('change', {bubbles: true, cancelable: true}),
455 - );
456 - // Should be ignored (no change):
457 - input.dispatchEvent(
458 - new Event('change', {bubbles: true, cancelable: true}),
459 - );
448 + let root = ReactDOMClient.createRoot(container);
449 + let ref = {current: null};
450 + await act(() => {
451 + root.render(<input ref={ref} type={type} onChange={cb} />);
452 + });
453 + let input = ref.current;
454 + await act(() => {
455 + // Should be ignored (no change):
456 + input.dispatchEvent(
457 + new Event('change', {bubbles: true, cancelable: true}),
458 + );
459 + setUntrackedValue.call(input, '42');
460 + input.dispatchEvent(
461 + new Event('change', {bubbles: true, cancelable: true}),
462 + );
463 + // Should be ignored (no change):
464 + input.dispatchEvent(
465 + new Event('change', {bubbles: true, cancelable: true}),
466 + );
467 + });
468 expect(called).toBe(1);
461 - ReactDOM.unmountComponentAtNode(container);
469 + root.unmount();
470
471 called = 0;
464 - input = ReactDOM.render(<input type={type} onChange={cb} />, container);
465 - // Should be ignored (no change):
466 - input.dispatchEvent(
467 - new Event('input', {bubbles: true, cancelable: true}),
468 - );
469 - setUntrackedValue.call(input, '42');
470 - input.dispatchEvent(
471 - new Event('input', {bubbles: true, cancelable: true}),
472 - );
473 - // Should be ignored (no change):
474 - input.dispatchEvent(
475 - new Event('input', {bubbles: true, cancelable: true}),
476 - );
472 + root = ReactDOMClient.createRoot(container);
473 + ref = {current: null};
474 + await act(() => {
475 + root.render(<input ref={ref} type={type} onChange={cb} />);
476 + });
477 + input = ref.current;
478 + await act(() => {
479 + // Should be ignored (no change):
480 + input.dispatchEvent(
481 + new Event('input', {bubbles: true, cancelable: true}),
482 + );
483 + setUntrackedValue.call(input, '42');
484 + input.dispatchEvent(
485 + new Event('input', {bubbles: true, cancelable: true}),
486 + );
487 + // Should be ignored (no change):
488 + input.dispatchEvent(
489 + new Event('input', {bubbles: true, cancelable: true}),
490 + );
491 + });
492 expect(called).toBe(1);
478 - ReactDOM.unmountComponentAtNode(container);
493 + root.unmount();
494
495 called = 0;
481 - input = ReactDOM.render(<input type={type} onChange={cb} />, container);
482 - // Should be ignored (no change):
483 - input.dispatchEvent(
484 - new Event('change', {bubbles: true, cancelable: true}),
485 - );
486 - setUntrackedValue.call(input, '42');
487 - input.dispatchEvent(
488 - new Event('input', {bubbles: true, cancelable: true}),
489 - );
490 - // Should be ignored (no change):
491 - input.dispatchEvent(
492 - new Event('change', {bubbles: true, cancelable: true}),
493 - );
496 + root = ReactDOMClient.createRoot(container);
497 + ref = {current: null};
498 + await act(() => {
499 + root.render(<input ref={ref} type={type} onChange={cb} />);
500 + });
501 + input = ref.current;
502 + await act(() => {
503 + // Should be ignored (no change):
504 + input.dispatchEvent(
505 + new Event('change', {bubbles: true, cancelable: true}),
506 + );
507 + setUntrackedValue.call(input, '42');
508 + input.dispatchEvent(
509 + new Event('input', {bubbles: true, cancelable: true}),
510 + );
511 + // Should be ignored (no change):
512 + input.dispatchEvent(
513 + new Event('change', {bubbles: true, cancelable: true}),
514 + );
515 + });
516 expect(called).toBe(1);
495 - ReactDOM.unmountComponentAtNode(container);
496 - });
517 + root.unmount();
518 + }
519 });
520
521 it('should listen for both change and input events when supported', async () => {
packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js
+12 -8
@@ -158,7 +158,8 @@ describe('EnterLeaveEventPlugin', () => {
158 });
159
160 // Test for https://github.com/facebook/react/issues/16763.
161 - it('should call mouseEnter once from sibling rendered inside a rendered component in legacy roots', done => {
161 + // @gate !disableLegacyMode
162 + it('should call mouseEnter once from sibling rendered inside a rendered component in legacy roots', async () => {
163 const mockFn = jest.fn();
164
165 class Parent extends React.Component {
@@ -191,8 +192,6 @@ describe('EnterLeaveEventPlugin', () => {
192 relatedTarget: this.firstEl.current,
193 }),
194 );
194 - expect(mockFn.mock.calls.length).toBe(1);
195 - done();
195 }
196
197 render() {
@@ -205,10 +204,14 @@ describe('EnterLeaveEventPlugin', () => {
204 }
205 }
206
208 - ReactDOM.render(<Parent />, container);
207 + await act(() => {
208 + ReactDOM.render(<Parent />, container);
209 + });
210 + expect(mockFn.mock.calls.length).toBe(1);
211 });
212
211 - it('should call mouseEnter when pressing a non tracked React node in legacy root', done => {
213 + // @gate !disableLegacyMode
214 + it('should call mouseEnter when pressing a non tracked React node in legacy root', async () => {
215 const mockFn = jest.fn();
216
217 class Parent extends React.Component {
@@ -243,8 +246,6 @@ describe('EnterLeaveEventPlugin', () => {
246 relatedTarget: this.siblingEl.current,
247 }),
248 );
246 - expect(mockFn.mock.calls.length).toBe(1);
247 - done();
249 }
250
251 render() {
@@ -256,7 +257,10 @@ describe('EnterLeaveEventPlugin', () => {
257 }
258 }
259
259 - ReactDOM.render(<Parent />, container);
260 + await act(() => {
261 + ReactDOM.render(<Parent />, container);
262 + });
263 + expect(mockFn.mock.calls.length).toBe(1);
264 });
265
266 it('should work with portals outside of the root that has onMouseLeave', async () => {
packages/react-dom/unstable_testing.experimental.js
-4
@@ -11,11 +11,7 @@ export {
11 createPortal,
12 findDOMNode,
13 flushSync,
14 - hydrate,
15 - render,
16 - unmountComponentAtNode,
14 unstable_batchedUpdates,
18 - unstable_renderSubtreeIntoContainer,
15 useFormStatus,
16 useFormState,
17 prefetchDNS,
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+17
@@ -399,6 +399,7 @@ describe('ReactSuspense', () => {
399 },
400 );
401
402 + // @gate !disableLegacyMode
403 it('mounts a lazy class component in non-concurrent mode (legacy)', async () => {
404 class Class extends React.Component {
405 componentDidMount() {
@@ -685,6 +686,7 @@ describe('ReactSuspense', () => {
686 });
687
688 describe('outside concurrent mode (legacy)', () => {
689 + // @gate !disableLegacyMode
690 it('a mounted class component can suspend without losing state', async () => {
691 class TextWithLifecycle extends React.Component {
692 componentDidMount() {
@@ -763,6 +765,7 @@ describe('ReactSuspense', () => {
765 expect(container.textContent).toEqual('AB:2C');
766 });
767
768 + // @gate !disableLegacyMode
769 it('bails out on timed-out primary children even if they receive an update', async () => {
770 let instance;
771 class Stateful extends React.Component {
@@ -803,6 +806,7 @@ describe('ReactSuspense', () => {
806 expect(container.textContent).toEqual('Stateful: 2B');
807 });
808
809 + // @gate !disableLegacyMode
810 it('when updating a timed-out tree, always retries the suspended component', async () => {
811 let instance;
812 class Stateful extends React.Component {
@@ -858,6 +862,7 @@ describe('ReactSuspense', () => {
862 expect(container.textContent).toEqual('Stateful: 2B');
863 });
864
865 + // @gate !disableLegacyMode
866 it('suspends in a class that has componentWillUnmount and is then deleted', async () => {
867 class AsyncTextWithUnmount extends React.Component {
868 componentWillUnmount() {
@@ -884,6 +889,7 @@ describe('ReactSuspense', () => {
889 expect(container.textContent).toEqual('B');
890 });
891
892 + // @gate !disableLegacyMode
893 it('suspends in a component that also contains useEffect', async () => {
894 const {useLayoutEffect} = React;
895
@@ -911,6 +917,7 @@ describe('ReactSuspense', () => {
917 assertLog(['A', 'Did commit: A']);
918 });
919
920 + // @gate !disableLegacyMode
921 it('retries when an update is scheduled on a timed out tree', async () => {
922 let instance;
923 class Stateful extends React.Component {
@@ -958,6 +965,7 @@ describe('ReactSuspense', () => {
965 expect(container.textContent).toEqual('Step: 3');
966 });
967
968 + // @gate !disableLegacyMode
969 it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
970 let mounts = 0;
971 class ShouldMountOnce extends React.Component {
@@ -1004,6 +1012,7 @@ describe('ReactSuspense', () => {
1012 expect(mounts).toBe(1);
1013 });
1014
1015 + // @gate !disableLegacyMode
1016 it('reuses effects, including deletions, from the suspended tree', async () => {
1017 const {useState} = React;
1018
@@ -1045,6 +1054,7 @@ describe('ReactSuspense', () => {
1054 expect(container.textContent).toEqual('Tab: 2 + sibling');
1055 });
1056
1057 + // @gate !disableLegacyMode
1058 it('does not warn if a mounted component is pinged', async () => {
1059 const {useState} = React;
1060
@@ -1076,6 +1086,7 @@ describe('ReactSuspense', () => {
1086 expect(container.textContent).toEqual('Loading...');
1087 });
1088
1089 + // @gate !disableLegacyMode
1090 it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
1091 function App() {
1092 return (
@@ -1118,6 +1129,7 @@ describe('ReactSuspense', () => {
1129 ]);
1130 });
1131
1132 + // @gate !disableLegacyMode
1133 it('#14162', async () => {
1134 const {lazy} = React;
1135
@@ -1155,6 +1167,7 @@ describe('ReactSuspense', () => {
1167 ReactDOM.render(<App name="world" />, container);
1168 });
1169
1170 + // @gate !disableLegacyMode
1171 it('updates memoized child of suspense component when context updates (simple memo)', async () => {
1172 const {useContext, createContext, useState, memo} = React;
1173
@@ -1194,6 +1207,7 @@ describe('ReactSuspense', () => {
1207 expect(container.textContent).toEqual('new value');
1208 });
1209
1210 + // @gate !disableLegacyMode
1211 it('updates memoized child of suspense component when context updates (manual memo)', async () => {
1212 const {useContext, createContext, useState, memo} = React;
1213
@@ -1238,6 +1252,7 @@ describe('ReactSuspense', () => {
1252 expect(container.textContent).toEqual('new value');
1253 });
1254
1255 + // @gate !disableLegacyMode
1256 it('updates memoized child of suspense component when context updates (function)', async () => {
1257 const {useContext, createContext, useState} = React;
1258
@@ -1282,6 +1297,7 @@ describe('ReactSuspense', () => {
1297 expect(container.textContent).toEqual('new value');
1298 });
1299
1300 + // @gate !disableLegacyMode
1301 it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
1302 const {forwardRef, useContext, createContext, useState} = React;
1303
@@ -1321,6 +1337,7 @@ describe('ReactSuspense', () => {
1337 expect(container.textContent).toEqual('new value');
1338 });
1339
1340 + // @gate !disableLegacyMode
1341 it('updates context consumer within child of suspended suspense component when context updates', async () => {
1342 const {createContext, useState} = React;
1343
packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
+1
@@ -448,6 +448,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
448 expect(container.innerHTML).toBe('<h1>Hello</h1>');
449 });
450
451 + // @gate !disableLegacyMode
452 it('regression: unmount hidden tree, in legacy mode', async () => {
453 // In legacy mode, when a tree suspends and switches to a fallback, the
454 // effects are not unmounted. So we have to unmount them during a deletion.
packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
+3
@@ -262,6 +262,9 @@ describe('updaters', () => {
262 await waitForAll([]);
263 });
264
265 + // This test should be convertable to createRoot but the allScheduledTypes assertions are no longer the same
266 + // So I'm leaving it in legacy mode for now and just disabling if legacy mode is turned off
267 + // @gate !disableLegacyMode
268 it('should cover suspense pings', async () => {
269 let data = null;
270 let resolver = null;
packages/react/src/__tests__/ReactStrictMode-test.js
+2
@@ -77,6 +77,7 @@ describe('ReactStrictMode', () => {
77 });
78
79 // @gate __DEV__
80 + // @gate !disableLegacyMode
81 it('should invoke only precommit lifecycle methods twice in legacy roots', async () => {
82 let log = [];
83 let shouldComponentUpdate = false;
@@ -242,6 +243,7 @@ describe('ReactStrictMode', () => {
243 ]);
244 });
245
246 + // @gate !disableLegacyMode
247 it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
248 const {StrictMode} = React;
249
packages/shared/ReactFeatureFlags.js
+5
@@ -195,6 +195,11 @@ export const disableStringRefs = __NEXT_MAJOR__;
195 // Warn on any usage of ReactTestRenderer
196 export const enableReactTestRendererWarning = false;
197
198 +// Disables legacy mode
199 +// This allows us to land breaking changes to remove legacy mode APIs in experimental builds
200 +// before removing them in stable in the next Major
201 +export const disableLegacyMode = __NEXT_MAJOR__;
202 +
203 // -----------------------------------------------------------------------------
204 // Chopping Block
205 //
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -104,6 +104,7 @@ export const enableRefAsProp = false;
104 export const disableStringRefs = false;
105
106 export const enableReactTestRendererWarning = false;
107 +export const disableLegacyMode = false;
108
109 export const enableBigIntSupport = false;
110
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -97,6 +97,7 @@ export const disableStringRefs = false;
97 export const enableReactTestRendererWarning = false;
98
99 export const enableBigIntSupport = false;
100 +export const disableLegacyMode = false;
101
102 // Flow magic to verify the exports of this file match the original version.
103 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -101,6 +101,7 @@ export const enableRefAsProp = __NEXT_MAJOR__;
101 export const disableStringRefs = __NEXT_MAJOR__;
102 export const enableReactTestRendererWarning = false;
103 export const enableBigIntSupport = __NEXT_MAJOR__;
104 +export const disableLegacyMode = __NEXT_MAJOR__;
105
106 // Flow magic to verify the exports of this file match the original version.
107 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+1
@@ -91,6 +91,7 @@ export const enableRefAsProp = false;
91 export const disableStringRefs = false;
92
93 export const enableReactTestRendererWarning = false;
94 +export const disableLegacyMode = false;
95
96 export const enableBigIntSupport = false;
97
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -94,6 +94,7 @@ export const enableRefAsProp = false;
94 export const disableStringRefs = false;
95
96 export const enableReactTestRendererWarning = false;
97 +export const disableLegacyMode = false;
98
99 export const enableBigIntSupport = false;
100
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -126,5 +126,7 @@ export const enableBigIntSupport = false;
126 // because JSX is an extremely hot path.
127 export const disableStringRefs = false;
128
129 +export const disableLegacyMode = false;
130 +
131 // Flow magic to verify the exports of this file match the original version.
132 ((((null: any): ExportsType): FeatureFlagsType): ExportsType);
scripts/error-codes/codes.json
+2 -1
@@ -493,5 +493,6 @@
493 "505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain.",
494 "506": "Functions are not valid as a child of Client Components. This may happen if you return %s instead of <%s /> from render. Or maybe you meant to call this function rather than return it.%s",
495 "507": "Expected the last optional `callback` argument to be a function. Instead received: %s.",
496 - "508": "The first argument must be a React class instance. Instead received: %s."
496 + "508": "The first argument must be a React class instance. Instead received: %s.",
497 + "509": "ReactDOM: Unsupported Legacy Mode API."
498 }