@@ -456,3 +456,73 @@ describe('ReactDOMServerIntegration', () => {
));
});
});
+
+describe('ReactDOMServerIntegration (legacy)', () => {
+ function initModules() {
+ // Reset warning cache.
+ jest.resetModules();
+
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactDOMServer = require('react-dom/server');
+ ReactTestUtils = require('react-dom/test-utils');
+
+ // Make them available to the helpers.
+ return {
+ ReactDOM,
+ ReactDOMServer,
+ ReactTestUtils,
+ };
+ }
+
+ const {resetModules, expectMarkupMatch} =
+ ReactDOMServerIntegrationUtils(initModules);
+
+ beforeEach(() => {
+ resetModules();
+ });
+
+ it('legacy mode can explicitly ignore errors reconnecting different element types of children', () =>
+ expectMarkupMatch(
+
,
+
+
+
,
+ ));
+
+ it('legacy mode can explicitly ignore reconnecting more children', () =>
+ expectMarkupMatch(
+
,
+
,
+ ));
+
+ it('legacy mode can explicitly ignore reconnecting fewer children', () =>
+ expectMarkupMatch(
+
,
+
,
+ ));
+
+ it('legacy mode can explicitly ignore reconnecting reordered children', () =>
+ expectMarkupMatch(
+
,
+
,
+ ));
+});
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js
index 76da3e92c82ad..e5564d3d9348c 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js
@@ -12,7 +12,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -20,13 +20,13 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js
index e567ac9eac3ad..9e503be7520b2 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js
@@ -13,7 +13,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -21,13 +21,13 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
@@ -253,7 +253,7 @@ describe('ReactDOMServerIntegrationSelect', () => {
First
True
,
- 1,
+ 2,
);
expect(e.firstChild.selected).toBe(false);
expect(e.lastChild.selected).toBe(true);
@@ -268,7 +268,7 @@ describe('ReactDOMServerIntegrationSelect', () => {
First
Undefined
,
- 1,
+ 2,
);
expect(e.firstChild.selected).toBe(true);
expect(e.lastChild.selected).toBe(false);
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
index f3a8b869ad818..8ea1c9d53baee 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
@@ -13,7 +13,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
let forwardRef;
@@ -26,7 +26,7 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
forwardRef = React.forwardRef;
@@ -44,7 +44,7 @@ function initModules() {
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js
index 697ec7f340d88..dd19385e62c56 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js
@@ -13,7 +13,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -21,13 +21,13 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.js
index 121ffe93ad25a..55336a2cb5636 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.js
@@ -15,7 +15,6 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -35,7 +34,6 @@ describe('ReactDOMServerIntegration - Untrusted URLs', () => {
function initModules() {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
@@ -43,7 +41,7 @@ describe('ReactDOMServerIntegration - Untrusted URLs', () => {
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
@@ -204,7 +202,6 @@ describe('ReactDOMServerIntegration - Untrusted URLs - disableJavaScriptURLs', (
ReactFeatureFlags.disableJavaScriptURLs = true;
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
@@ -212,7 +209,7 @@ describe('ReactDOMServerIntegration - Untrusted URLs - disableJavaScriptURLs', (
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js
index b335b03b01d38..bc5980f23dda2 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js
@@ -12,7 +12,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -20,13 +20,13 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index f373213b099bf..8d0023b6f2078 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -776,14 +776,7 @@ describe('ReactDOMServerPartialHydration', () => {
const span2 = container.getElementsByTagName('span')[0];
// This is a new node.
expect(span).not.toBe(span2);
-
- if (gate(flags => flags.dfsEffectsRefactor)) {
- // The effects list refactor causes this to be null because the Suspense Activity's child
- // is null. However, since we can't hydrate Suspense in legacy this change in behavior is ok
- expect(ref.current).toBe(null);
- } else {
- expect(ref.current).toBe(span2);
- }
+ expect(ref.current).toBe(null);
// Resolving the promise should render the final content.
suspend = false;
diff --git a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
index 0a9e78fd8c225..537e448f86dd8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js
@@ -11,6 +11,7 @@
let React;
let ReactDOM;
+let ReactDOMClient;
let Suspense;
let Scheduler;
let act;
@@ -23,6 +24,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
Suspense = React.Suspense;
@@ -98,7 +100,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
return text;
}
- it('hides and unhides timed out DOM elements', async () => {
+ it('hides and unhides timed out DOM elements in legacy roots', async () => {
const divs = [
React.createRef(null),
React.createRef(null),
@@ -144,18 +146,22 @@ describe('ReactDOMSuspensePlaceholder', () => {
);
}
- ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
);
+ });
+
expect(container.textContent).toEqual('Loading...');
- await act(async () => {
- await resolveText('B');
+ await act(() => {
+ resolveText('B');
});
expect(container.textContent).toEqual('ABC');
});
it(
- 'outside concurrent mode, re-hides children if their display is updated ' +
+ 'in legacy roots, re-hides children if their display is updated ' +
'but the boundary is still showing the fallback',
async () => {
const {useState} = React;
@@ -207,7 +213,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
);
// Regression test for https://github.com/facebook/react/issues/14188
- it('can call findDOMNode() in a suspended component commit phase', async () => {
+ it('can call findDOMNode() in a suspended component commit phase in legacy roots', async () => {
const log = [];
const Lazy = React.lazy(
() =>
@@ -267,7 +273,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
});
// Regression test for https://github.com/facebook/react/issues/14188
- it('can call findDOMNode() in a suspended component commit phase (#2)', () => {
+ it('can call legacy findDOMNode() in a suspended component commit phase (#2)', async () => {
let suspendOnce = Promise.resolve();
function Suspend() {
if (suspendOnce) {
@@ -304,9 +310,16 @@ describe('ReactDOMSuspensePlaceholder', () => {
);
}
- ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
);
+ });
+
expect(log).toEqual(['cDM']);
- ReactDOM.render(
, container);
+ await act(() => {
+ root.render(
);
+ });
+
expect(log).toEqual(['cDM', 'cDU']);
});
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js b/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js
index b949e0ab522f9..cf51eff4aced3 100644
--- a/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js
@@ -13,7 +13,7 @@
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactDOMServer;
let ReactTestUtils;
@@ -21,13 +21,13 @@ function initModules() {
// Reset warning cache.
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
// Make them available to the helpers.
return {
- ReactDOM,
+ ReactDOMClient,
ReactDOMServer,
ReactTestUtils,
};
diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
index 040234f5c8059..403a3de7b6b17 100644
--- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
@@ -12,6 +12,7 @@
let PropTypes;
let React;
let ReactDOM;
+let ReactDOMClient;
let act;
let ReactFeatureFlags;
let Scheduler;
@@ -44,6 +45,7 @@ describe('ReactErrorBoundaries', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
React = require('react');
act = require('internal-test-utils').act;
Scheduler = require('scheduler');
@@ -576,103 +578,139 @@ describe('ReactErrorBoundaries', () => {
};
});
- it('does not swallow exceptions on mounting without boundaries', () => {
+ it('does not swallow exceptions on mounting without boundaries', async () => {
let container = document.createElement('div');
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ let root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
container = document.createElement('div');
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
container = document.createElement('div');
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
});
- it('does not swallow exceptions on updating without boundaries', () => {
+ it('does not swallow exceptions on updating without boundaries', async () => {
let container = document.createElement('div');
- ReactDOM.render(
, container);
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ let root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
);
+ });
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
container = document.createElement('div');
- ReactDOM.render(
, container);
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
);
+ });
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
container = document.createElement('div');
- ReactDOM.render(
, container);
- expect(() => {
- ReactDOM.render(
, container);
- }).toThrow('Hello');
+ root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
);
+ });
+ await expect(async () => {
+ await act(async () => {
+ root.render(
);
+ });
+ }).rejects.toThrow('Hello');
});
- it('does not swallow exceptions on unmounting without boundaries', () => {
+ it('does not swallow exceptions on unmounting without boundaries', async () => {
const container = document.createElement('div');
- ReactDOM.render(
, container);
- expect(() => {
- ReactDOM.unmountComponentAtNode(container);
- }).toThrow('Hello');
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
);
+ });
+ await expect(async () => {
+ root.unmount();
+ }).rejects.toThrow('Hello');
});
- it('prevents errors from leaking into other roots', () => {
+ it('prevents errors from leaking into other roots', async () => {
const container1 = document.createElement('div');
+ const root1 = ReactDOMClient.createRoot(container1);
const container2 = document.createElement('div');
+ const root2 = ReactDOMClient.createRoot(container2);
const container3 = document.createElement('div');
+ const root3 = ReactDOMClient.createRoot(container3);
- ReactDOM.render(
Before 1 , container1);
- expect(() => {
- ReactDOM.render(
, container2);
- }).toThrow('Hello');
- ReactDOM.render(
-
-
- ,
- container3,
- );
+ await act(async () => {
+ root1.render(
Before 1 );
+ });
+ await expect(async () => {
+ await act(async () => {
+ root2.render(
);
+ });
+ }).rejects.toThrow('Hello');
+ await act(async () => {
+ root3.render(
+
+
+ ,
+ );
+ });
expect(container1.firstChild.textContent).toBe('Before 1');
expect(container2.firstChild).toBe(null);
expect(container3.firstChild.textContent).toBe('Caught an error: Hello.');
- ReactDOM.render(
After 1 , container1);
- ReactDOM.render(
After 2 , container2);
- ReactDOM.render(
-
After 3 ,
- container3,
- );
+ await act(async () => {
+ root1.render(
After 1 );
+ });
+ await act(async () => {
+ root2.render(
After 2 );
+ });
+ await act(async () => {
+ root3.render(
After 3 );
+ });
expect(container1.firstChild.textContent).toBe('After 1');
expect(container2.firstChild.textContent).toBe('After 2');
expect(container3.firstChild.textContent).toBe('After 3');
-
- ReactDOM.unmountComponentAtNode(container1);
- ReactDOM.unmountComponentAtNode(container2);
- ReactDOM.unmountComponentAtNode(container3);
+ root1.unmount();
+ root2.unmount();
+ root3.unmount();
expect(container1.firstChild).toBe(null);
expect(container2.firstChild).toBe(null);
expect(container3.firstChild).toBe(null);
});
- it('logs a single error when using error boundary', () => {
+ it('logs a single error when using error boundary', async () => {
const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
spyOnDev(console, 'error');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
if (__DEV__) {
- expect(console.error).toHaveBeenCalledTimes(2);
+ expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error.mock.calls[0][0]).toContain(
- 'ReactDOM.render is no longer supported',
- );
- expect(console.error.mock.calls[1][0]).toContain(
'The above error occurred in the
component:',
);
}
@@ -689,21 +727,33 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('renders an error state if child throws in render', () => {
+ it('renders an error state if child throws in render', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -716,21 +766,33 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('renders an error state if child throws in constructor', () => {
+ it('renders an error state if child throws in constructor', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -741,21 +803,31 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenConstructor constructor [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('renders an error state if child throws in componentWillMount', () => {
+ it('renders an error state if child throws in componentWillMount', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -767,15 +839,24 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenComponentWillMount constructor',
+ 'BrokenComponentWillMount componentWillMount [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
// @gate !disableLegacyContext || !__DEV__
- it('renders an error state if context provider throws in componentWillMount', () => {
+ it('renders an error state if context provider throws in componentWillMount', async () => {
class BrokenComponentWillMountWithContext extends React.Component {
static childContextTypes = {foo: PropTypes.number};
getChildContext() {
@@ -790,18 +871,20 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
// @gate !disableModulePatternComponents
// @gate !disableLegacyContext
- it('renders an error state if module-style context provider throws in componentWillMount', () => {
+ it('renders an error state if module-style context provider throws in componentWillMount', async () => {
function BrokenComponentWillMountWithContext() {
return {
getChildContext() {
@@ -820,13 +903,16 @@ describe('ReactErrorBoundaries', () => {
};
const container = document.createElement('div');
- expect(() =>
- ReactDOM.render(
-
-
- ,
- container,
- ),
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ async () =>
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ }),
).toErrorDev(
'Warning: The component appears to be a function component that ' +
'returns a class instance. ' +
@@ -839,19 +925,34 @@ describe('ReactErrorBoundaries', () => {
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
- it('mounts the error message if mounting fails', () => {
+ it('mounts the error message if mounting fails', async () => {
function renderError(error) {
return ;
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
assertLog([
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
+ 'ErrorMessage constructor',
+ 'ErrorMessage componentWillMount',
+ 'ErrorMessage render',
+ // logs for error retry
'ErrorBoundary constructor',
'ErrorBoundary componentWillMount',
'ErrorBoundary render success',
@@ -868,23 +969,25 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'ErrorBoundary componentWillUnmount',
'ErrorMessage componentWillUnmount',
]);
});
- it('propagates errors on retry on mounting', () => {
+ it('propagates errors on retry on mounting', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -907,21 +1010,42 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'RetryErrorBoundary constructor',
+ 'RetryErrorBoundary componentWillMount',
+ 'RetryErrorBoundary render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'RetryErrorBoundary static getDerivedStateFromError [!]',
+ 'RetryErrorBoundary componentWillMount',
+ 'RetryErrorBoundary render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('propagates errors inside boundary during componentWillMount', () => {
+ it('propagates errors inside boundary during componentWillMount', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -933,23 +1057,34 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenComponentWillMountErrorBoundary constructor',
+ 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('propagates errors inside boundary while rendering error state', () => {
+ it('propagates errors inside boundary while rendering error state', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -969,23 +1104,41 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRenderErrorBoundary constructor',
+ 'BrokenRenderErrorBoundary componentWillMount',
+ 'BrokenRenderErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'BrokenRenderErrorBoundary static getDerivedStateFromError',
+ 'BrokenRenderErrorBoundary componentWillMount',
+ 'BrokenRenderErrorBoundary render error [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('does not call componentWillUnmount when aborting initial mount', () => {
+ it('does not call componentWillUnmount when aborting initial mount', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -1004,14 +1157,27 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'Normal constructor',
+ 'Normal componentWillMount',
+ 'Normal render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('resets callback refs if mounting aborts', () => {
+ it('resets callback refs if mounting aborts', async () => {
function childRef(x) {
Scheduler.log('Child ref is set to ' + x);
}
@@ -1020,13 +1186,15 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -1039,29 +1207,41 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'Error message ref is set to [object HTMLDivElement]',
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'ErrorBoundary componentWillUnmount',
'Error message ref is set to null',
]);
});
- it('resets object refs if mounting aborts', () => {
+ it('resets object refs if mounting aborts', async () => {
const childRef = React.createRef();
const errorMessageRef = React.createRef();
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -1074,25 +1254,37 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillMount',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render error',
'ErrorBoundary componentDidMount',
]);
expect(errorMessageRef.current.toString()).toEqual(
'[object HTMLDivElement]',
);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
expect(errorMessageRef.current).toEqual(null);
});
- it('successfully mounts if no error occurs', () => {
+ it('successfully mounts if no error occurs', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
- Mounted successfully.
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+ Mounted successfully.
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Mounted successfully.');
assertLog([
'ErrorBoundary constructor',
@@ -1101,27 +1293,30 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidMount',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches if child throws in constructor during update', () => {
+ it('catches if child throws in constructor during update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1141,32 +1336,49 @@ describe('ReactErrorBoundaries', () => {
// Render the error message
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'Normal componentWillReceiveProps',
+ 'Normal componentWillUpdate',
+ 'Normal render',
+ 'Normal2 constructor',
+ 'Normal2 componentWillMount',
+ 'Normal2 render',
+ 'BrokenConstructor constructor [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
'Normal componentWillUnmount',
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches if child throws in componentWillMount during update', () => {
+ it('catches if child throws in componentWillMount during update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1187,32 +1399,50 @@ describe('ReactErrorBoundaries', () => {
// Render the error message
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'Normal componentWillReceiveProps',
+ 'Normal componentWillUpdate',
+ 'Normal render',
+ 'Normal2 constructor',
+ 'Normal2 componentWillMount',
+ 'Normal2 render',
+ 'BrokenComponentWillMount constructor',
+ 'BrokenComponentWillMount componentWillMount [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
'Normal componentWillUnmount',
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches if child throws in componentWillReceiveProps during update', () => {
+ it('catches if child throws in componentWillReceiveProps during update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1228,33 +1458,47 @@ describe('ReactErrorBoundaries', () => {
// Render the error message
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'Normal componentWillReceiveProps',
+ 'Normal componentWillUpdate',
+ 'Normal render',
+ 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
'Normal componentWillUnmount',
'BrokenComponentWillReceiveProps componentWillUnmount',
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches if child throws in componentWillUpdate during update', () => {
+ it('catches if child throws in componentWillUpdate during update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1263,10 +1507,22 @@ describe('ReactErrorBoundaries', () => {
'Normal componentWillReceiveProps',
'Normal componentWillUpdate',
'Normal render',
- // BrokenComponentWillUpdate will abort rendering:
+ // BrokenComponentWillUpdate will abort rendering:
+ 'BrokenComponentWillUpdate componentWillReceiveProps',
+ 'BrokenComponentWillUpdate componentWillUpdate [!]',
+ // Handle the error
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'Normal componentWillReceiveProps',
+ 'Normal componentWillUpdate',
+ 'Normal render',
'BrokenComponentWillUpdate componentWillReceiveProps',
'BrokenComponentWillUpdate componentWillUpdate [!]',
- // Handle the error
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
@@ -1275,28 +1531,31 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches if child throws in render during update', () => {
+ it('catches if child throws in render during update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1317,15 +1576,31 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'Normal componentWillReceiveProps',
+ 'Normal componentWillUpdate',
+ 'Normal render',
+ 'Normal2 constructor',
+ 'Normal2 componentWillMount',
+ 'Normal2 render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
'Normal componentWillUnmount',
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('keeps refs up-to-date during updates', () => {
+ it('keeps refs up-to-date during updates', async () => {
function child1Ref(x) {
Scheduler.log('Child1 ref is set to ' + x);
}
@@ -1337,12 +1612,14 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
assertLog([
'ErrorBoundary constructor',
'ErrorBoundary componentWillMount',
@@ -1351,14 +1628,15 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidMount',
]);
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1372,6 +1650,16 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary static getDerivedStateFromError',
'ErrorBoundary componentWillUpdate',
'ErrorBoundary render error',
+ // logs for error retry
+ 'ErrorBoundary componentWillReceiveProps',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render success',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'ErrorBoundary static getDerivedStateFromError',
+ 'ErrorBoundary componentWillUpdate',
+ 'ErrorBoundary render error',
// Update Child1 ref since Child1 has been unmounted
// Child2 ref is never set because its mounting aborted
'Child1 ref is set to null',
@@ -1379,31 +1667,34 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'ErrorBoundary componentWillUnmount',
'Error message ref is set to null',
]);
});
- it('recovers from componentWillUnmount errors on update', () => {
+ it('recovers from componentWillUnmount errors on update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1437,31 +1728,34 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('recovers from nested componentWillUnmount errors on update', () => {
+ it('recovers from nested componentWillUnmount errors on update', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
-
-
- ,
- container,
- );
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1496,11 +1790,11 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('picks the right boundary when handling unmounting errors', () => {
+ it('picks the right boundary when handling unmounting errors', async () => {
function renderInnerError(error) {
return Caught an inner error: {error.message}.
;
}
@@ -1509,31 +1803,34 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
-
-
- ,
- container,
- );
+ logName="OuterErrorBoundary"
+ renderError={renderOuterError}>
+
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
+ await act(async () => {
+ root.render(
- ,
- container,
- );
+ logName="OuterErrorBoundary"
+ renderError={renderOuterError}>
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an inner error: Hello.');
assertLog([
// Update outer boundary
@@ -1559,39 +1856,43 @@ describe('ReactErrorBoundaries', () => {
'InnerErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'OuterErrorBoundary componentWillUnmount',
'InnerErrorBoundary componentWillUnmount',
]);
});
- it('can recover from error state', () => {
+ it('can recover from error state', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
// Error boundary doesn't retry by itself:
expect(container.textContent).toBe('Caught an error: Hello.');
// Force the success path:
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.textContent).not.toContain('Caught an error');
assertLog([
'ErrorBoundary componentWillReceiveProps',
@@ -1606,75 +1907,88 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'ErrorBoundary componentWillUnmount',
'Normal componentWillUnmount',
]);
});
- it('can update multiple times in error state', () => {
+ it('can update multiple times in error state', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
- ReactDOM.render(Other screen
, container);
+ await act(async () => {
+ root.render(Other screen
);
+ });
expect(container.textContent).toBe('Other screen');
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
});
- it("doesn't get into inconsistent state during removals", () => {
+ it("doesn't get into inconsistent state during removals", async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
- ReactDOM.render( , container);
+ await act(async () => {
+ root.render( );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
Scheduler.unstable_clearLog();
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it("doesn't get into inconsistent state during additions", () => {
+ it("doesn't get into inconsistent state during additions", async () => {
const container = document.createElement('div');
- ReactDOM.render( , container);
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
Scheduler.unstable_clearLog();
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it("doesn't get into inconsistent state during reorders", () => {
+ it("doesn't get into inconsistent state during reorders", async () => {
function getAMixOfNormalAndBrokenRenderElements() {
const elements = [];
for (let i = 0; i < 100; i++) {
@@ -1704,25 +2018,32 @@ describe('ReactErrorBoundaries', () => {
let fail = false;
const container = document.createElement('div');
- ReactDOM.render(
- {getAMixOfNormalAndBrokenRenderElements()} ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+ {getAMixOfNormalAndBrokenRenderElements()}
+ ,
+ );
+ });
expect(container.textContent).not.toContain('Caught an error');
fail = true;
- ReactDOM.render(
- {getAMixOfNormalAndBrokenRenderElements()} ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+ {getAMixOfNormalAndBrokenRenderElements()}
+ ,
+ );
+ });
expect(container.textContent).toBe('Caught an error: Hello.');
Scheduler.unstable_clearLog();
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches errors originating downstream', () => {
+ it('catches errors originating downstream', async () => {
let fail = false;
class Stateful extends React.Component {
state = {shouldThrow: false};
@@ -1738,43 +2059,38 @@ describe('ReactErrorBoundaries', () => {
let statefulInst;
const container = document.createElement('div');
- ReactDOM.render(
-
- (statefulInst = inst)} />
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+ (statefulInst = inst)} />
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
expect(() => {
fail = true;
statefulInst.forceUpdate();
}).not.toThrow();
-
- assertLog([
- 'Stateful render [!]',
- 'ErrorBoundary static getDerivedStateFromError',
- 'ErrorBoundary componentWillUpdate',
- 'ErrorBoundary render error',
- 'ErrorBoundary componentDidUpdate',
- ]);
-
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches errors in componentDidMount', () => {
+ it('catches errors in componentDidMount', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+
+
+ ,
+ );
+ });
assertLog([
'ErrorBoundary constructor',
'ErrorBoundary componentWillMount',
@@ -1817,26 +2133,29 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('catches errors in componentDidUpdate', () => {
+ it('catches errors in componentDidUpdate', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
assertLog([
'ErrorBoundary componentWillReceiveProps',
'ErrorBoundary componentWillUpdate',
@@ -1855,33 +2174,29 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
it('catches errors in useEffect', async () => {
const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
await act(() => {
- ReactDOM.render(
+ root.render(
Initial value
,
- container,
);
- assertLog([
- 'ErrorBoundary constructor',
- 'ErrorBoundary componentWillMount',
- 'ErrorBoundary render success',
- 'BrokenUseEffect render',
- 'ErrorBoundary componentDidMount',
- ]);
-
- expect(container.firstChild.textContent).toBe('Initial value');
- Scheduler.unstable_clearLog();
});
// verify flushed passive effects and handle the error
assertLog([
+ // logs for error retry
+ 'ErrorBoundary constructor',
+ 'ErrorBoundary componentWillMount',
+ 'ErrorBoundary render success',
+ 'BrokenUseEffect render',
+ 'ErrorBoundary componentDidMount',
'BrokenUseEffect useEffect [!]',
// Handle the error
'ErrorBoundary static getDerivedStateFromError',
@@ -1889,18 +2204,19 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary render error',
'ErrorBoundary componentDidUpdate',
]);
-
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
- it('catches errors in useLayoutEffect', () => {
+ it('catches errors in useLayoutEffect', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
- Initial value
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+ Initial value
+ ,
+ );
+ });
assertLog([
'ErrorBoundary constructor',
'ErrorBoundary componentWillMount',
@@ -1920,18 +2236,20 @@ describe('ReactErrorBoundaries', () => {
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
- it('propagates errors inside boundary during componentDidMount', () => {
+ it('propagates errors inside boundary during componentDidMount', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
- (
- We should never catch our own error: {error.message}.
- )}
- />
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+ (
+ We should never catch our own error: {error.message}.
+ )}
+ />
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
assertLog([
'ErrorBoundary constructor',
@@ -1952,11 +2270,11 @@ describe('ReactErrorBoundaries', () => {
'ErrorBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog(['ErrorBoundary componentWillUnmount']);
});
- it('calls static getDerivedStateFromError for each error that is captured', () => {
+ it('calls static getDerivedStateFromError for each error that is captured', async () => {
function renderUnmountError(error) {
return Caught an unmounting error: {error.message}.
;
}
@@ -1965,40 +2283,43 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
-
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+
+
+
+
+
+ ,
+ );
+ });
Scheduler.unstable_clearLog();
- ReactDOM.render(
-
-
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe(
'Caught an unmounting error: E2.' + 'Caught an updating error: E4.',
@@ -2048,7 +2369,7 @@ describe('ReactErrorBoundaries', () => {
'InnerUpdateBoundary componentDidUpdate',
]);
- ReactDOM.unmountComponentAtNode(container);
+ root.unmount();
assertLog([
'OuterErrorBoundary componentWillUnmount',
'InnerUnmountBoundary componentWillUnmount',
@@ -2056,7 +2377,7 @@ describe('ReactErrorBoundaries', () => {
]);
});
- it('discards a bad root if the root component fails', () => {
+ it('discards a bad root if the root component fails', async () => {
const X = null;
const Y = undefined;
let err1;
@@ -2064,7 +2385,13 @@ describe('ReactErrorBoundaries', () => {
try {
const container = document.createElement('div');
- expect(() => ReactDOM.render( , container)).toErrorDev(
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ async () =>
+ await act(async () => {
+ root.render( , container);
+ }),
+ ).toErrorDev(
'React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function ' +
'(for composite components) but got: null.',
@@ -2074,7 +2401,13 @@ describe('ReactErrorBoundaries', () => {
}
try {
const container = document.createElement('div');
- expect(() => ReactDOM.render( , container)).toErrorDev(
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ async () =>
+ await act(async () => {
+ root.render( , container);
+ }),
+ ).toErrorDev(
'React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function ' +
'(for composite components) but got: undefined.',
@@ -2087,19 +2420,23 @@ describe('ReactErrorBoundaries', () => {
expect(err2.message).toMatch(/got: undefined/);
});
- it('renders empty output if error boundary does not handle the error', () => {
+ it('renders empty output if error boundary does not handle the error', async () => {
const container = document.createElement('div');
- expect(() =>
- ReactDOM.render(
-
- Sibling
-
-
-
-
,
- container,
- ),
- ).toThrow('Hello');
+ const root = ReactDOMClient.createRoot(container);
+
+ await expect(async () => {
+ await act(async () => {
+ root.render(
+
+ Sibling
+
+
+
+
,
+ );
+ });
+ }).rejects.toThrow('Hello');
+
expect(container.innerHTML).toBe('');
assertLog([
'NoopErrorBoundary constructor',
@@ -2114,10 +2451,22 @@ describe('ReactErrorBoundaries', () => {
'BrokenRender constructor',
'BrokenRender componentWillMount',
'BrokenRender render [!]',
+ // logs for error retry
+ 'NoopErrorBoundary constructor',
+ 'NoopErrorBoundary componentWillMount',
+ 'NoopErrorBoundary render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
+ 'NoopErrorBoundary static getDerivedStateFromError',
+ 'NoopErrorBoundary render',
+ 'BrokenRender constructor',
+ 'BrokenRender componentWillMount',
+ 'BrokenRender render [!]',
]);
});
- it('passes first error when two errors happen in commit', () => {
+ it('passes first error when two errors happen in commit', async () => {
const errors = [];
let caughtError;
class Parent extends React.Component {
@@ -2140,10 +2489,13 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
try {
// Here, we test the behavior where there is no error boundary and we
// delegate to the host root.
- ReactDOM.render( , container);
+ await act(async () => {
+ root.render( );
+ });
} catch (e) {
if (e.message !== 'parent sad' && e.message !== 'child sad') {
throw e;
@@ -2156,19 +2508,22 @@ describe('ReactErrorBoundaries', () => {
expect(caughtError.message).toBe('child sad');
});
- it('propagates uncaught error inside unbatched initial mount', () => {
+ it('propagates uncaught error inside unbatched initial mount', async () => {
function Foo() {
throw new Error('foo error');
}
const container = document.createElement('div');
- expect(() => {
- ReactDOM.unstable_batchedUpdates(() => {
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await ReactDOM.unstable_batchedUpdates(async () => {
+ await act(async () => {
+ root.render( );
+ });
});
- }).toThrow('foo error');
+ }).rejects.toThrow('foo error');
});
- it('handles errors that occur in before-mutation commit hook', () => {
+ it('handles errors that occur in before-mutation commit hook', async () => {
const errors = [];
let caughtError;
class Parent extends React.Component {
@@ -2193,9 +2548,14 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
try {
- ReactDOM.render( , container);
+ await act(async () => {
+ root.render( );
+ });
} catch (e) {
if (e.message !== 'parent sad' && e.message !== 'child sad') {
throw e;
@@ -2208,7 +2568,7 @@ describe('ReactErrorBoundaries', () => {
expect(caughtError.message).toBe('child sad');
});
- it('should warn if an error boundary with only componentDidCatch does not update state', () => {
+ it('should warn if an error boundary with only componentDidCatch does not update state', async () => {
class InvalidErrorBoundary extends React.Component {
componentDidCatch(error, info) {
// This component does not define getDerivedStateFromError().
@@ -2225,13 +2585,15 @@ describe('ReactErrorBoundaries', () => {
};
const container = document.createElement('div');
- expect(() => {
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
}).toErrorDev(
'InvalidErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' +
'In that method, return a state update to display an error message or fallback UI.',
@@ -2239,7 +2601,7 @@ describe('ReactErrorBoundaries', () => {
expect(container.textContent).toBe('');
});
- it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', () => {
+ it('should call both componentDidCatch and getDerivedStateFromError if both exist on a component', async () => {
let componentDidCatchError, getDerivedStateFromErrorError;
class ErrorBoundaryWithBothMethods extends React.Component {
state = {error: null};
@@ -2261,34 +2623,39 @@ describe('ReactErrorBoundaries', () => {
};
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(container.textContent).toBe('ErrorBoundary');
expect(componentDidCatchError).toBe(thrownError);
expect(getDerivedStateFromErrorError).toBe(thrownError);
});
- it('should catch errors from invariants in completion phase', () => {
+ it('should catch errors from invariants in completion phase', async () => {
const container = document.createElement('div');
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toContain(
'Caught an error: input is a void element tag',
);
});
- it('should catch errors from errors in the throw phase from boundaries', () => {
+ it('should catch errors from errors in the throw phase from boundaries', async () => {
const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
const thrownError = new Error('original error');
const Throws = () => {
@@ -2304,22 +2671,24 @@ describe('ReactErrorBoundaries', () => {
}
}
- ReactDOM.render(
-
-
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
expect(container.textContent).toContain(
'Caught an error: gotta catch em all',
);
});
- it('should protect errors from errors in the stack generation', () => {
+ it('should protect errors from errors in the stack generation', async () => {
const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
const evilError = {
message: 'gotta catch em all',
@@ -2340,19 +2709,22 @@ describe('ReactErrorBoundaries', () => {
return ;
}
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await expect(async () => {
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
+ }).rejects.toThrow('gotta catch em all');
expect(container.textContent).toContain(
'Caught an error: gotta catch em all.',
);
});
- it('catches errors thrown in componentWillUnmount', () => {
+ it('catches errors thrown in componentWillUnmount', async () => {
class LocalErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
@@ -2392,16 +2764,18 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
-
- ReactDOM.render(
-
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(async () => {
+ root.render(
+
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('sibling');
expect(container.lastChild.textContent).toBe('broken');
@@ -2412,12 +2786,13 @@ describe('ReactErrorBoundaries', () => {
'BrokenComponentWillUnmount render',
]);
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
// React should skip over the unmounting boundary and find the nearest still-mounted boundary.
expect(container.firstChild.textContent).toBe('OuterFallback');
@@ -2432,7 +2807,7 @@ describe('ReactErrorBoundaries', () => {
]);
});
- it('catches errors thrown while detaching refs', () => {
+ it('catches errors thrown while detaching refs', async () => {
class LocalErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
@@ -2474,16 +2849,18 @@ describe('ReactErrorBoundaries', () => {
}
const container = document.createElement('div');
-
- ReactDOM.render(
-
-
-
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(async () => {
+ root.render(
+
+
+
+
+
+ ,
+ );
+ });
expect(container.firstChild.textContent).toBe('sibling');
expect(container.lastChild.textContent).toBe('ref');
@@ -2495,12 +2872,13 @@ describe('ReactErrorBoundaries', () => {
'LocalBrokenCallbackRef ref true',
]);
- ReactDOM.render(
-
-
- ,
- container,
- );
+ await act(async () => {
+ root.render(
+
+
+ ,
+ );
+ });
// React should skip over the unmounting boundary and find the nearest still-mounted boundary.
expect(container.firstChild.textContent).toBe('OuterFallback');
diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundariesHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundariesHooks-test.internal.js
index 63558ea98acd2..4a5679e5fcff2 100644
--- a/packages/react-dom/src/__tests__/ReactErrorBoundariesHooks-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactErrorBoundariesHooks-test.internal.js
@@ -10,16 +10,18 @@
'use strict';
let React;
-let ReactDOM;
+let ReactDOMClient;
+let act;
describe('ReactErrorBoundariesHooks', () => {
beforeEach(() => {
jest.resetModules();
- ReactDOM = require('react-dom');
React = require('react');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
});
- it('should preserve hook order if errors are caught', () => {
+ it('should preserve hook order if errors are caught', async () => {
function ErrorThrower() {
React.useMemo(() => undefined, []);
throw new Error('expected');
@@ -57,10 +59,15 @@ describe('ReactErrorBoundariesHooks', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
- expect(() => {
- ReactDOM.render( , container);
- }).not.toThrow();
+ await expect(
+ act(() => {
+ root.render( );
+ }),
+ ).resolves.not.toThrow();
});
});
diff --git a/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
index d1f5493e95f44..42908a693cce3 100644
--- a/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
+++ b/packages/react-dom/src/__tests__/ReactLegacyCompositeComponent-test.js
@@ -11,16 +11,18 @@
let React;
let ReactDOM;
-let ReactTestUtils;
+let ReactDOMClient;
let PropTypes;
+let act;
describe('ReactLegacyCompositeComponent', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
- ReactTestUtils = require('react-dom/test-utils');
+ ReactDOMClient = require('react-dom/client');
PropTypes = require('prop-types');
+ act = require('internal-test-utils').act;
});
it('should warn about `setState` in render in legacy mode', () => {
@@ -70,7 +72,7 @@ describe('ReactLegacyCompositeComponent', () => {
});
// @gate !disableLegacyContext
- it('should pass context to children when not owner', () => {
+ it('should pass context to children when not owner', async () => {
class Parent extends React.Component {
render() {
return (
@@ -106,13 +108,17 @@ describe('ReactLegacyCompositeComponent', () => {
return {this.context.foo}
;
}
}
-
- const component = ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let component;
+ await act(() => {
+ root.render( (component = current)} />);
+ });
expect(ReactDOM.findDOMNode(component).innerHTML).toBe('bar');
});
// @gate !disableLegacyContext
- it('should pass context when re-rendered for static child', () => {
+ it('should pass context when re-rendered for static child', async () => {
let parentInstance = null;
let childInstance = null;
@@ -156,24 +162,31 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- parentInstance = ReactTestUtils.renderIntoDocument(
-
-
-
-
- ,
- );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(() => {
+ root.render(
+ (parentInstance = current)}>
+
+
+
+ ,
+ );
+ });
expect(parentInstance.state.flag).toBe(false);
expect(childInstance.context).toEqual({foo: 'bar', flag: false});
- parentInstance.setState({flag: true});
+ await act(() => {
+ parentInstance.setState({flag: true});
+ });
expect(parentInstance.state.flag).toBe(true);
expect(childInstance.context).toEqual({foo: 'bar', flag: true});
});
// @gate !disableLegacyContext
- it('should pass context when re-rendered for static child within a composite component', () => {
+ it('should pass context when re-rendered for static child within a composite component', async () => {
class Parent extends React.Component {
static childContextTypes = {
flag: PropTypes.bool,
@@ -217,20 +230,27 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- const wrapper = ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let wrapper;
+ await act(() => {
+ root.render( (wrapper = current)} />);
+ });
expect(wrapper.parentRef.current.state.flag).toEqual(true);
expect(wrapper.childRef.current.context).toEqual({flag: true});
// We update while is still a static prop relative to this update
- wrapper.parentRef.current.setState({flag: false});
+ await act(() => {
+ wrapper.parentRef.current.setState({flag: false});
+ });
expect(wrapper.parentRef.current.state.flag).toEqual(false);
expect(wrapper.childRef.current.context).toEqual({flag: false});
});
// @gate !disableLegacyContext
- it('should pass context transitively', () => {
+ it('should pass context transitively', async () => {
let childInstance = null;
let grandchildInstance = null;
@@ -286,13 +306,18 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
expect(grandchildInstance.context).toEqual({foo: 'bar', depth: 1});
});
// @gate !disableLegacyContext
- it('should pass context when re-rendered', () => {
+ it('should pass context when re-rendered', async () => {
let parentInstance = null;
let childInstance = null;
@@ -334,11 +359,16 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- parentInstance = ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( (parentInstance = current)} />);
+ });
+
expect(childInstance).toBeNull();
expect(parentInstance.state.flag).toBe(false);
- ReactDOM.unstable_batchedUpdates(function () {
+ await act(() => {
parentInstance.setState({flag: true});
});
expect(parentInstance.state.flag).toBe(true);
@@ -699,7 +729,7 @@ describe('ReactLegacyCompositeComponent', () => {
);
});
- it('should replace state in legacy mode', () => {
+ it('should replace state in legacy mode', async () => {
class Moo extends React.Component {
state = {x: 1};
render() {
@@ -707,15 +737,23 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- const moo = ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let moo;
+ await act(() => {
+ root.render( (moo = current)} />);
+ });
+
// No longer a public API, but we can test that it works internally by
// reaching into the updater.
- moo.updater.enqueueReplaceState(moo, {y: 2});
+ await act(() => {
+ moo.updater.enqueueReplaceState(moo, {y: 2});
+ });
expect('x' in moo.state).toBe(false);
expect(moo.state.y).toBe(2);
});
- it('should support objects with prototypes as state in legacy mode', () => {
+ it('should support objects with prototypes as state in legacy mode', async () => {
const NotActuallyImmutable = function (str) {
this.str = str;
};
@@ -732,24 +770,34 @@ describe('ReactLegacyCompositeComponent', () => {
}
}
- const moo = ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let moo;
+ await act(() => {
+ root.render( (moo = current)} />);
+ });
+
expect(moo.state.str).toBe('first');
expect(moo.state.amIImmutable()).toBe(true);
const secondState = new NotActuallyImmutable('second');
- moo._replaceState(secondState);
+ await act(() => {
+ moo._replaceState(secondState);
+ });
expect(moo.state.str).toBe('second');
expect(moo.state.amIImmutable()).toBe(true);
expect(moo.state).toBe(secondState);
- moo.setState({str: 'third'});
+ await act(() => {
+ moo.setState({str: 'third'});
+ });
expect(moo.state.str).toBe('third');
// Here we lose the prototype.
expect(moo.state.amIImmutable).toBe(undefined);
// When more than one state update is enqueued, we have the same behavior
const fifthState = new NotActuallyImmutable('fifth');
- ReactDOM.unstable_batchedUpdates(function () {
+ await act(() => {
moo.setState({str: 'fourth'});
moo._replaceState(fifthState);
});
@@ -757,7 +805,7 @@ describe('ReactLegacyCompositeComponent', () => {
// When more than one state update is enqueued, we have the same behavior
const sixthState = new NotActuallyImmutable('sixth');
- ReactDOM.unstable_batchedUpdates(function () {
+ await act(() => {
moo._replaceState(sixthState);
moo.setState({str: 'seventh'});
});
diff --git a/packages/react-dom/src/__tests__/ReactMultiChildReconcile-test.js b/packages/react-dom/src/__tests__/ReactMultiChildReconcile-test.js
index 0bfb27ade197b..3a2a5126882ce 100644
--- a/packages/react-dom/src/__tests__/ReactMultiChildReconcile-test.js
+++ b/packages/react-dom/src/__tests__/ReactMultiChildReconcile-test.js
@@ -10,7 +10,8 @@
'use strict';
const React = require('react');
-const ReactDOM = require('react-dom');
+const ReactDOMClient = require('react-dom/client');
+const act = require('internal-test-utils').act;
const stripEmptyValues = function (obj) {
const ret = {};
@@ -221,24 +222,40 @@ function verifyDomOrderingAccurate(outerContainer, statusDisplays) {
expect(orderedDomKeys).toEqual(orderedLogicalKeys);
}
-function testPropsSequenceWithPreparedChildren(sequence, prepareChildren) {
+async function testPropsSequenceWithPreparedChildren(
+ sequence,
+ prepareChildren,
+) {
const container = document.createElement('div');
- const parentInstance = ReactDOM.render(
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ let parentInstance;
+ await act(() => {
+ root.render(
+ {
+ if (parentInstance === undefined) {
+ parentInstance = current;
+ }
+ }}
+ />,
+ );
+ });
let statusDisplays = parentInstance.getStatusDisplays();
let lastInternalStates = getInternalStateByUserName(statusDisplays);
verifyStatuses(statusDisplays, sequence[0]);
for (let i = 1; i < sequence.length; i++) {
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
verifyStatuses(statusDisplays, sequence[i]);
verifyStatesPreserved(lastInternalStates, statusDisplays);
@@ -274,13 +291,13 @@ function prepareChildrenModernIterable(childrenArray) {
};
}
-function testPropsSequence(sequence) {
- testPropsSequenceWithPreparedChildren(sequence, prepareChildrenArray);
- testPropsSequenceWithPreparedChildren(
+async function testPropsSequence(sequence) {
+ await testPropsSequenceWithPreparedChildren(sequence, prepareChildrenArray);
+ await testPropsSequenceWithPreparedChildren(
sequence,
prepareChildrenLegacyIterable,
);
- testPropsSequenceWithPreparedChildren(
+ await testPropsSequenceWithPreparedChildren(
sequence,
prepareChildrenModernIterable,
);
@@ -291,7 +308,7 @@ describe('ReactMultiChildReconcile', () => {
jest.resetModules();
});
- it('should reset internal state if removed then readded in an array', () => {
+ it('should reset internal state if removed then readded in an array', async () => {
// Test basics.
const props = {
usernameToStatus: {
@@ -300,32 +317,44 @@ describe('ReactMultiChildReconcile', () => {
};
const container = document.createElement('div');
- const parentInstance = ReactDOM.render(
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ let parentInstance;
+ await act(() => {
+ root.render(
+ {
+ if (parentInstance === undefined) {
+ parentInstance = current;
+ }
+ }}
+ />,
+ );
+ });
let statusDisplays = parentInstance.getStatusDisplays();
const startingInternalState = statusDisplays.jcw.getInternalState();
// Now remove the child.
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeFalsy();
// Now reset the props that cause there to be a child
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeTruthy();
expect(statusDisplays.jcw.getInternalState()).not.toBe(
@@ -333,7 +362,7 @@ describe('ReactMultiChildReconcile', () => {
);
});
- it('should reset internal state if removed then readded in a legacy iterable', () => {
+ it('should reset internal state if removed then readded in a legacy iterable', async () => {
// Test basics.
const props = {
usernameToStatus: {
@@ -342,32 +371,47 @@ describe('ReactMultiChildReconcile', () => {
};
const container = document.createElement('div');
- const parentInstance = ReactDOM.render(
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ let parentInstance;
+ await act(() => {
+ root.render(
+ {
+ if (parentInstance === undefined) {
+ parentInstance = current;
+ }
+ }}
+ />,
+ );
+ });
+
let statusDisplays = parentInstance.getStatusDisplays();
const startingInternalState = statusDisplays.jcw.getInternalState();
// Now remove the child.
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeFalsy();
// Now reset the props that cause there to be a child
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeTruthy();
expect(statusDisplays.jcw.getInternalState()).not.toBe(
@@ -375,7 +419,7 @@ describe('ReactMultiChildReconcile', () => {
);
});
- it('should reset internal state if removed then readded in a modern iterable', () => {
+ it('should reset internal state if removed then readded in a modern iterable', async () => {
// Test basics.
const props = {
usernameToStatus: {
@@ -384,32 +428,47 @@ describe('ReactMultiChildReconcile', () => {
};
const container = document.createElement('div');
- const parentInstance = ReactDOM.render(
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ let parentInstance;
+ await act(() => {
+ root.render(
+ {
+ if (parentInstance === undefined) {
+ parentInstance = current;
+ }
+ }}
+ />,
+ );
+ });
+
let statusDisplays = parentInstance.getStatusDisplays();
const startingInternalState = statusDisplays.jcw.getInternalState();
// Now remove the child.
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeFalsy();
// Now reset the props that cause there to be a child
- ReactDOM.render(
- ,
- container,
- );
+ await act(() => {
+ root.render(
+ ,
+ );
+ });
+
statusDisplays = parentInstance.getStatusDisplays();
expect(statusDisplays.jcw).toBeTruthy();
expect(statusDisplays.jcw.getInternalState()).not.toBe(
@@ -417,7 +476,7 @@ describe('ReactMultiChildReconcile', () => {
);
});
- it('should create unique identity', () => {
+ it('should create unique identity', async () => {
// Test basics.
const usernameToStatus = {
jcw: 'jcwStatus',
@@ -425,10 +484,10 @@ describe('ReactMultiChildReconcile', () => {
bob: 'bobStatus',
};
- testPropsSequence([{usernameToStatus: usernameToStatus}]);
+ await testPropsSequence([{usernameToStatus: usernameToStatus}]);
});
- it('should preserve order if children order has not changed', () => {
+ it('should preserve order if children order has not changed', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -443,10 +502,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should transition from zero to one children correctly', () => {
+ it('should transition from zero to one children correctly', async () => {
const PROPS_SEQUENCE = [
{usernameToStatus: {}},
{
@@ -455,10 +514,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should transition from one to zero children correctly', () => {
+ it('should transition from one to zero children correctly', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -467,11 +526,11 @@ describe('ReactMultiChildReconcile', () => {
},
{usernameToStatus: {}},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should transition from one child to null children', () => {
- testPropsSequence([
+ it('should transition from one child to null children', async () => {
+ await testPropsSequence([
{
usernameToStatus: {
first: 'firstStatus',
@@ -481,8 +540,8 @@ describe('ReactMultiChildReconcile', () => {
]);
});
- it('should transition from null children to one child', () => {
- testPropsSequence([
+ it('should transition from null children to one child', async () => {
+ await testPropsSequence([
{},
{
usernameToStatus: {
@@ -492,8 +551,8 @@ describe('ReactMultiChildReconcile', () => {
]);
});
- it('should transition from zero children to null children', () => {
- testPropsSequence([
+ it('should transition from zero children to null children', async () => {
+ await testPropsSequence([
{
usernameToStatus: {},
},
@@ -501,8 +560,8 @@ describe('ReactMultiChildReconcile', () => {
]);
});
- it('should transition from null children to zero children', () => {
- testPropsSequence([
+ it('should transition from null children to zero children', async () => {
+ await testPropsSequence([
{},
{
usernameToStatus: {},
@@ -514,7 +573,7 @@ describe('ReactMultiChildReconcile', () => {
* `FriendsStatusDisplay` renders nulls as empty children (it's a convention
* of `FriendsStatusDisplay`, nothing related to React or these test cases.
*/
- it('should remove nulled out children at the beginning', () => {
+ it('should remove nulled out children at the beginning', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -529,10 +588,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should remove nulled out children at the end', () => {
+ it('should remove nulled out children at the end', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -547,10 +606,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should reverse the order of two children', () => {
+ it('should reverse the order of two children', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -565,10 +624,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should reverse the order of more than two children', () => {
+ it('should reverse the order of more than two children', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -585,10 +644,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should cycle order correctly', () => {
+ it('should cycle order correctly', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -632,10 +691,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should cycle order correctly in the other direction', () => {
+ it('should cycle order correctly in the other direction', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -679,10 +738,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should remove nulled out children and ignore new null children', () => {
+ it('should remove nulled out children and ignore new null children', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -698,10 +757,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should remove nulled out children and reorder remaining', () => {
+ it('should remove nulled out children and reorder remaining', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -719,10 +778,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should append children to the end', () => {
+ it('should append children to the end', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -738,10 +797,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should append multiple children to the end', () => {
+ it('should append multiple children to the end', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -758,10 +817,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should prepend children to the beginning', () => {
+ it('should prepend children to the beginning', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -777,10 +836,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should prepend multiple children to the beginning', () => {
+ it('should prepend multiple children to the beginning', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -797,10 +856,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should not prepend an empty child to the beginning', () => {
+ it('should not prepend an empty child to the beginning', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -816,10 +875,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should not append an empty child to the end', () => {
+ it('should not append an empty child to the end', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -835,10 +894,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should not insert empty children in the middle', () => {
+ it('should not insert empty children in the middle', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -856,10 +915,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should insert one new child in the middle', () => {
+ it('should insert one new child in the middle', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -875,10 +934,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should insert multiple new truthy children in the middle', () => {
+ it('should insert multiple new truthy children in the middle', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -896,10 +955,10 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
- it('should insert non-empty children in middle where nulls were', () => {
+ it('should insert non-empty children in middle where nulls were', async () => {
const PROPS_SEQUENCE = [
{
usernameToStatus: {
@@ -920,6 +979,6 @@ describe('ReactMultiChildReconcile', () => {
},
},
];
- testPropsSequence(PROPS_SEQUENCE);
+ await testPropsSequence(PROPS_SEQUENCE);
});
});
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js
index d7ed05673c67f..45babfd4032d3 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsActUnmockedScheduler-test.js
@@ -32,7 +32,7 @@ beforeEach(() => {
yields = [];
React = require('react');
ReactDOMClient = require('react-dom/client');
- act = React.unstable_act;
+ act = React.act;
container = document.createElement('div');
document.body.appendChild(container);
});
diff --git a/packages/react-dom/src/__tests__/multiple-copies-of-react-test.js b/packages/react-dom/src/__tests__/multiple-copies-of-react-test.js
index 1de130b2fed2d..96647eed81cf4 100644
--- a/packages/react-dom/src/__tests__/multiple-copies-of-react-test.js
+++ b/packages/react-dom/src/__tests__/multiple-copies-of-react-test.js
@@ -10,7 +10,8 @@
'use strict';
let React = require('react');
-const ReactTestUtils = require('react-dom/test-utils');
+const ReactDOMClient = require('react-dom/client');
+const act = require('internal-test-utils').act;
class TextWithStringRef extends React.Component {
render() {
@@ -21,10 +22,14 @@ class TextWithStringRef extends React.Component {
}
describe('when different React version is used with string ref', () => {
- it('throws the "Refs must have owner" warning', () => {
- expect(() => {
- ReactTestUtils.renderIntoDocument( );
- }).toThrow(
+ it('throws the "Refs must have owner" warning', async () => {
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ act(() => {
+ root.render( );
+ }),
+ ).rejects.toThrow(
'Element ref was specified as a string (foo) but no owner was set. This could happen for one of' +
' the following reasons:\n' +
'1. You may be adding a ref to a function component\n' +
diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
index 8392b57af03cf..e9c4479b028fb 100644
--- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
@@ -14,11 +14,12 @@ const shouldIgnoreConsoleError = require('../../../../../scripts/jest/shouldIgno
module.exports = function (initModules) {
let ReactDOM;
+ let ReactDOMClient;
let ReactDOMServer;
let act;
function resetModules() {
- ({ReactDOM, ReactDOMServer} = initModules());
+ ({ReactDOM, ReactDOMClient, ReactDOMServer} = initModules());
act = require('internal-test-utils').act;
}
@@ -51,11 +52,24 @@ module.exports = function (initModules) {
async function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
if (forceHydrate) {
await act(() => {
- ReactDOM.hydrate(reactElement, domElement);
+ if (ReactDOMClient) {
+ ReactDOMClient.hydrateRoot(domElement, reactElement, {
+ onRecoverableError: () => {
+ // TODO: assert on recoverable error count.
+ },
+ });
+ } else {
+ ReactDOM.hydrate(reactElement, domElement);
+ }
});
} else {
await act(() => {
- ReactDOM.render(reactElement, domElement);
+ if (ReactDOMClient) {
+ const root = ReactDOMClient.createRoot(domElement);
+ root.render(reactElement);
+ } else {
+ ReactDOM.render(reactElement, domElement);
+ }
});
}
}
@@ -80,7 +94,11 @@ module.exports = function (initModules) {
for (let i = 0; i < console.error.mock.calls.length; i++) {
const args = console.error.mock.calls[i];
const [format, ...rest] = args;
- if (!shouldIgnoreConsoleError(format, rest)) {
+ if (
+ !shouldIgnoreConsoleError(format, rest, {
+ TODO_ignoreHydrationErrors: true,
+ })
+ ) {
filteredWarnings.push(args);
}
}
diff --git a/packages/react-dom/src/events/__tests__/SyntheticClipboardEvent-test.js b/packages/react-dom/src/events/__tests__/SyntheticClipboardEvent-test.js
index 30064ecad99d8..aae6b02239eba 100644
--- a/packages/react-dom/src/events/__tests__/SyntheticClipboardEvent-test.js
+++ b/packages/react-dom/src/events/__tests__/SyntheticClipboardEvent-test.js
@@ -10,14 +10,16 @@
'use strict';
let React;
-let ReactDOM;
+let ReactDOMClient;
+let act;
describe('SyntheticClipboardEvent', () => {
let container;
beforeEach(() => {
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
// The container has to be attached for events to fire.
container = document.createElement('div');
@@ -32,7 +34,7 @@ describe('SyntheticClipboardEvent', () => {
describe('ClipboardEvent interface', () => {
describe('clipboardData', () => {
describe('when event has clipboardData', () => {
- it("returns event's clipboardData", () => {
+ it("returns event's clipboardData", async () => {
let expectedCount = 0;
// Mock clipboardData since jsdom implementation doesn't have a constructor
@@ -47,30 +49,39 @@ describe('SyntheticClipboardEvent', () => {
expect(event.clipboardData).toBe(clipboardData);
expectedCount++;
};
- const div = ReactDOM.render(
-
,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
,
+ );
+ });
+
+ const div = container.firstChild;
let event;
event = document.createEvent('Event');
event.initEvent('copy', true, true);
event.clipboardData = clipboardData;
- div.dispatchEvent(event);
-
+ await act(() => {
+ div.dispatchEvent(event);
+ });
event = document.createEvent('Event');
event.initEvent('cut', true, true);
event.clipboardData = clipboardData;
- div.dispatchEvent(event);
+ await act(() => {
+ div.dispatchEvent(event);
+ });
event = document.createEvent('Event');
event.initEvent('paste', true, true);
event.clipboardData = clipboardData;
- div.dispatchEvent(event);
+ await act(() => {
+ div.dispatchEvent(event);
+ });
expect(expectedCount).toBe(3);
});
@@ -79,7 +90,7 @@ describe('SyntheticClipboardEvent', () => {
});
describe('EventInterface', () => {
- it('is able to `preventDefault` and `stopPropagation`', () => {
+ it('is able to `preventDefault` and `stopPropagation`', async () => {
let expectedCount = 0;
const eventHandler = event => {
@@ -92,14 +103,19 @@ describe('SyntheticClipboardEvent', () => {
expectedCount++;
};
- const div = ReactDOM.render(
-
,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(() => {
+ root.render(
+
,
+ );
+ });
+
+ const div = container.firstChild;
let event;
event = document.createEvent('Event');
diff --git a/packages/react-dom/src/events/__tests__/SyntheticFocusEvent-test.js b/packages/react-dom/src/events/__tests__/SyntheticFocusEvent-test.js
index 9e70ca1486439..ef889c4b60d10 100644
--- a/packages/react-dom/src/events/__tests__/SyntheticFocusEvent-test.js
+++ b/packages/react-dom/src/events/__tests__/SyntheticFocusEvent-test.js
@@ -9,13 +9,15 @@
describe('SyntheticFocusEvent', () => {
let React;
- let ReactDOM;
+ let ReactDOMClient;
+ let act;
let container;
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
container = document.createElement('div');
document.body.appendChild(container);
@@ -26,44 +28,54 @@ describe('SyntheticFocusEvent', () => {
container = null;
});
- test('onFocus events have the focus type', () => {
+ test('onFocus events have the focus type', async () => {
const log = [];
- ReactDOM.render(
- log.push(`onFocus: ${event.type}`)}
- onFocusCapture={event => log.push(`onFocusCapture: ${event.type}`)}
- />,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+ log.push(`onFocus: ${event.type}`)}
+ onFocusCapture={event => log.push(`onFocusCapture: ${event.type}`)}
+ />,
+ );
+ });
+
const button = container.querySelector('button');
- button.dispatchEvent(
- new FocusEvent('focusin', {
- bubbles: true,
- cancelable: false,
- }),
- );
+ await act(() => {
+ button.dispatchEvent(
+ new FocusEvent('focusin', {
+ bubbles: true,
+ cancelable: false,
+ }),
+ );
+ });
expect(log).toEqual(['onFocusCapture: focus', 'onFocus: focus']);
});
- test('onBlur events have the blur type', () => {
+ test('onBlur events have the blur type', async () => {
const log = [];
- ReactDOM.render(
- log.push(`onBlur: ${event.type}`)}
- onBlurCapture={event => log.push(`onBlurCapture: ${event.type}`)}
- />,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+ log.push(`onBlur: ${event.type}`)}
+ onBlurCapture={event => log.push(`onBlurCapture: ${event.type}`)}
+ />,
+ );
+ });
+
const button = container.querySelector('button');
- button.dispatchEvent(
- new FocusEvent('focusout', {
- bubbles: true,
- cancelable: false,
- }),
- );
+ await act(() => {
+ button.dispatchEvent(
+ new FocusEvent('focusout', {
+ bubbles: true,
+ cancelable: false,
+ }),
+ );
+ });
expect(log).toEqual(['onBlurCapture: blur', 'onBlur: blur']);
});
diff --git a/packages/react-dom/src/events/__tests__/SyntheticMouseEvent-test.js b/packages/react-dom/src/events/__tests__/SyntheticMouseEvent-test.js
index cb927354f64d3..a20886a3e2c04 100644
--- a/packages/react-dom/src/events/__tests__/SyntheticMouseEvent-test.js
+++ b/packages/react-dom/src/events/__tests__/SyntheticMouseEvent-test.js
@@ -10,7 +10,8 @@
'use strict';
let React;
-let ReactDOM;
+let ReactDOMClient;
+let act;
describe('SyntheticMouseEvent', () => {
let container;
@@ -18,7 +19,8 @@ describe('SyntheticMouseEvent', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
// The container has to be attached for events to fire.
container = document.createElement('div');
@@ -30,7 +32,7 @@ describe('SyntheticMouseEvent', () => {
container = null;
});
- it('should only use values from movementX/Y when event type is mousemove', () => {
+ it('should only use values from movementX/Y when event type is mousemove', async () => {
const events = [];
const onMouseMove = event => {
events.push(event.movementX);
@@ -40,10 +42,11 @@ describe('SyntheticMouseEvent', () => {
events.push(event.movementX);
};
- const node = ReactDOM.render(
-
,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
);
+ });
+ const node = container.firstChild;
let event = new MouseEvent('mousemove', {
relatedTarget: null,
@@ -52,7 +55,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 2,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
event = new MouseEvent('mousemove', {
relatedTarget: null,
@@ -61,7 +66,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 8,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
// Now trigger a mousedown event to see if movementX has changed back to 0
event = new MouseEvent('mousedown', {
@@ -71,7 +78,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 65,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
expect(events.length).toBe(3);
expect(events[0]).toBe(0);
@@ -79,7 +88,7 @@ describe('SyntheticMouseEvent', () => {
expect(events[2]).toBe(0); // mousedown event should have movementX at 0
});
- it('should correctly calculate movementX/Y for capture phase', () => {
+ it('should correctly calculate movementX/Y for capture phase', async () => {
const events = [];
const onMouseMove = event => {
events.push(['move', false, event.movementX, event.movementY]);
@@ -94,15 +103,18 @@ describe('SyntheticMouseEvent', () => {
events.push(['down', true, event.movementX, event.movementY]);
};
- const node = ReactDOM.render(
-
,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
,
+ );
+ });
+ const node = container.firstChild;
let event = new MouseEvent('mousemove', {
relatedTarget: null,
@@ -111,7 +123,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 2,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
event = new MouseEvent('mousemove', {
relatedTarget: null,
@@ -120,7 +134,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 9,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
// Now trigger a mousedown event to see if movementX has changed back to 0
event = new MouseEvent('mousedown', {
@@ -130,7 +146,9 @@ describe('SyntheticMouseEvent', () => {
screenY: 65,
});
- node.dispatchEvent(event);
+ await act(() => {
+ node.dispatchEvent(event);
+ });
expect(events).toEqual([
['move', true, 0, 0],
diff --git a/packages/react-dom/src/events/plugins/__tests__/BeforeInputEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/BeforeInputEventPlugin-test.js
index dd0fb8c9231ea..353f2de4d8687 100644
--- a/packages/react-dom/src/events/plugins/__tests__/BeforeInputEventPlugin-test.js
+++ b/packages/react-dom/src/events/plugins/__tests__/BeforeInputEventPlugin-test.js
@@ -10,17 +10,21 @@
'use strict';
let React;
-let ReactDOM;
+let ReactDOMClient;
+let act;
describe('BeforeInputEventPlugin', () => {
let container;
- function loadReactDOM(envSimulator) {
+ function loadReactDOMClientAndAct(envSimulator) {
jest.resetModules();
if (envSimulator) {
envSimulator();
}
- return require('react-dom');
+ return {
+ ReactDOMClient: require('react-dom/client'),
+ act: require('internal-test-utils').act,
+ };
}
function simulateIE11() {
@@ -724,32 +728,36 @@ describe('BeforeInputEventPlugin', () => {
},
];
- const testInputComponent = (env, scenes) => {
+ const testInputComponent = async (env, scenes) => {
let beforeInputEvent;
let compositionStartEvent;
let compositionUpdateEvent;
let spyOnBeforeInput;
let spyOnCompositionStart;
let spyOnCompositionUpdate;
- ReactDOM = loadReactDOM(env.emulator);
- const node = ReactDOM.render(
- {
- spyOnBeforeInput();
- beforeInputEvent = e;
- }}
- onCompositionStart={e => {
- spyOnCompositionStart();
- compositionStartEvent = e;
- }}
- onCompositionUpdate={e => {
- spyOnCompositionUpdate();
- compositionUpdateEvent = e;
- }}
- />,
- container,
- );
+ ({ReactDOMClient, act} = loadReactDOMClientAndAct(env.emulator));
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+ {
+ spyOnBeforeInput();
+ beforeInputEvent = e;
+ }}
+ onCompositionStart={e => {
+ spyOnCompositionStart();
+ compositionStartEvent = e;
+ }}
+ onCompositionUpdate={e => {
+ spyOnCompositionUpdate();
+ compositionUpdateEvent = e;
+ }}
+ />,
+ );
+ });
+
+ const node = container.firstChild;
scenes.forEach((s, id) => {
beforeInputEvent = null;
@@ -770,32 +778,37 @@ describe('BeforeInputEventPlugin', () => {
});
};
- const testContentEditableComponent = (env, scenes) => {
+ const testContentEditableComponent = async (env, scenes) => {
let beforeInputEvent;
let compositionStartEvent;
let compositionUpdateEvent;
let spyOnBeforeInput;
let spyOnCompositionStart;
let spyOnCompositionUpdate;
- ReactDOM = loadReactDOM(env.emulator);
- const node = ReactDOM.render(
- {
- spyOnBeforeInput();
- beforeInputEvent = e;
- }}
- onCompositionStart={e => {
- spyOnCompositionStart();
- compositionStartEvent = e;
- }}
- onCompositionUpdate={e => {
- spyOnCompositionUpdate();
- compositionUpdateEvent = e;
- }}
- />,
- container,
- );
+ ({ReactDOMClient, act} = loadReactDOMClientAndAct(env.emulator));
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(() => {
+ root.render(
+
{
+ spyOnBeforeInput();
+ beforeInputEvent = e;
+ }}
+ onCompositionStart={e => {
+ spyOnCompositionStart();
+ compositionStartEvent = e;
+ }}
+ onCompositionUpdate={e => {
+ spyOnCompositionUpdate();
+ compositionUpdateEvent = e;
+ }}
+ />,
+ );
+ });
+
+ const node = container.firstChild;
scenes.forEach((s, id) => {
beforeInputEvent = null;
@@ -816,33 +829,33 @@ describe('BeforeInputEventPlugin', () => {
});
};
- it('should extract onBeforeInput when simulating in Webkit on input[type=text]', () => {
- testInputComponent(environments[0], scenarios);
+ it('should extract onBeforeInput when simulating in Webkit on input[type=text]', async () => {
+ await testInputComponent(environments[0], scenarios);
});
- it('should extract onBeforeInput when simulating in Webkit on contenteditable', () => {
- testContentEditableComponent(environments[0], scenarios);
+ it('should extract onBeforeInput when simulating in Webkit on contenteditable', async () => {
+ await testContentEditableComponent(environments[0], scenarios);
});
- it('should extract onBeforeInput when simulating in IE11 on input[type=text]', () => {
- testInputComponent(environments[1], scenarios);
+ it('should extract onBeforeInput when simulating in IE11 on input[type=text]', async () => {
+ await testInputComponent(environments[1], scenarios);
});
- it('should extract onBeforeInput when simulating in IE11 on contenteditable', () => {
- testContentEditableComponent(environments[1], scenarios);
+ it('should extract onBeforeInput when simulating in IE11 on contenteditable', async () => {
+ await testContentEditableComponent(environments[1], scenarios);
});
- it('should extract onBeforeInput when simulating in env with no CompositionEvent on input[type=text]', () => {
- testInputComponent(environments[2], scenarios);
+ it('should extract onBeforeInput when simulating in env with no CompositionEvent on input[type=text]', async () => {
+ await testInputComponent(environments[2], scenarios);
});
// in an environment using composition fallback onBeforeInput will not work
// as expected on a contenteditable as keydown and keyup events are translated
// to keypress events
- it('should extract onBeforeInput when simulating in env with only CompositionEvent on input[type=text]', () => {
- testInputComponent(environments[3], scenarios);
+ it('should extract onBeforeInput when simulating in env with only CompositionEvent on input[type=text]', async () => {
+ await testInputComponent(environments[3], scenarios);
});
- it('should extract onBeforeInput when simulating in env with only CompositionEvent on contenteditable', () => {
- testContentEditableComponent(environments[3], scenarios);
+ it('should extract onBeforeInput when simulating in env with only CompositionEvent on contenteditable', async () => {
+ await testContentEditableComponent(environments[3], scenarios);
});
});
diff --git a/packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js
index f6a9232e87c62..569a29a22a5b8 100644
--- a/packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js
+++ b/packages/react-dom/src/events/plugins/__tests__/EnterLeaveEventPlugin-test.js
@@ -11,6 +11,8 @@
let React;
let ReactDOM;
+let ReactDOMClient;
+let act;
describe('EnterLeaveEventPlugin', () => {
let container;
@@ -20,6 +22,8 @@ describe('EnterLeaveEventPlugin', () => {
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
// The container has to be attached for events to fire.
container = document.createElement('div');
@@ -31,7 +35,7 @@ describe('EnterLeaveEventPlugin', () => {
container = null;
});
- it('should set onMouseLeave relatedTarget properly in iframe', () => {
+ it('should set onMouseLeave relatedTarget properly in iframe', async () => {
const iframe = document.createElement('iframe');
container.appendChild(iframe);
const iframeDocument = iframe.contentDocument;
@@ -41,30 +45,36 @@ describe('EnterLeaveEventPlugin', () => {
iframeDocument.close();
const leaveEvents = [];
- const node = ReactDOM.render(
-
{
- e.persist();
- leaveEvents.push(e);
- }}
- />,
+ const root = ReactDOMClient.createRoot(
iframeDocument.body.getElementsByTagName('div')[0],
);
-
- node.dispatchEvent(
- new MouseEvent('mouseout', {
- bubbles: true,
- cancelable: true,
- relatedTarget: iframe.contentWindow,
- }),
- );
+ await act(() => {
+ root.render(
+
{
+ e.persist();
+ leaveEvents.push(e);
+ }}
+ />,
+ );
+ });
+ const node = iframeDocument.body.getElementsByTagName('div')[0].firstChild;
+ await act(() => {
+ node.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: iframe.contentWindow,
+ }),
+ );
+ });
expect(leaveEvents.length).toBe(1);
expect(leaveEvents[0].target).toBe(node);
expect(leaveEvents[0].relatedTarget).toBe(iframe.contentWindow);
});
- it('should set onMouseEnter relatedTarget properly in iframe', () => {
+ it('should set onMouseEnter relatedTarget properly in iframe', async () => {
const iframe = document.createElement('iframe');
container.appendChild(iframe);
const iframeDocument = iframe.contentDocument;
@@ -74,23 +84,29 @@ describe('EnterLeaveEventPlugin', () => {
iframeDocument.close();
const enterEvents = [];
- const node = ReactDOM.render(
-
{
- e.persist();
- enterEvents.push(e);
- }}
- />,
+ const root = ReactDOMClient.createRoot(
iframeDocument.body.getElementsByTagName('div')[0],
);
-
- node.dispatchEvent(
- new MouseEvent('mouseover', {
- bubbles: true,
- cancelable: true,
- relatedTarget: null,
- }),
- );
+ await act(() => {
+ root.render(
+
{
+ e.persist();
+ enterEvents.push(e);
+ }}
+ />,
+ );
+ });
+ const node = iframeDocument.body.getElementsByTagName('div')[0].firstChild;
+ await act(() => {
+ node.dispatchEvent(
+ new MouseEvent('mouseover', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: null,
+ }),
+ );
+ });
expect(enterEvents.length).toBe(1);
expect(enterEvents[0].target).toBe(node);
@@ -98,7 +114,7 @@ describe('EnterLeaveEventPlugin', () => {
});
// Regression test for https://github.com/facebook/react/issues/10906.
- it('should find the common parent after updates', () => {
+ it('should find the common parent after updates', async () => {
let parentEnterCalls = 0;
let childEnterCalls = 0;
let parent = null;
@@ -117,18 +133,24 @@ describe('EnterLeaveEventPlugin', () => {
}
}
- ReactDOM.render(
, container);
- // The issue only reproduced on insertion during the first update.
- ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
);
+ });
+ await act(() => {
+ root.render(
);
+ });
// Enter from parent into the child.
- parent.dispatchEvent(
- new MouseEvent('mouseout', {
- bubbles: true,
- cancelable: true,
- relatedTarget: parent.firstChild,
- }),
- );
+ await act(() => {
+ parent.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: parent.firstChild,
+ }),
+ );
+ });
// Entering a child should fire on the child, not on the parent.
expect(childEnterCalls).toBe(1);
@@ -136,7 +158,7 @@ describe('EnterLeaveEventPlugin', () => {
});
// Test for https://github.com/facebook/react/issues/16763.
- it('should call mouseEnter once from sibling rendered inside a rendered component', done => {
+ it('should call mouseEnter once from sibling rendered inside a rendered component in legacy roots', done => {
const mockFn = jest.fn();
class Parent extends React.Component {
@@ -186,7 +208,7 @@ describe('EnterLeaveEventPlugin', () => {
ReactDOM.render(
, container);
});
- it('should call mouseEnter when pressing a non tracked React node', done => {
+ it('should call mouseEnter when pressing a non tracked React node in legacy root', done => {
const mockFn = jest.fn();
class Parent extends React.Component {
@@ -237,7 +259,7 @@ describe('EnterLeaveEventPlugin', () => {
ReactDOM.render(
, container);
});
- it('should work with portals outside of the root that has onMouseLeave', () => {
+ it('should work with portals outside of the root that has onMouseLeave', async () => {
const divRef = React.createRef();
const onMouseLeave = jest.fn();
@@ -249,21 +271,27 @@ describe('EnterLeaveEventPlugin', () => {
);
}
- ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(() => {
+ root.render(
);
+ });
// Leave from the portal div
- divRef.current.dispatchEvent(
- new MouseEvent('mouseout', {
- bubbles: true,
- cancelable: true,
- relatedTarget: document.body,
- }),
- );
+ await act(() => {
+ divRef.current.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: document.body,
+ }),
+ );
+ });
expect(onMouseLeave).toHaveBeenCalledTimes(1);
});
- it('should work with portals that have onMouseEnter outside of the root ', () => {
+ it('should work with portals that have onMouseEnter outside of the root ', async () => {
const divRef = React.createRef();
const otherDivRef = React.createRef();
const onMouseEnter = jest.fn();
@@ -279,7 +307,11 @@ describe('EnterLeaveEventPlugin', () => {
);
}
- ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+
+ await act(() => {
+ root.render(
);
+ });
// Leave from the portal div
divRef.current.dispatchEvent(
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index 568e8327198f1..daea520a61eca 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -39,7 +39,9 @@ const getFiberCurrentPropsFromNode = EventInternals[2];
const enqueueStateRestore = EventInternals[3];
const restoreStateIfNeeded = EventInternals[4];
-const act = React.unstable_act;
+// TODO: Add a warning if this API is accessed with advice to switch to
+// importing directly from the React package instead.
+const act = React.act;
function Event(suffix) {}
diff --git a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js
index 98328bc435af9..ccd84d08a0d5d 100644
--- a/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js
+++ b/packages/react-native-renderer/src/__tests__/ResponderEventPlugin-test.internal.js
@@ -1377,11 +1377,12 @@ describe('ResponderEventPlugin', () => {
expect(ResponderEventPlugin._getResponder()).toBe(null);
});
- it('should determine the first common ancestor correctly', () => {
+ it('should determine the first common ancestor correctly', async () => {
// This test was moved here from the ReactTreeTraversal test since only the
// ResponderEventPlugin uses `getLowestCommonAncestor`
const React = require('react');
- const ReactTestUtils = require('react-dom/test-utils');
+ const ReactDOMClient = require('react-dom/client');
+ const act = require('internal-test-utils').act;
const getLowestCommonAncestor =
require('react-native-renderer/src/legacy-events/ResponderEventPlugin').getLowestCommonAncestor;
// This works by accident and will likely break in the future.
@@ -1422,7 +1423,12 @@ describe('ResponderEventPlugin', () => {
}
}
- const parent = ReactTestUtils.renderIntoDocument(
);
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let parent;
+ await act(() => {
+ root.render(
(parent = current)} />);
+ });
const ancestors = [
// Common ancestor with self is self.
diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js
index 53d7bc601aa05..573e497454028 100644
--- a/packages/react-reconciler/src/ReactFiber.js
+++ b/packages/react-reconciler/src/ReactFiber.js
@@ -28,7 +28,6 @@ import {
isHostSingletonType,
} from './ReactFiberConfig';
import {
- createRootStrictEffectsByDefault,
enableCache,
enableProfilerTimer,
enableScopeAPI,
@@ -456,7 +455,7 @@ export function createHostRootFiber(
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode;
- if (isStrictMode === true || createRootStrictEffectsByDefault) {
+ if (isStrictMode === true) {
mode |= StrictLegacyMode | StrictEffectsMode;
}
if (
diff --git a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
index 4cf0fd4d3bd12..61cde5648a9e6 100644
--- a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
@@ -28,7 +28,7 @@ describe('act warnings', () => {
React = require('react');
Scheduler = require('scheduler');
ReactNoop = require('react-noop-renderer');
- act = React.unstable_act;
+ act = React.act;
useState = React.useState;
Suspense = React.Suspense;
startTransition = React.startTransition;
diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
index 63dc79e6f99d3..0711fb3adb3d0 100644
--- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
@@ -20,7 +20,7 @@ describe('ReactFiberHostContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- act = React.unstable_act;
+ act = React.act;
ReactFiberReconciler = require('react-reconciler');
ConcurrentRoot =
require('react-reconciler/src/ReactRootTags').ConcurrentRoot;
diff --git a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
index 28b5333e4cf4c..6b1c284a7eb9d 100644
--- a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
@@ -28,7 +28,7 @@ describe('isomorphic act()', () => {
ReactNoop = require('react-noop-renderer');
DiscreteEventPriority =
require('react-reconciler/constants').DiscreteEventPriority;
- act = React.unstable_act;
+ act = React.act;
use = React.use;
Suspense = React.Suspense;
startTransition = React.startTransition;
diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
index 17067638ac811..a29280b36c569 100644
--- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
@@ -370,7 +370,7 @@ describe('`act` bypasses Scheduler methods completely,', () => {
}
const root = ReactNoop.createRoot();
- const publicAct = React.unstable_act;
+ const publicAct = React.act;
const prevIsReactActEnvironment = global.IS_REACT_ACT_ENVIRONMENT;
try {
global.IS_REACT_ACT_ENVIRONMENT = true;
diff --git a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
index 66ae53a757d66..4ad7bab5614bb 100644
--- a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
@@ -26,12 +26,10 @@ describe('ReactScope', () => {
});
describe('ReactDOM', () => {
- let ReactDOM;
let ReactDOMClient;
let container;
beforeEach(() => {
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
container = document.createElement('div');
@@ -44,7 +42,7 @@ describe('ReactScope', () => {
});
// @gate www
- it('DO_NOT_USE_queryAllNodes() works as intended', () => {
+ it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -68,18 +66,28 @@ describe('ReactScope', () => {
);
}
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
expect(scopeRef.current).toBe(null);
});
// @gate www
- it('DO_NOT_USE_queryAllNodes() provides the correct host instance', () => {
+ it('DO_NOT_USE_queryAllNodes() provides the correct host instance', async () => {
const testScopeQuery = (type, props) => type === 'div';
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -103,7 +111,11 @@ describe('ReactScope', () => {
);
}
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([divRef.current]);
let filterQuery = (type, props, instance) =>
@@ -115,18 +127,24 @@ describe('ReactScope', () => {
testScopeQuery(type, props);
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
filterQuery = (type, props, instance) =>
[spanRef.current, aRef.current].includes(instance) ||
testScopeQuery(type, props);
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(filterQuery);
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
expect(scopeRef.current).toBe(null);
});
// @gate www
- it('DO_NOT_USE_queryFirstNode() works as intended', () => {
+ it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -150,18 +168,28 @@ describe('ReactScope', () => {
);
}
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(divRef.current);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(aRef.current);
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
expect(scopeRef.current).toBe(null);
});
// @gate www
- it('containsNode() works as intended', () => {
+ it('containsNode() works as intended', async () => {
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
const divRef = React.createRef();
@@ -194,24 +222,34 @@ describe('ReactScope', () => {
);
}
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
});
// @gate www
- it('scopes support server-side rendering and hydration', () => {
+ it('scopes support server-side rendering and hydration', async () => {
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
const divRef = React.createRef();
@@ -235,14 +273,16 @@ describe('ReactScope', () => {
'',
);
container.innerHTML = html;
- ReactDOM.hydrate( , container);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(container, );
+ });
const testScopeQuery = (type, props) => true;
const nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
});
// @gate www
- it('getChildContextValues() works as intended', () => {
+ it('getChildContextValues() works as intended', async () => {
const TestContext = React.createContext();
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -260,13 +300,23 @@ describe('ReactScope', () => {
);
}
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
+
let nodes = scopeRef.current.getChildContextValues(TestContext);
expect(nodes).toEqual([1]);
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
+
nodes = scopeRef.current.getChildContextValues(TestContext);
expect(nodes).toEqual([1, 2]);
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
expect(scopeRef.current).toBe(null);
});
diff --git a/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js b/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js
index af35defe2325c..62ff0e19e6230 100644
--- a/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js
+++ b/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js
@@ -27,15 +27,6 @@ describe('StrictEffectsMode', () => {
assertLog = InternalTestUtils.assertLog;
});
- function supportsDoubleInvokeEffects() {
- return gate(
- flags =>
- flags.build === 'development' &&
- flags.createRootStrictEffectsByDefault &&
- flags.dfsEffectsRefactor,
- );
- }
-
it('should not double invoke effects in legacy mode', async () => {
function App({text}) {
React.useEffect(() => {
@@ -52,7 +43,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- ReactTestRenderer.create( );
+ ReactTestRenderer.create(
+
+
+ ,
+ );
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
@@ -75,12 +70,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'useLayoutEffect mount',
'useEffect mount',
@@ -94,7 +94,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog([
@@ -128,12 +132,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'useEffect One mount',
'useEffect Two mount',
@@ -147,7 +156,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog([
@@ -181,12 +194,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'useLayoutEffect One mount',
'useLayoutEffect Two mount',
@@ -200,7 +218,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog([
@@ -232,12 +254,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'useLayoutEffect mount',
'useEffect mount',
@@ -249,7 +276,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
@@ -286,10 +317,15 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- ReactTestRenderer.create( , {isConcurrent: true});
+ ReactTestRenderer.create(
+
+
+ ,
+ {isConcurrent: true},
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'componentDidMount',
'componentWillUnmount',
@@ -321,12 +357,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'componentDidMount',
'componentWillUnmount',
@@ -337,7 +378,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog(['componentDidUpdate']);
@@ -366,19 +411,28 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog(['componentWillUnmount']);
} else {
assertLog([]);
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog(['componentDidUpdate']);
@@ -410,7 +464,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- ReactTestRenderer.create( );
+ ReactTestRenderer.create(
+
+
+ ,
+ );
});
assertLog(['componentDidMount']);
@@ -437,12 +495,17 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'mount',
'useLayoutEffect mount',
@@ -502,10 +565,15 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- ReactTestRenderer.create( , {isConcurrent: true});
+ ReactTestRenderer.create(
+
+
+ ,
+ {isConcurrent: true},
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'App useLayoutEffect mount',
'App useEffect mount',
@@ -522,7 +590,7 @@ describe('StrictEffectsMode', () => {
_setShowChild(true);
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'App useLayoutEffect unmount',
'Child useLayoutEffect mount',
@@ -585,12 +653,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'componentDidMount',
'useLayoutEffect mount',
@@ -611,7 +684,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog([
@@ -666,12 +743,17 @@ describe('StrictEffectsMode', () => {
let renderer;
await act(() => {
- renderer = ReactTestRenderer.create( , {
- isConcurrent: true,
- });
+ renderer = ReactTestRenderer.create(
+
+
+ ,
+ {
+ isConcurrent: true,
+ },
+ );
});
- if (supportsDoubleInvokeEffects()) {
+ if (__DEV__) {
assertLog([
'useLayoutEffect mount',
'useEffect mount',
@@ -686,7 +768,11 @@ describe('StrictEffectsMode', () => {
}
await act(() => {
- renderer.update( );
+ renderer.update(
+
+
+ ,
+ );
});
assertLog([
diff --git a/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js b/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
index fa72a610ab9a8..880b77a27abe7 100644
--- a/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js
@@ -32,9 +32,6 @@ describe('StrictEffectsMode defaults', () => {
waitForAll = InternalTestUtils.waitForAll;
waitForPaint = InternalTestUtils.waitForPaint;
assertLog = InternalTestUtils.assertLog;
-
- const ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.createRootStrictEffectsByDefault = __DEV__;
});
it('should not double invoke effects in legacy mode', async () => {
@@ -53,7 +50,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.renderLegacySyncRoot( );
+ ReactNoop.renderLegacySyncRoot(
+
+
+ ,
+ );
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
@@ -79,7 +80,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.renderLegacySyncRoot( );
+ ReactNoop.renderLegacySyncRoot(
+
+
+ ,
+ );
});
assertLog(['componentDidMount']);
@@ -98,9 +103,9 @@ describe('StrictEffectsMode defaults', () => {
await act(async () => {
ReactNoop.render(
- <>
+
- >,
+ ,
);
await waitForPaint([
@@ -112,10 +117,10 @@ describe('StrictEffectsMode defaults', () => {
await act(async () => {
ReactNoop.render(
- <>
+
- >,
+ ,
);
assertLog([]);
@@ -151,9 +156,9 @@ describe('StrictEffectsMode defaults', () => {
await act(async () => {
ReactNoop.render(
- <>
+
- >,
+ ,
);
await waitForAll([
@@ -168,10 +173,10 @@ describe('StrictEffectsMode defaults', () => {
await act(async () => {
ReactNoop.render(
- <>
+
- >,
+ ,
);
await waitFor([
@@ -209,7 +214,11 @@ describe('StrictEffectsMode defaults', () => {
return text;
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -222,7 +231,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -255,7 +268,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -268,7 +285,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -301,7 +322,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -314,7 +339,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -345,7 +374,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -356,7 +389,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog(['useLayoutEffect mount', 'useEffect mount']);
@@ -383,7 +420,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
expect(onRefMock.mock.calls.length).toBe(3);
@@ -417,7 +458,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -447,7 +492,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -457,7 +506,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog(['componentDidUpdate']);
@@ -490,7 +543,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -540,7 +597,11 @@ describe('StrictEffectsMode defaults', () => {
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -599,15 +660,19 @@ describe('StrictEffectsMode defaults', () => {
function App({text}) {
return (
- <>
+
- >
+
);
}
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
@@ -623,7 +688,11 @@ describe('StrictEffectsMode defaults', () => {
]);
await act(() => {
- ReactNoop.render( );
+ ReactNoop.render(
+
+
+ ,
+ );
});
assertLog([
diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js
index 8ce009ed72043..38b74563c30a7 100644
--- a/packages/react-refresh/src/__tests__/ReactFresh-test.js
+++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js
@@ -17,13 +17,13 @@ let ReactDOMClient;
let ReactFreshRuntime;
let Scheduler;
let act;
-let internalAct;
let createReactClass;
let waitFor;
let assertLog;
describe('ReactFresh', () => {
let container;
+ let root;
beforeEach(() => {
if (__DEV__) {
@@ -34,8 +34,7 @@ describe('ReactFresh', () => {
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
- act = React.unstable_act;
- internalAct = require('internal-test-utils').act;
+ act = require('internal-test-utils').act;
const InternalTestUtils = require('internal-test-utils');
waitFor = InternalTestUtils.waitFor;
@@ -47,6 +46,7 @@ describe('ReactFresh', () => {
new React.Component().updater,
);
container = document.createElement('div');
+ root = ReactDOMClient.createRoot(container);
document.body.appendChild(container);
}
});
@@ -63,10 +63,10 @@ describe('ReactFresh', () => {
return Component;
}
- function render(version, props) {
+ async function render(version, props) {
const Component = version();
- act(() => {
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
});
return Component;
}
@@ -99,9 +99,9 @@ describe('ReactFresh', () => {
);
}
- it('can preserve state for compatible types', () => {
+ it('can preserve state for compatible types', async () => {
if (__DEV__) {
- const HelloV1 = render(() => {
+ const HelloV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -118,7 +118,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -143,7 +143,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -153,15 +153,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => HelloV1);
- render(() => HelloV2);
- render(() => HelloV1);
+ await render(() => HelloV1);
+ await render(() => HelloV2);
+ await render(() => HelloV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -169,7 +169,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -189,9 +189,9 @@ describe('ReactFresh', () => {
}
});
- it('can preserve state for forwardRef', () => {
+ it('can preserve state for forwardRef', async () => {
if (__DEV__) {
- const OuterV1 = render(() => {
+ const OuterV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -211,7 +211,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -239,7 +239,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -249,15 +249,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => OuterV1);
- render(() => OuterV2);
- render(() => OuterV1);
+ await render(() => OuterV1);
+ await render(() => OuterV2);
+ await render(() => OuterV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -279,9 +279,9 @@ describe('ReactFresh', () => {
}
});
- it('should not consider two forwardRefs around the same type to be equivalent', () => {
+ it('should not consider two forwardRefs around the same type to be equivalent', async () => {
if (__DEV__) {
- const ParentV1 = render(
+ const ParentV1 = await render(
() => {
function Hello() {
const [val, setVal] = React.useState(0);
@@ -317,32 +317,32 @@ describe('ReactFresh', () => {
let el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
// Switching up the inner types should reset the state.
- render(() => ParentV1, {cond: false});
+ await render(() => ParentV1, {cond: false});
expect(el).not.toBe(container.firstChild);
el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
// Switch them up back again.
- render(() => ParentV1, {cond: true});
+ await render(() => ParentV1, {cond: true});
expect(el).not.toBe(container.firstChild);
el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
// Now bump up the state to prepare for patching.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -383,14 +383,14 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Switching up the condition should still reset the state.
- render(() => ParentV2, {cond: false});
+ await render(() => ParentV2, {cond: false});
expect(el).not.toBe(container.firstChild);
el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('red');
// Now bump up the state to prepare for top-level renders.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el).toBe(container.firstChild);
@@ -398,18 +398,18 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Finally, verify using top-level render with stale type keeps state.
- render(() => ParentV1);
- render(() => ParentV2);
- render(() => ParentV1);
+ await render(() => ParentV1);
+ await render(() => ParentV2);
+ await render(() => ParentV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('1');
expect(el.style.color).toBe('red');
}
});
- it('can update forwardRef render function with its wrapper', () => {
+ it('can update forwardRef render function with its wrapper', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello({color}) {
const [val, setVal] = React.useState(0);
return (
@@ -429,7 +429,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -458,9 +458,9 @@ describe('ReactFresh', () => {
}
});
- it('can update forwardRef render function in isolation', () => {
+ it('can update forwardRef render function in isolation', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello({color}) {
const [val, setVal] = React.useState(0);
return (
@@ -483,7 +483,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -515,9 +515,9 @@ describe('ReactFresh', () => {
}
});
- it('can preserve state for simple memo', () => {
+ it('can preserve state for simple memo', async () => {
if (__DEV__) {
- const OuterV1 = render(() => {
+ const OuterV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -537,7 +537,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -565,7 +565,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -575,15 +575,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => OuterV1);
- render(() => OuterV2);
- render(() => OuterV1);
+ await render(() => OuterV1);
+ await render(() => OuterV2);
+ await render(() => OuterV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -605,9 +605,9 @@ describe('ReactFresh', () => {
}
});
- it('can preserve state for memo with custom comparison', () => {
+ it('can preserve state for memo with custom comparison', async () => {
if (__DEV__) {
- const OuterV1 = render(() => {
+ const OuterV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -626,7 +626,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -653,7 +653,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -663,15 +663,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => OuterV1);
- render(() => OuterV2);
- render(() => OuterV1);
+ await render(() => OuterV1);
+ await render(() => OuterV2);
+ await render(() => OuterV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -693,9 +693,9 @@ describe('ReactFresh', () => {
}
});
- it('can update simple memo function in isolation', () => {
+ it('can update simple memo function in isolation', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -713,7 +713,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -740,9 +740,9 @@ describe('ReactFresh', () => {
}
});
- it('can preserve state for memo(forwardRef)', () => {
+ it('can preserve state for memo(forwardRef)', async () => {
if (__DEV__) {
- const OuterV1 = render(() => {
+ const OuterV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -762,7 +762,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -790,7 +790,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -800,15 +800,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => OuterV1);
- render(() => OuterV2);
- render(() => OuterV1);
+ await render(() => OuterV1);
+ await render(() => OuterV2);
+ await render(() => OuterV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -832,7 +832,8 @@ describe('ReactFresh', () => {
it('can preserve state for lazy after resolution', async () => {
if (__DEV__) {
- const AppV1 = render(() => {
+ let resolve;
+ const AppV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -845,8 +846,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -865,7 +866,7 @@ describe('ReactFresh', () => {
expect(container.textContent).toBe('Loading');
await act(() => {
- jest.runAllTimers();
+ resolve();
});
expect(container.textContent).toBe('0');
@@ -873,7 +874,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -892,8 +893,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -916,7 +917,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump the state again.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -926,15 +927,15 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => AppV1);
- render(() => AppV2);
- render(() => AppV1);
+ await render(() => AppV1);
+ await render(() => AppV2);
+ await render(() => AppV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
// Finally, a render with incompatible type should reset it.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -968,7 +969,8 @@ describe('ReactFresh', () => {
it('can patch lazy before resolution', async () => {
if (__DEV__) {
- render(() => {
+ let resolve;
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -981,8 +983,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -1014,7 +1016,7 @@ describe('ReactFresh', () => {
});
await act(() => {
- jest.runAllTimers();
+ resolve();
});
// Expect different color on initial mount.
@@ -1023,7 +1025,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump state.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -1050,7 +1052,8 @@ describe('ReactFresh', () => {
it('can patch lazy(forwardRef) before resolution', async () => {
if (__DEV__) {
- render(() => {
+ let resolve;
+ await render(() => {
function renderHello() {
const [val, setVal] = React.useState(0);
return (
@@ -1064,8 +1067,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -1098,7 +1101,7 @@ describe('ReactFresh', () => {
});
await act(() => {
- jest.runAllTimers();
+ resolve();
});
// Expect different color on initial mount.
@@ -1107,7 +1110,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump state.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -1135,7 +1138,8 @@ describe('ReactFresh', () => {
it('can patch lazy(memo) before resolution', async () => {
if (__DEV__) {
- render(() => {
+ let resolve;
+ await render(() => {
function renderHello() {
const [val, setVal] = React.useState(0);
return (
@@ -1149,8 +1153,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -1183,7 +1187,7 @@ describe('ReactFresh', () => {
});
await act(() => {
- jest.runAllTimers();
+ resolve();
});
// Expect different color on initial mount.
@@ -1192,7 +1196,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump state.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -1220,7 +1224,8 @@ describe('ReactFresh', () => {
it('can patch lazy(memo(forwardRef)) before resolution', async () => {
if (__DEV__) {
- render(() => {
+ let resolve;
+ await render(() => {
function renderHello() {
const [val, setVal] = React.useState(0);
return (
@@ -1234,8 +1239,8 @@ describe('ReactFresh', () => {
const Outer = React.lazy(
() =>
- new Promise(resolve => {
- setTimeout(() => resolve({default: Hello}), 100);
+ new Promise(_resolve => {
+ resolve = () => _resolve({default: Hello});
}),
);
$RefreshReg$(Outer, 'Outer');
@@ -1268,7 +1273,7 @@ describe('ReactFresh', () => {
});
await act(() => {
- jest.runAllTimers();
+ resolve();
});
// Expect different color on initial mount.
@@ -1277,7 +1282,7 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('red');
// Bump state.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.firstChild).toBe(el);
@@ -1303,9 +1308,9 @@ describe('ReactFresh', () => {
}
});
- it('can patch both trees while suspense is displaying the fallback', async () => {
+ it('only patches the fallback tree while suspended', async () => {
if (__DEV__) {
- const AppV1 = render(
+ const AppV1 = await render(
() => {
function Hello({children}) {
const [val, setVal] = React.useState(0);
@@ -1343,7 +1348,7 @@ describe('ReactFresh', () => {
expect(primaryChild.style.display).toBe('');
// Bump primary content state.
- act(() => {
+ await act(() => {
primaryChild.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.childNodes.length).toBe(1);
@@ -1371,7 +1376,7 @@ describe('ReactFresh', () => {
expect(primaryChild.style.display).toBe('');
// Now force the tree to suspend.
- render(() => AppV1, {shouldSuspend: true});
+ await render(() => AppV1, {shouldSuspend: true});
// Expect to see two trees, one of them is hidden.
expect(container.childNodes.length).toBe(2);
@@ -1385,7 +1390,7 @@ describe('ReactFresh', () => {
expect(fallbackChild.style.display).toBe('');
// Bump fallback state.
- act(() => {
+ await act(() => {
fallbackChild.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(container.childNodes.length).toBe(2);
@@ -1411,19 +1416,19 @@ describe('ReactFresh', () => {
$RefreshReg$(Hello, 'Hello');
});
- // Colors inside both trees should change:
+ // Only update color in the visible child
expect(container.childNodes.length).toBe(2);
expect(container.childNodes[0]).toBe(primaryChild);
expect(container.childNodes[1]).toBe(fallbackChild);
expect(primaryChild.textContent).toBe('Content 1');
- expect(primaryChild.style.color).toBe('red');
+ expect(primaryChild.style.color).toBe('green');
expect(primaryChild.style.display).toBe('none');
expect(fallbackChild.textContent).toBe('Fallback 1');
expect(fallbackChild.style.color).toBe('red');
expect(fallbackChild.style.display).toBe('');
// Only primary tree should exist now:
- render(() => AppV1, {shouldSuspend: false});
+ await render(() => AppV1, {shouldSuspend: false});
expect(container.childNodes.length).toBe(1);
expect(container.childNodes[0]).toBe(primaryChild);
expect(primaryChild.textContent).toBe('Content 1');
@@ -1450,11 +1455,11 @@ describe('ReactFresh', () => {
}
});
- it('does not re-render ancestor components unnecessarily during a hot update', () => {
+ it('does not re-render ancestor components unnecessarily during a hot update', async () => {
if (__DEV__) {
let appRenders = 0;
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -1478,7 +1483,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -1508,7 +1513,7 @@ describe('ReactFresh', () => {
expect(appRenders).toBe(1);
// Bump the state.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('2');
@@ -1518,11 +1523,11 @@ describe('ReactFresh', () => {
}
});
- it('batches re-renders during a hot update', () => {
+ it('batches re-renders during a hot update', async () => {
if (__DEV__) {
let helloRenders = 0;
- render(() => {
+ await render(() => {
function Hello({children}) {
helloRenders++;
return X{children}X
;
@@ -1559,9 +1564,9 @@ describe('ReactFresh', () => {
}
});
- it('does not leak state between components', () => {
+ it('does not leak state between components', async () => {
if (__DEV__) {
- const AppV1 = render(
+ const AppV1 = await render(
() => {
function Hello1() {
const [val, setVal] = React.useState(0);
@@ -1594,21 +1599,21 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
// Switch the condition, flipping inner content.
// This should reset the state.
- render(() => AppV1, {cond: true});
+ await render(() => AppV1, {cond: true});
const el2 = container.firstChild;
expect(el2).not.toBe(el);
expect(el2.textContent).toBe('0');
expect(el2.style.color).toBe('blue');
// Bump it again.
- act(() => {
+ await act(() => {
el2.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el2.textContent).toBe('1');
@@ -1641,7 +1646,7 @@ describe('ReactFresh', () => {
expect(el2.style.color).toBe('red');
// Flip the condition again.
- render(() => AppV1, {cond: false});
+ await render(() => AppV1, {cond: false});
const el3 = container.firstChild;
expect(el3).not.toBe(el2);
expect(el3.textContent).toBe('0');
@@ -1649,9 +1654,9 @@ describe('ReactFresh', () => {
}
});
- it('can force remount by changing signature', () => {
+ it('can force remount by changing signature', async () => {
if (__DEV__) {
- const HelloV1 = render(() => {
+ const HelloV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -1670,7 +1675,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -1719,7 +1724,7 @@ describe('ReactFresh', () => {
expect(newEl.style.color).toBe('yellow');
// Bump state again.
- act(() => {
+ await act(() => {
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(newEl.textContent).toBe('1');
@@ -1728,11 +1733,11 @@ describe('ReactFresh', () => {
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
- render(() => HelloV1);
- render(() => HelloV2);
- render(() => HelloV3);
- render(() => HelloV2);
- render(() => HelloV1);
+ await render(() => HelloV1);
+ await render(() => HelloV2);
+ await render(() => HelloV3);
+ await render(() => HelloV2);
+ await render(() => HelloV1);
expect(container.firstChild).toBe(newEl);
expect(newEl.textContent).toBe('1');
expect(newEl.style.color).toBe('yellow');
@@ -1780,7 +1785,7 @@ describe('ReactFresh', () => {
}
});
- it('keeps a valid tree when forcing remount', () => {
+ it('keeps a valid tree when forcing remount', async () => {
if (__DEV__) {
const HelloV1 = prepare(() => {
function Hello() {
@@ -1910,26 +1915,31 @@ describe('ReactFresh', () => {
,
];
- // First, check that each tree handles remounts in isolation.
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
for (let i = 0; i < trees.length; i++) {
- runRemountingStressTest(trees[i]);
+ await runRemountingStressTest(trees[i]);
}
// Then check that each tree is resilient to updates from another tree.
for (let i = 0; i < trees.length; i++) {
for (let j = 0; j < trees.length; j++) {
- ReactDOM.render(null, container);
+ await act(() => {
+ root.render(null);
+ });
+
// Intentionally don't clean up between the tests:
- runRemountingStressTest(trees[i]);
- runRemountingStressTest(trees[j]);
- runRemountingStressTest(trees[i]);
+ await runRemountingStressTest(trees[i]);
+ await runRemountingStressTest(trees[j]);
+ await runRemountingStressTest(trees[i]);
}
}
}
- });
+ }, 10000);
- function runRemountingStressTest(tree) {
+ async function runRemountingStressTest(tree) {
patch(() => {
function Hello({children}) {
return ;
@@ -1939,7 +1949,10 @@ describe('ReactFresh', () => {
return Hello;
});
- ReactDOM.render(tree, container);
+ await act(() => {
+ root.render(tree);
+ });
+
const elements = container.querySelectorAll('section');
// Each tree above produces exactly three elements:
expect(elements.length).toBe(3);
@@ -2003,8 +2016,10 @@ describe('ReactFresh', () => {
expect(el.dataset.color).toBe('black');
});
- // Do another render just in case.
- ReactDOM.render(tree, container);
+ await act(() => {
+ root.render(tree);
+ });
+
expect(container.querySelectorAll('section').length).toBe(3);
container.querySelectorAll('section').forEach((el, index) => {
expect(el).toBe(elementsAfterRemount[index]);
@@ -2012,21 +2027,21 @@ describe('ReactFresh', () => {
});
}
- it('can remount on signature change within a wrapper', () => {
+ it('can remount on signature change within a wrapper', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => Hello);
+ await testRemountingWithWrapper(Hello => Hello);
}
});
- it('can remount on signature change within a simple memo wrapper', () => {
+ it('can remount on signature change within a simple memo wrapper', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => React.memo(Hello));
+ await testRemountingWithWrapper(Hello => React.memo(Hello));
}
});
- it('can remount on signature change within a lazy simple memo wrapper', () => {
+ it('can remount on signature change within a lazy simple memo wrapper', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello =>
+ await testRemountingWithWrapper(Hello =>
React.lazy(() => ({
then(cb) {
cb({default: React.memo(Hello)});
@@ -2036,35 +2051,37 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within forwardRef', () => {
+ it('can remount on signature change within forwardRef', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => React.forwardRef(Hello));
+ await testRemountingWithWrapper(Hello => React.forwardRef(Hello));
}
});
- it('can remount on signature change within forwardRef render function', () => {
+ it('can remount on signature change within forwardRef render function', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => React.forwardRef(() => ));
+ await testRemountingWithWrapper(Hello =>
+ React.forwardRef(() => ),
+ );
}
});
- it('can remount on signature change within nested memo', () => {
+ it('can remount on signature change within nested memo', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello =>
+ await testRemountingWithWrapper(Hello =>
React.memo(React.memo(React.memo(Hello))),
);
}
});
- it('can remount on signature change within a memo wrapper and custom comparison', () => {
+ it('can remount on signature change within a memo wrapper and custom comparison', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => React.memo(Hello, () => true));
+ await testRemountingWithWrapper(Hello => React.memo(Hello, () => true));
}
});
- it('can remount on signature change within a class', () => {
+ it('can remount on signature change within a class', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const child = ;
return class Wrapper extends React.PureComponent {
render() {
@@ -2075,9 +2092,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a context provider', () => {
+ it('can remount on signature change within a context provider', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const Context = React.createContext();
const child = (
@@ -2091,9 +2108,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a context consumer', () => {
+ it('can remount on signature change within a context consumer', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const Context = React.createContext();
const child = {() => } ;
return function Wrapper() {
@@ -2103,9 +2120,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a suspense node', () => {
+ it('can remount on signature change within a suspense node', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
// TODO: we'll probably want to test fallback trees too.
const child = (
@@ -2119,9 +2136,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a mode node', () => {
+ it('can remount on signature change within a mode node', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const child = (
@@ -2134,9 +2151,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a fragment node', () => {
+ it('can remount on signature change within a fragment node', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const child = (
<>
@@ -2149,9 +2166,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within multiple siblings', () => {
+ it('can remount on signature change within multiple siblings', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const child = (
<>
<>
@@ -2168,9 +2185,9 @@ describe('ReactFresh', () => {
}
});
- it('can remount on signature change within a profiler node', () => {
+ it('can remount on signature change within a profiler node', async () => {
if (__DEV__) {
- testRemountingWithWrapper(Hello => {
+ await testRemountingWithWrapper(Hello => {
const child = ;
return function Wrapper() {
return (
@@ -2183,8 +2200,8 @@ describe('ReactFresh', () => {
}
});
- function testRemountingWithWrapper(wrap) {
- render(() => {
+ async function testRemountingWithWrapper(wrap) {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -2206,7 +2223,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -2255,7 +2272,7 @@ describe('ReactFresh', () => {
expect(newEl.style.color).toBe('yellow');
// Bump state again.
- act(() => {
+ await act(() => {
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(newEl.textContent).toBe('1');
@@ -2303,11 +2320,11 @@ describe('ReactFresh', () => {
expect(finalEl.style.color).toBe('orange');
}
- it('resets hooks with dependencies on hot reload', () => {
+ it('resets hooks with dependencies on hot reload', async () => {
if (__DEV__) {
let useEffectWithEmptyArrayCalls = 0;
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
const tranformed = React.useMemo(() => val * 2, [val]);
@@ -2332,33 +2349,31 @@ describe('ReactFresh', () => {
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect ran
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('2'); // val * 2
expect(useEffectWithEmptyArrayCalls).toBe(1); // useEffect didn't re-run
// Perform a hot update.
- act(() => {
- patch(() => {
- function Hello() {
- const [val, setVal] = React.useState(0);
- const tranformed = React.useMemo(() => val * 10, [val]);
- const handleClick = React.useCallback(() => setVal(v => v - 1), []);
+ patch(() => {
+ function Hello() {
+ const [val, setVal] = React.useState(0);
+ const tranformed = React.useMemo(() => val * 10, [val]);
+ const handleClick = React.useCallback(() => setVal(v => v - 1), []);
- React.useEffect(() => {
- useEffectWithEmptyArrayCalls++;
- }, []);
+ React.useEffect(() => {
+ useEffectWithEmptyArrayCalls++;
+ }, []);
- return (
-
- {tranformed}
-
- );
- }
- $RefreshReg$(Hello, 'Hello');
- return Hello;
- });
+ return (
+
+ {tranformed}
+
+ );
+ }
+ $RefreshReg$(Hello, 'Hello');
+ return Hello;
});
// Assert the state was preserved but memo was evicted.
@@ -2368,7 +2383,7 @@ describe('ReactFresh', () => {
expect(useEffectWithEmptyArrayCalls).toBe(2); // useEffect re-ran
// This should fire the new callback which decreases the counter.
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('0');
@@ -2378,9 +2393,9 @@ describe('ReactFresh', () => {
});
// This pattern is inspired by useSubscription and similar mechanisms.
- it('does not get into infinite loops during render phase updates', () => {
+ it('does not get into infinite loops during render phase updates', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
const source = React.useMemo(() => ({value: 10}), []);
const [state, setState] = React.useState({value: null});
@@ -2398,20 +2413,18 @@ describe('ReactFresh', () => {
expect(el.style.color).toBe('blue');
// Perform a hot update.
- act(() => {
- patch(() => {
- function Hello() {
- const source = React.useMemo(() => ({value: 20}), []);
- const [state, setState] = React.useState({value: null});
- if (state !== source) {
- // This should perform a single render-phase update.
- setState(source);
- }
- return {state.value}
;
+ patch(() => {
+ function Hello() {
+ const source = React.useMemo(() => ({value: 20}), []);
+ const [state, setState] = React.useState({value: null});
+ if (state !== source) {
+ // This should perform a single render-phase update.
+ setState(source);
}
- $RefreshReg$(Hello, 'Hello');
- return Hello;
- });
+ return {state.value}
;
+ }
+ $RefreshReg$(Hello, 'Hello');
+ return Hello;
});
expect(container.firstChild).toBe(el);
@@ -2448,7 +2461,6 @@ describe('ReactFresh', () => {
};
});
- const root = ReactDOMClient.createRoot(container);
root.render( );
await waitFor(['App#layout']);
const el = container.firstChild;
@@ -2482,7 +2494,7 @@ describe('ReactFresh', () => {
expect(el.firstChild.textContent).toBe('0');
expect(el.firstChild.style.color).toBe('red');
- await internalAct(() => {
+ await act(() => {
el.firstChild.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
@@ -2522,9 +2534,9 @@ describe('ReactFresh', () => {
expect(el.firstChild.style.color).toBe('orange');
});
- it('remounts failed error boundaries (componentDidCatch)', () => {
+ it('remounts failed error boundaries (componentDidCatch)', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
return Hi ;
}
@@ -2600,9 +2612,9 @@ describe('ReactFresh', () => {
}
});
- it('remounts failed error boundaries (getDerivedStateFromError)', () => {
+ it('remounts failed error boundaries (getDerivedStateFromError)', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
return Hi ;
}
@@ -2678,9 +2690,9 @@ describe('ReactFresh', () => {
}
});
- it('remounts error boundaries that failed asynchronously after hot update', () => {
+ it('remounts error boundaries that failed asynchronously after hot update', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
const [x] = React.useState('');
React.useEffect(() => {}, []);
@@ -2722,26 +2734,25 @@ describe('ReactFresh', () => {
const secondP = firstP.nextSibling.nextSibling;
// Perform a hot update that fails.
- act(() => {
- patch(() => {
- function Hello() {
- const [x, setX] = React.useState('');
- React.useEffect(() => {
- setTimeout(() => {
- setX(42); // This will crash next render.
- }, 1);
- }, []);
- x.slice();
- return Hi ;
- }
- $RefreshReg$(Hello, 'Hello');
- });
+ let crash;
+ patch(() => {
+ function Hello() {
+ const [x, setX] = React.useState('');
+ React.useEffect(() => {
+ crash = () => {
+ setX(42); // This will crash next render.
+ };
+ }, []);
+ x.slice();
+ return Hi ;
+ }
+ $RefreshReg$(Hello, 'Hello');
});
expect(container.innerHTML).toBe('A
Hi B
');
// Run timeout inside effect:
- act(() => {
- jest.runAllTimers();
+ await act(() => {
+ crash();
});
expect(container.innerHTML).toBe(
'A
Oops: x.slice is not a function B
',
@@ -2750,16 +2761,14 @@ describe('ReactFresh', () => {
expect(container.firstChild.nextSibling.nextSibling).toBe(secondP);
// Perform a hot update that fixes the error.
- act(() => {
- patch(() => {
- function Hello() {
- const [x] = React.useState('');
- React.useEffect(() => {}, []); // Removes the bad effect code.
- x.slice(); // Doesn't throw initially.
- return Fixed! ;
- }
- $RefreshReg$(Hello, 'Hello');
- });
+ patch(() => {
+ function Hello() {
+ const [x] = React.useState('');
+ React.useEffect(() => {}, []); // Removes the bad effect code.
+ x.slice(); // Doesn't throw initially.
+ return Fixed! ;
+ }
+ $RefreshReg$(Hello, 'Hello');
});
// This should remount the error boundary (but not anything above it).
@@ -2769,16 +2778,14 @@ describe('ReactFresh', () => {
// Verify next hot reload doesn't remount anything.
const helloNode = container.firstChild.nextSibling;
- act(() => {
- patch(() => {
- function Hello() {
- const [x] = React.useState('');
- React.useEffect(() => {}, []);
- x.slice();
- return Nice. ;
- }
- $RefreshReg$(Hello, 'Hello');
- });
+ patch(() => {
+ function Hello() {
+ const [x] = React.useState('');
+ React.useEffect(() => {}, []);
+ x.slice();
+ return Nice. ;
+ }
+ $RefreshReg$(Hello, 'Hello');
});
expect(container.firstChild.nextSibling).toBe(helloNode);
@@ -2786,9 +2793,9 @@ describe('ReactFresh', () => {
}
});
- it('remounts a failed root on mount', () => {
+ it('remounts a failed root on mount', async () => {
if (__DEV__) {
- expect(() => {
+ await expect(
render(() => {
function Hello() {
throw new Error('No');
@@ -2796,8 +2803,8 @@ describe('ReactFresh', () => {
$RefreshReg$(Hello, 'Hello');
return Hello;
- });
- }).toThrow('No');
+ }),
+ ).rejects.toThrow('No');
expect(container.innerHTML).toBe('');
// A bad retry
@@ -2849,7 +2856,9 @@ describe('ReactFresh', () => {
expect(container.innerHTML).toBe('Fixed 2! ');
// Updates after intentional unmount are ignored.
- ReactDOM.unmountComponentAtNode(container);
+ await act(() => {
+ root.unmount();
+ });
patch(() => {
function Hello() {
throw new Error('Ignored');
@@ -2867,9 +2876,9 @@ describe('ReactFresh', () => {
}
});
- it('does not retry an intentionally unmounted failed root', () => {
+ it('does not retry an intentionally unmounted failed root', async () => {
if (__DEV__) {
- expect(() => {
+ await expect(
render(() => {
function Hello() {
throw new Error('No');
@@ -2877,12 +2886,14 @@ describe('ReactFresh', () => {
$RefreshReg$(Hello, 'Hello');
return Hello;
- });
- }).toThrow('No');
+ }),
+ ).rejects.toThrow('No');
expect(container.innerHTML).toBe('');
// Intentional unmount.
- ReactDOM.unmountComponentAtNode(container);
+ await act(() => {
+ root.unmount();
+ });
// Perform a hot update that fixes the error.
patch(() => {
@@ -2896,9 +2907,9 @@ describe('ReactFresh', () => {
}
});
- it('remounts a failed root on update', () => {
+ it('remounts a failed root on update', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Hello() {
return Hi ;
}
@@ -2974,7 +2985,9 @@ describe('ReactFresh', () => {
expect(container.innerHTML).toBe('At last. ');
// Check we don't attempt to reverse an intentional unmount.
- ReactDOM.unmountComponentAtNode(container);
+ await act(() => {
+ root.unmount();
+ });
expect(container.innerHTML).toBe('');
patch(() => {
function Hello() {
@@ -2985,7 +2998,8 @@ describe('ReactFresh', () => {
expect(container.innerHTML).toBe('');
// Mount a new container.
- render(() => {
+ root = ReactDOMClient.createRoot(container);
+ await render(() => {
function Hello() {
return Hi ;
}
@@ -3007,7 +3021,9 @@ describe('ReactFresh', () => {
expect(container.innerHTML).toBe('');
// Check we don't attempt to reverse an intentional unmount, even after an error.
- ReactDOM.unmountComponentAtNode(container);
+ await act(() => {
+ root.unmount();
+ });
expect(container.innerHTML).toBe('');
patch(() => {
function Hello() {
@@ -3019,10 +3035,12 @@ describe('ReactFresh', () => {
}
});
- it('regression test: does not get into an infinite loop', () => {
+ it('regression test: does not get into an infinite loop', async () => {
if (__DEV__) {
const containerA = document.createElement('div');
const containerB = document.createElement('div');
+ const rootA = ReactDOMClient.createRoot(containerA);
+ const rootB = ReactDOMClient.createRoot(containerB);
// Initially, nothing interesting.
const RootAV1 = () => {
@@ -3034,9 +3052,9 @@ describe('ReactFresh', () => {
};
$RefreshReg$(RootBV1, 'RootB');
- act(() => {
- ReactDOM.render( , containerA);
- ReactDOM.render( , containerB);
+ await act(() => {
+ rootA.render( );
+ rootB.render( );
});
expect(containerA.innerHTML).toBe('A1');
expect(containerB.innerHTML).toBe('B1');
@@ -3046,7 +3064,11 @@ describe('ReactFresh', () => {
throw new Error('A2!');
};
$RefreshReg$(RootAV2, 'RootA');
- expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A2!');
+ await expect(
+ act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ }),
+ ).rejects.toThrow('A2!');
expect(containerA.innerHTML).toBe('');
expect(containerB.innerHTML).toBe('B1');
@@ -3060,7 +3082,11 @@ describe('ReactFresh', () => {
return 'A3';
};
$RefreshReg$(RootAV3, 'RootA');
- expect(() => ReactFreshRuntime.performReactRefresh()).toThrow('A3!');
+ await expect(
+ act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ }),
+ ).rejects.toThrow('A3!');
expect(containerA.innerHTML).toBe('');
expect(containerB.innerHTML).toBe('B1');
@@ -3068,15 +3094,17 @@ describe('ReactFresh', () => {
return 'A4';
};
$RefreshReg$(RootAV4, 'RootA');
- ReactFreshRuntime.performReactRefresh();
+ await act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ });
expect(containerA.innerHTML).toBe('A4');
expect(containerB.innerHTML).toBe('B1');
}
});
- it('remounts classes on every edit', () => {
+ it('remounts classes on every edit', async () => {
if (__DEV__) {
- const HelloV1 = render(() => {
+ const HelloV1 = await render(() => {
class Hello extends React.Component {
state = {count: 0};
handleClick = () => {
@@ -3105,7 +3133,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -3136,14 +3164,14 @@ describe('ReactFresh', () => {
const newEl = container.firstChild;
expect(newEl.textContent).toBe('0');
expect(newEl.style.color).toBe('red');
- act(() => {
+ await act(() => {
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(newEl.textContent).toBe('1');
// Now top-level renders of both types resolve to latest.
- render(() => HelloV1);
- render(() => HelloV2);
+ await render(() => HelloV1);
+ await render(() => HelloV2);
expect(container.firstChild).toBe(newEl);
expect(newEl.style.color).toBe('red');
expect(newEl.textContent).toBe('1');
@@ -3173,24 +3201,24 @@ describe('ReactFresh', () => {
const finalEl = container.firstChild;
expect(finalEl.textContent).toBe('0');
expect(finalEl.style.color).toBe('orange');
- act(() => {
+ await act(() => {
finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(finalEl.textContent).toBe('1');
- render(() => HelloV3);
- render(() => HelloV2);
- render(() => HelloV1);
+ await render(() => HelloV3);
+ await render(() => HelloV2);
+ await render(() => HelloV1);
expect(container.firstChild).toBe(finalEl);
expect(finalEl.style.color).toBe('orange');
expect(finalEl.textContent).toBe('1');
}
});
- it('updates refs when remounting', () => {
+ it('updates refs when remounting', async () => {
if (__DEV__) {
const testRef = React.createRef();
- render(
+ await render(
() => {
class Hello extends React.Component {
getColor() {
@@ -3261,9 +3289,9 @@ describe('ReactFresh', () => {
}
});
- it('remounts on conversion from class to function and back', () => {
+ it('remounts on conversion from class to function and back', async () => {
if (__DEV__) {
- const HelloV1 = render(() => {
+ const HelloV1 = await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -3280,7 +3308,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -3311,14 +3339,14 @@ describe('ReactFresh', () => {
const newEl = container.firstChild;
expect(newEl.textContent).toBe('0');
expect(newEl.style.color).toBe('red');
- act(() => {
+ await act(() => {
newEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(newEl.textContent).toBe('1');
// Now top-level renders of both types resolve to latest.
- render(() => HelloV1);
- render(() => HelloV2);
+ await render(() => HelloV1);
+ await render(() => HelloV2);
expect(container.firstChild).toBe(newEl);
expect(newEl.style.color).toBe('red');
expect(newEl.textContent).toBe('1');
@@ -3342,14 +3370,14 @@ describe('ReactFresh', () => {
const finalEl = container.firstChild;
expect(finalEl.textContent).toBe('0');
expect(finalEl.style.color).toBe('orange');
- act(() => {
+ await act(() => {
finalEl.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(finalEl.textContent).toBe('1');
- render(() => HelloV3);
- render(() => HelloV2);
- render(() => HelloV1);
+ await render(() => HelloV3);
+ await render(() => HelloV2);
+ await render(() => HelloV1);
expect(container.firstChild).toBe(finalEl);
expect(finalEl.style.color).toBe('orange');
expect(finalEl.textContent).toBe('1');
@@ -3373,9 +3401,9 @@ describe('ReactFresh', () => {
}
});
- it('can find host instances for a family', () => {
+ it('can find host instances for a family', async () => {
if (__DEV__) {
- render(() => {
+ await render(() => {
function Child({children}) {
return {children}
;
}
@@ -3481,7 +3509,7 @@ describe('ReactFresh', () => {
});
}
- it('can update multiple roots independently', () => {
+ it('can update multiple roots independently', async () => {
if (__DEV__) {
// Declare the first version.
const HelloV1 = () => {
@@ -3504,7 +3532,9 @@ describe('ReactFresh', () => {
);
};
$RefreshReg$(HelloV2, 'Hello');
- ReactFreshRuntime.performReactRefresh();
+ await act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ });
// Mount three roots.
const cont1 = document.createElement('div');
@@ -3513,10 +3543,19 @@ describe('ReactFresh', () => {
document.body.appendChild(cont1);
document.body.appendChild(cont2);
document.body.appendChild(cont3);
+ const root1 = ReactDOMClient.createRoot(cont1);
+ const root2 = ReactDOMClient.createRoot(cont2);
+ const root3 = ReactDOMClient.createRoot(cont3);
try {
- ReactDOM.render( , cont1);
- ReactDOM.render( , cont2);
- ReactDOM.render( , cont3);
+ await act(() => {
+ root1.render( );
+ });
+ await act(() => {
+ root2.render( );
+ });
+ await act(() => {
+ root3.render( );
+ });
// Expect we see the V2 color.
expect(cont1.firstChild.style.color).toBe('red');
@@ -3527,7 +3566,7 @@ describe('ReactFresh', () => {
expect(cont3.firstChild.textContent).toBe('0');
// Bump the state for each of them.
- act(() => {
+ await act(() => {
cont1.firstChild.dispatchEvent(
new MouseEvent('click', {bubbles: true}),
);
@@ -3555,7 +3594,9 @@ describe('ReactFresh', () => {
);
};
$RefreshReg$(HelloV3, 'Hello');
- ReactFreshRuntime.performReactRefresh();
+ await act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ });
// It should affect all roots.
expect(cont1.firstChild.style.color).toBe('green');
@@ -3566,7 +3607,9 @@ describe('ReactFresh', () => {
expect(cont3.firstChild.textContent).toBe('1');
// Unmount the second root.
- ReactDOM.unmountComponentAtNode(cont2);
+ await act(() => {
+ root2.unmount();
+ });
// Make the first root throw and unmount on hot update.
const HelloV4 = ({id}) => {
if (id === 1) {
@@ -3580,9 +3623,11 @@ describe('ReactFresh', () => {
);
};
$RefreshReg$(HelloV4, 'Hello');
- expect(() => {
- ReactFreshRuntime.performReactRefresh();
- }).toThrow('Oops.');
+ await expect(
+ act(() => {
+ ReactFreshRuntime.performReactRefresh();
+ }),
+ ).rejects.toThrow('Oops.');
// Still, we expect the last root to be updated.
expect(cont1.innerHTML).toBe('');
@@ -3791,18 +3836,20 @@ describe('ReactFresh', () => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
- act = React.unstable_act;
- internalAct = require('internal-test-utils').act;
+ act = require('internal-test-utils').act;
// Important! Inject into the global hook *after* ReactDOM runs:
ReactFreshRuntime = require('react-refresh/runtime');
ReactFreshRuntime.injectIntoGlobalHook(global);
+ root = ReactDOMClient.createRoot(container);
+
// We're verifying that we're able to track roots mounted after this point.
// The rest of this test is taken from the simplest first test case.
- render(() => {
+ await render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
@@ -3819,7 +3866,7 @@ describe('ReactFresh', () => {
const el = container.firstChild;
expect(el.textContent).toBe('0');
expect(el.style.color).toBe('blue');
- act(() => {
+ await act(() => {
el.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(el.textContent).toBe('1');
@@ -3846,7 +3893,7 @@ describe('ReactFresh', () => {
});
// This simulates the scenario in https://github.com/facebook/react/issues/20100
- it('does not block DevTools when an unsupported renderer is injected', () => {
+ it('does not block DevTools when an unsupported legacy renderer is injected', () => {
if (__DEV__) {
initFauxDevToolsHook();
@@ -3872,13 +3919,12 @@ describe('ReactFresh', () => {
ReactFreshRuntime = require('react-refresh/runtime');
ReactFreshRuntime.injectIntoGlobalHook(global);
- render(() => {
- function Hello() {
- return Hi!
;
- }
- $RefreshReg$(Hello, 'Hello');
- return Hello;
- });
+ const Hello = () => {
+ return Hi!
;
+ };
+ $RefreshReg$(Hello, 'Hello');
+ const Component = Hello;
+ ReactDOM.render( , container);
expect(onCommitFiberRoot).toHaveBeenCalled();
}
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
index 5d966a16ded59..7c9271fcdcc19 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
@@ -226,20 +226,55 @@ describe('ReactFlightDOMEdge', () => {
const [stream1, stream2] = passThrough(stream).tee();
const serializedContent = await readResult(stream1);
+
expect(serializedContent.length).toBeLessThan(400);
expect(timesRendered).toBeLessThan(5);
- const result = await ReactServerDOMClient.createFromReadableStream(
- stream2,
- {
- ssrManifest: {
- moduleMap: null,
- moduleLoading: null,
- },
+ const model = await ReactServerDOMClient.createFromReadableStream(stream2, {
+ ssrManifest: {
+ moduleMap: null,
+ moduleLoading: null,
},
+ });
+
+ // Use the SSR render to resolve any lazy elements
+ const ssrStream = await ReactDOMServer.renderToReadableStream(model);
+ // Should still match the result when parsed
+ const result = await readResult(ssrStream);
+ expect(result).toEqual(resolvedChildren.join(''));
+ });
+
+ it('should execute repeated host components only once', async () => {
+ const div = this is a long return value
;
+ let timesRendered = 0;
+ function ServerComponent() {
+ timesRendered++;
+ return div;
+ }
+ const element = ;
+ const children = new Array(30).fill(element);
+ const resolvedChildren = new Array(30).fill(
+ 'this is a long return value
',
);
+ const stream = ReactServerDOMServer.renderToReadableStream(children);
+ const [stream1, stream2] = passThrough(stream).tee();
+
+ const serializedContent = await readResult(stream1);
+ expect(serializedContent.length).toBeLessThan(400);
+ expect(timesRendered).toBeLessThan(5);
+
+ const model = await ReactServerDOMClient.createFromReadableStream(stream2, {
+ ssrManifest: {
+ moduleMap: null,
+ moduleLoading: null,
+ },
+ });
+
+ // Use the SSR render to resolve any lazy elements
+ const ssrStream = await ReactDOMServer.renderToReadableStream(model);
// Should still match the result when parsed
- expect(result).toEqual(resolvedChildren);
+ const result = await readResult(ssrStream);
+ expect(result).toEqual(resolvedChildren.join(''));
});
it('should execute repeated server components in a compact form', async () => {
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
index b95a6c824f092..dcab688b10c83 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
@@ -54,7 +54,7 @@ describe('ReactFlightDOMForm', () => {
ReactServerDOMClient = require('react-server-dom-webpack/client.edge');
ReactDOMServer = require('react-dom/server.edge');
ReactDOMClient = require('react-dom/client');
- act = React.unstable_act;
+ act = React.act;
useFormState = require('react-dom').useFormState;
container = document.createElement('div');
document.body.appendChild(container);
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 683cbb9feb6f7..f04642737e167 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -16,6 +16,7 @@ import {
enablePostpone,
enableTaint,
enableServerContext,
+ enableServerComponentKeys,
} from 'shared/ReactFeatureFlags';
import {
@@ -181,6 +182,8 @@ type Task = {
model: ReactClientValue,
ping: () => void,
toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
+ keyPath: null | string, // parent server component keys
+ implicitSlot: boolean, // true if the root server component of this sequence had a null key
context: ContextSnapshot,
thenableState: ThenableState | null,
};
@@ -314,7 +317,14 @@ export function createRequest(
};
request.pendingChunks++;
const rootContext = createRootContext(context);
- const rootTask = createTask(request, model, rootContext, abortSet);
+ const rootTask = createTask(
+ request,
+ model,
+ null,
+ false,
+ rootContext,
+ abortSet,
+ );
pingedTasks.push(rootTask);
return request;
}
@@ -338,12 +348,18 @@ function createRootContext(
const POP = {};
-function serializeThenable(request: Request, thenable: Thenable): number {
+function serializeThenable(
+ request: Request,
+ task: Task,
+ thenable: Thenable,
+): number {
request.pendingChunks++;
const newTask = createTask(
request,
null,
- getActiveContext(),
+ task.keyPath, // the server component sequence continues through Promise-as-a-child.
+ task.implicitSlot,
+ task.context,
request.abortableTasks,
);
@@ -500,11 +516,86 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
return lazyType;
}
+function renderFragment(
+ request: Request,
+ task: Task,
+ children: $ReadOnlyArray,
+): ReactJSONValue {
+ if (!enableServerComponentKeys) {
+ return children;
+ }
+ if (task.keyPath !== null) {
+ // We have a Server Component that specifies a key but we're now splitting
+ // the tree using a fragment.
+ const fragment = [
+ REACT_ELEMENT_TYPE,
+ REACT_FRAGMENT_TYPE,
+ task.keyPath,
+ {children},
+ ];
+ if (!task.implicitSlot) {
+ // If this was keyed inside a set. I.e. the outer Server Component was keyed
+ // then we need to handle reorders of the whole set. To do this we need to wrap
+ // this array in a keyed Fragment.
+ return fragment;
+ }
+ // If the outer Server Component was implicit but then an inner one had a key
+ // we don't actually need to be able to move the whole set around. It'll always be
+ // in an implicit slot. The key only exists to be able to reset the state of the
+ // children. We could achieve the same effect by passing on the keyPath to the next
+ // set of components inside the fragment. This would also allow a keyless fragment
+ // reconcile against a single child.
+ // Unfortunately because of JSON.stringify, we can't call the recursive loop for
+ // each child within this context because we can't return a set with already resolved
+ // values. E.g. a string would get double encoded. Returning would pop the context.
+ // So instead, we wrap it with an unkeyed fragment and inner keyed fragment.
+ return [fragment];
+ }
+ // Since we're yielding here, that implicitly resets the keyPath context on the
+ // way up. Which is what we want since we've consumed it. If this changes to
+ // be recursive serialization, we need to reset the keyPath and implicitSlot,
+ // before recursing here.
+ return children;
+}
+
+function renderClientElement(
+ task: Task,
+ type: any,
+ key: null | string,
+ props: any,
+): ReactJSONValue {
+ if (!enableServerComponentKeys) {
+ return [REACT_ELEMENT_TYPE, type, key, props];
+ }
+ // We prepend the terminal client element that actually gets serialized with
+ // the keys of any Server Components which are not serialized.
+ const keyPath = task.keyPath;
+ if (key === null) {
+ key = keyPath;
+ } else if (keyPath !== null) {
+ key = keyPath + ',' + key;
+ }
+ const element = [REACT_ELEMENT_TYPE, type, key, props];
+ if (task.implicitSlot && key !== null) {
+ // The root Server Component had no key so it was in an implicit slot.
+ // If we had a key lower, it would end up in that slot with an explicit key.
+ // We wrap the element in a fragment to give it an implicit key slot with
+ // an inner explicit key.
+ return [element];
+ }
+ // Since we're yielding here, that implicitly resets the keyPath context on the
+ // way up. Which is what we want since we've consumed it. If this changes to
+ // be recursive serialization, we need to reset the keyPath and implicitSlot,
+ // before recursing here. We also need to reset it once we render into an array
+ // or anything else too which we also get implicitly.
+ return element;
+}
+
function renderElement(
request: Request,
task: Task,
type: any,
- key: null | React$Key,
+ key: null | string,
ref: mixed,
props: any,
): ReactJSONValue {
@@ -525,7 +616,7 @@ function renderElement(
if (typeof type === 'function') {
if (isClientReference(type)) {
// This is a reference to a Client Component.
- return [REACT_ELEMENT_TYPE, type, key, props];
+ return renderClientElement(task, type, key, props);
}
// This is a server-side component.
@@ -552,31 +643,52 @@ function renderElement(
// the thenable here.
result = createLazyWrapperAroundWakeable(result);
}
- return renderModelDestructive(request, task, emptyRoot, '', result);
+ // Track this element's key on the Server Component on the keyPath context..
+ const prevKeyPath = task.keyPath;
+ const prevImplicitSlot = task.implicitSlot;
+ if (key !== null) {
+ // Append the key to the path. Technically a null key should really add the child
+ // index. We don't do that to hold the payload small and implementation simple.
+ task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;
+ } else if (prevKeyPath === null) {
+ // This sequence of Server Components has no keys. This means that it was rendered
+ // in a slot that needs to assign an implicit key. Even if children below have
+ // explicit keys, they should not be used for the outer most key since it might
+ // collide with other slots in that set.
+ task.implicitSlot = true;
+ }
+ const json = renderModelDestructive(request, task, emptyRoot, '', result);
+ task.keyPath = prevKeyPath;
+ task.implicitSlot = prevImplicitSlot;
+ return json;
} else if (typeof type === 'string') {
// This is a host element. E.g. HTML.
- return [REACT_ELEMENT_TYPE, type, key, props];
+ return renderClientElement(task, type, key, props);
} else if (typeof type === 'symbol') {
- if (type === REACT_FRAGMENT_TYPE) {
+ if (type === REACT_FRAGMENT_TYPE && key === null) {
// For key-less fragments, we add a small optimization to avoid serializing
// it as a wrapper.
- // TODO: If a key is specified, we should propagate its key to any children.
- // Same as if a Server Component has a key.
- return renderModelDestructive(
+ const prevImplicitSlot = task.implicitSlot;
+ if (task.keyPath === null) {
+ task.implicitSlot = true;
+ }
+ const json = renderModelDestructive(
request,
task,
emptyRoot,
'',
props.children,
);
+ task.implicitSlot = prevImplicitSlot;
+ return json;
}
// This might be a built-in React component. We'll let the client decide.
// Any built-in works as long as its props are serializable.
- return [REACT_ELEMENT_TYPE, type, key, props];
+ return renderClientElement(task, type, key, props);
} else if (type != null && typeof type === 'object') {
if (isClientReference(type)) {
// This is a reference to a Client Component.
- return [REACT_ELEMENT_TYPE, type, key, props];
+ return renderClientElement(task, type, key, props);
}
switch (type.$$typeof) {
case REACT_LAZY_TYPE: {
@@ -596,7 +708,29 @@ function renderElement(
prepareToUseHooksForComponent(prevThenableState);
const result = render(props, undefined);
- return renderModelDestructive(request, task, emptyRoot, '', result);
+ const prevKeyPath = task.keyPath;
+ const prevImplicitSlot = task.implicitSlot;
+ if (key !== null) {
+ // Append the key to the path. Technically a null key should really add the child
+ // index. We don't do that to hold the payload small and implementation simple.
+ task.keyPath = prevKeyPath === null ? key : prevKeyPath + ',' + key;
+ } else if (prevKeyPath === null) {
+ // This sequence of Server Components has no keys. This means that it was rendered
+ // in a slot that needs to assign an implicit key. Even if children below have
+ // explicit keys, they should not be used for the outer most key since it might
+ // collide with other slots in that set.
+ task.implicitSlot = true;
+ }
+ const json = renderModelDestructive(
+ request,
+ task,
+ emptyRoot,
+ '',
+ result,
+ );
+ task.keyPath = prevKeyPath;
+ task.implicitSlot = prevImplicitSlot;
+ return json;
}
case REACT_MEMO_TYPE: {
return renderElement(request, task, type.type, key, ref, props);
@@ -618,13 +752,13 @@ function renderElement(
);
}
}
- return [
- REACT_ELEMENT_TYPE,
+ return renderClientElement(
+ task,
type,
key,
// Rely on __popProvider being serialized last to pop the provider.
{value: props.value, children: props.children, __pop: POP},
- ];
+ );
}
// Fallthrough
}
@@ -647,18 +781,31 @@ function pingTask(request: Request, task: Task): void {
function createTask(
request: Request,
model: ReactClientValue,
+ keyPath: null | string,
+ implicitSlot: boolean,
context: ContextSnapshot,
abortSet: Set,
): Task {
const id = request.nextChunkId++;
if (typeof model === 'object' && model !== null) {
- // Register this model as having the ID we're about to write.
- request.writtenObjects.set(model, id);
+ // If we're about to write this into a new task we can assign it an ID early so that
+ // any other references can refer to the value we're about to write.
+ if (
+ enableServerComponentKeys &&
+ (keyPath !== null || implicitSlot || context !== rootContextSnapshot)
+ ) {
+ // If we're in some kind of context we can't necessarily reuse this object depending
+ // what parent components are used.
+ } else {
+ request.writtenObjects.set(model, id);
+ }
}
const task: Task = {
id,
status: PENDING,
model,
+ keyPath,
+ implicitSlot,
context,
ping: () => pingTask(request, task),
toJSON: function (
@@ -855,7 +1002,9 @@ function outlineModel(request: Request, value: ReactClientValue): number {
const newTask = createTask(
request,
value,
- getActiveContext(),
+ null, // The way we use outlining is for reusing an object.
+ false, // It makes no sense for that use case to be contextual.
+ rootContextSnapshot, // Therefore we don't pass any contextual information along.
request.abortableTasks,
);
retryTask(request, newTask);
@@ -988,6 +1137,8 @@ function renderModel(
key: string,
value: ReactClientValue,
): ReactJSONValue {
+ const prevKeyPath = task.keyPath;
+ const prevImplicitSlot = task.implicitSlot;
try {
return renderModelDestructive(request, task, parent, key, value);
} catch (thrownValue) {
@@ -1016,12 +1167,20 @@ function renderModel(
const newTask = createTask(
request,
task.model,
- getActiveContext(),
+ task.keyPath,
+ task.implicitSlot,
+ task.context,
request.abortableTasks,
);
const ping = newTask.ping;
(x: any).then(ping, ping);
newTask.thenableState = getThenableStateAfterSuspending();
+
+ // Restore the context. We assume that this will be restored by the inner
+ // functions in case nothing throws so we don't use "finally" here.
+ task.keyPath = prevKeyPath;
+ task.implicitSlot = prevImplicitSlot;
+
if (wasReactNode) {
return serializeLazyID(newTask.id);
}
@@ -1034,12 +1193,24 @@ function renderModel(
const postponeId = request.nextChunkId++;
logPostpone(request, postponeInstance.message);
emitPostponeChunk(request, postponeId, postponeInstance);
+
+ // Restore the context. We assume that this will be restored by the inner
+ // functions in case nothing throws so we don't use "finally" here.
+ task.keyPath = prevKeyPath;
+ task.implicitSlot = prevImplicitSlot;
+
if (wasReactNode) {
return serializeLazyID(postponeId);
}
return serializeByValueID(postponeId);
}
}
+
+ // Restore the context. We assume that this will be restored by the inner
+ // functions in case nothing throws so we don't use "finally" here.
+ task.keyPath = prevKeyPath;
+ task.implicitSlot = prevImplicitSlot;
+
if (wasReactNode) {
// Something errored. We'll still send everything we have up until this point.
// We'll replace this element with a lazy reference that throws on the client
@@ -1089,18 +1260,31 @@ function renderModelDestructive(
const writtenObjects = request.writtenObjects;
const existingId = writtenObjects.get(value);
if (existingId !== undefined) {
- if (existingId === -1) {
- // Seen but not yet outlined.
- const newId = outlineModel(request, value);
- return serializeByValueID(newId);
+ if (
+ enableServerComponentKeys &&
+ (task.keyPath !== null ||
+ task.implicitSlot ||
+ task.context !== rootContextSnapshot)
+ ) {
+ // If we're in some kind of context we can't reuse the result of this render or
+ // previous renders of this element. We only reuse elements if they're not wrapped
+ // by another Server Component.
} else if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
modelRoot = null;
+ } else if (existingId === -1) {
+ // Seen but not yet outlined.
+ // TODO: If we throw here we can treat this as suspending which causes an outline
+ // but that is able to reuse the same task if we're already in one but then that
+ // will be a lazy future value rather than guaranteed to exist but maybe that's good.
+ const newId = outlineModel(request, (value: any));
+ return serializeLazyID(newId);
} else {
- // We've already emitted this as an outlined object, so we can
- // just refer to that by its existing ID.
- return serializeByValueID(existingId);
+ // We've already emitted this as an outlined object, so we can refer to that by its
+ // existing ID. We use a lazy reference since, unlike plain objects, elements might
+ // suspend so it might not have emitted yet even if we have the ID for it.
+ return serializeLazyID(existingId);
}
} else {
// This is the first time we've seen this object. We may never see it again
@@ -1108,13 +1292,13 @@ function renderModelDestructive(
writtenObjects.set(value, -1);
}
- // TODO: Concatenate keys of parents onto children.
const element: React$Element = (value: any);
// Attempt to render the Server Component.
return renderElement(
request,
task,
element.type,
+ // $FlowFixMe[incompatible-call] the key of an element is null | string
element.key,
element.ref,
element.props,
@@ -1155,7 +1339,18 @@ function renderModelDestructive(
// $FlowFixMe[method-unbinding]
if (typeof value.then === 'function') {
if (existingId !== undefined) {
- if (modelRoot === value) {
+ if (
+ enableServerComponentKeys &&
+ (task.keyPath !== null ||
+ task.implicitSlot ||
+ task.context !== rootContextSnapshot)
+ ) {
+ // If we're in some kind of context we can't reuse the result of this render or
+ // previous renders of this element. We only reuse Promises if they're not wrapped
+ // by another Server Component.
+ const promiseId = serializeThenable(request, task, (value: any));
+ return serializePromiseID(promiseId);
+ } else if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
modelRoot = null;
@@ -1166,7 +1361,7 @@ function renderModelDestructive(
}
// We assume that any object with a .then property is a "Thenable" type,
// or a Promise type. Either of which can be represented by a Promise.
- const promiseId = serializeThenable(request, (value: any));
+ const promiseId = serializeThenable(request, task, (value: any));
writtenObjects.set(value, promiseId);
return serializePromiseID(promiseId);
}
@@ -1195,14 +1390,14 @@ function renderModelDestructive(
}
if (existingId !== undefined) {
- if (existingId === -1) {
- // Seen but not yet outlined.
- const newId = outlineModel(request, value);
- return serializeByValueID(newId);
- } else if (modelRoot === value) {
+ if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
modelRoot = null;
+ } else if (existingId === -1) {
+ // Seen but not yet outlined.
+ const newId = outlineModel(request, (value: any));
+ return serializeByValueID(newId);
} else {
// We've already emitted this as an outlined object, so we can
// just refer to that by its existing ID.
@@ -1215,8 +1410,7 @@ function renderModelDestructive(
}
if (isArray(value)) {
- // $FlowFixMe[incompatible-return]
- return value;
+ return renderFragment(request, task, value);
}
if (value instanceof Map) {
@@ -1282,7 +1476,7 @@ function renderModelDestructive(
const iteratorFn = getIteratorFn(value);
if (iteratorFn) {
- return Array.from((value: any));
+ return renderFragment(request, task, Array.from((value: any)));
}
// Verify that this is a simple plain object.
@@ -1582,6 +1776,7 @@ function retryTask(request: Request, task: Task): void {
return;
}
+ const prevContext = getActiveContext();
switchContext(task.context);
try {
// Track the root so we know that we have to emit this object even though it
@@ -1602,15 +1797,22 @@ function retryTask(request: Request, task: Task): void {
// Track the root again for the resolved object.
modelRoot = resolvedModel;
- // If the value is a string, it means it's a terminal value adn we already escaped it
- // We don't need to escape it again so it's not passed the toJSON replacer.
- // Object might contain unresolved values like additional elements.
- // This is simulating what the JSON loop would do if this was part of it.
- // $FlowFixMe[incompatible-type] stringify can return null
- const json: string =
- typeof resolvedModel === 'string'
- ? stringify(resolvedModel)
- : stringify(resolvedModel, task.toJSON);
+ // The keyPath resets at any terminal child node.
+ task.keyPath = null;
+ task.implicitSlot = false;
+
+ let json: string;
+ if (typeof resolvedModel === 'object' && resolvedModel !== null) {
+ // Object might contain unresolved values like additional elements.
+ // This is simulating what the JSON loop would do if this was part of it.
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
+ json = stringify(resolvedModel, task.toJSON);
+ } else {
+ // If the value is a string, it means it's a terminal value and we already escaped it
+ // We don't need to escape it again so it's not passed the toJSON replacer.
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
+ json = stringify(resolvedModel);
+ }
emitModelChunk(request, task.id, json);
request.abortableTasks.delete(task);
@@ -1646,6 +1848,10 @@ function retryTask(request: Request, task: Task): void {
task.status = ERRORED;
const digest = logRecoverableError(request, x);
emitErrorChunk(request, task.id, digest, x);
+ } finally {
+ if (enableServerContext) {
+ switchContext(prevContext);
+ }
}
}
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index 4b19f599db5ac..c82c33811539c 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -54,7 +54,7 @@ import {getPublicInstance} from './ReactFiberConfigTestHost';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
-const act = React.unstable_act;
+const act = React.act;
// TODO: Remove from public bundle
diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js
index a417f3e755cab..7c84775bb6cc3 100644
--- a/packages/react/index.classic.fb.js
+++ b/packages/react/index.classic.fb.js
@@ -9,7 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
- act as unstable_act,
+ act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js
index dd86090f093e1..c35fd1bd55157 100644
--- a/packages/react/index.experimental.js
+++ b/packages/react/index.experimental.js
@@ -9,7 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
- act as unstable_act,
+ act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.js b/packages/react/index.js
index abce6537b5675..70f61f58e070b 100644
--- a/packages/react/index.js
+++ b/packages/react/index.js
@@ -30,7 +30,7 @@ export type ChildrenArray<+T> = $ReadOnlyArray> | T;
// We can't use export * from in Flow for some reason.
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
- act as unstable_act,
+ act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js
index 10ae150f64eef..4f73dded7f7eb 100644
--- a/packages/react/index.modern.fb.js
+++ b/packages/react/index.modern.fb.js
@@ -9,7 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
- act as unstable_act,
+ act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js
index 9f8e46063782a..2997a62b4a44a 100644
--- a/packages/react/index.stable.js
+++ b/packages/react/index.stable.js
@@ -9,7 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
- act as unstable_act,
+ act,
Children,
Component,
Fragment,
diff --git a/packages/react/jsx-runtime.react-server.js b/packages/react/jsx-runtime.react-server.js
new file mode 100644
index 0000000000000..fa48a68d3e0d9
--- /dev/null
+++ b/packages/react/jsx-runtime.react-server.js
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+export {Fragment, jsx, jsxs} from './src/jsx/ReactJSXServer';
diff --git a/packages/react/npm/jsx-runtime.react-server.js b/packages/react/npm/jsx-runtime.react-server.js
new file mode 100644
index 0000000000000..cd80277331d46
--- /dev/null
+++ b/packages/react/npm/jsx-runtime.react-server.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('./cjs/react-jsx-runtime.react-server.production.min.js');
+} else {
+ module.exports = require('./cjs/react-jsx-runtime.react-server.development.js');
+}
diff --git a/packages/react/package.json b/packages/react/package.json
index 39b545825875c..049e457704d4e 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -25,7 +25,10 @@
"default": "./index.js"
},
"./package.json": "./package.json",
- "./jsx-runtime": "./jsx-runtime.js",
+ "./jsx-runtime": {
+ "react-server": "./jsx-runtime.react-server.js",
+ "default": "./jsx-runtime.js"
+ },
"./jsx-dev-runtime": "./jsx-dev-runtime.js",
"./src/*": "./src/*"
},
diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js
index 32c17c64d5675..8ff0ea625c04f 100644
--- a/packages/react/src/__tests__/ReactChildren-test.js
+++ b/packages/react/src/__tests__/ReactChildren-test.js
@@ -11,12 +11,14 @@
describe('ReactChildren', () => {
let React;
- let ReactTestUtils;
+ let ReactDOMClient;
+ let act;
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactTestUtils = require('react-dom/test-utils');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
});
it('should support identity for simple', () => {
@@ -962,16 +964,20 @@ describe('ReactChildren', () => {
});
describe('with fragments enabled', () => {
- it('warns for keys for arrays of elements in a fragment', () => {
+ it('warns for keys for arrays of elements in a fragment', async () => {
class ComponentReturningArray extends React.Component {
render() {
return [
,
];
}
}
- expect(() =>
- ReactTestUtils.renderIntoDocument( ),
- ).toErrorDev(
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render( );
+ });
+ }).toErrorDev(
'Warning: ' +
'Each child in a list should have a unique "key" prop.' +
' See https://reactjs.org/link/warning-keys for more information.' +
@@ -979,20 +985,28 @@ describe('ReactChildren', () => {
);
});
- it('does not warn when there are keys on elements in a fragment', () => {
+ it('does not warn when there are keys on elements in a fragment', async () => {
class ComponentReturningArray extends React.Component {
render() {
return [
,
];
}
}
- ReactTestUtils.renderIntoDocument( );
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
});
- it('warns for keys for arrays at the top level', () => {
- expect(() =>
- ReactTestUtils.renderIntoDocument([
,
]),
- ).toErrorDev(
+ it('warns for keys for arrays at the top level', async () => {
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render([
,
]);
+ });
+ }).toErrorDev(
'Warning: ' +
'Each child in a list should have a unique "key" prop.' +
' See https://reactjs.org/link/warning-keys for more information.',
diff --git a/packages/react/src/__tests__/ReactElement-test.js b/packages/react/src/__tests__/ReactElement-test.js
index a7ff7d9747374..05aab3c326db7 100644
--- a/packages/react/src/__tests__/ReactElement-test.js
+++ b/packages/react/src/__tests__/ReactElement-test.js
@@ -13,7 +13,6 @@ let act;
let React;
let ReactDOMClient;
-let ReactTestUtils;
describe('ReactElement', () => {
let ComponentClass;
@@ -25,7 +24,6 @@ describe('ReactElement', () => {
React = require('react');
ReactDOMClient = require('react-dom/client');
- ReactTestUtils = require('react-dom/test-utils');
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
ComponentClass = class extends React.Component {
@@ -223,19 +221,21 @@ describe('ReactElement', () => {
expect(element.props).toEqual({foo: '56'});
});
- it('preserves the owner on the element', () => {
+ it('preserves the owner on the element', async () => {
let element;
+ let instance;
class Wrapper extends React.Component {
+ componentDidMount() {
+ instance = this;
+ }
render() {
element = React.createElement(ComponentClass);
return element;
}
}
-
- const instance = ReactTestUtils.renderIntoDocument(
- React.createElement(Wrapper),
- );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(Wrapper)));
expect(element._owner.stateNode).toBe(instance);
});
@@ -327,23 +327,28 @@ describe('ReactElement', () => {
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
- it('should normalize props with default values', () => {
+ it('should normalize props with default values', async () => {
+ let instance;
class Component extends React.Component {
+ componentDidMount() {
+ instance = this;
+ }
render() {
return React.createElement('span', null, this.props.prop);
}
}
Component.defaultProps = {prop: 'testKey'};
- const instance = ReactTestUtils.renderIntoDocument(
- React.createElement(Component),
- );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render(React.createElement(Component));
+ });
expect(instance.props.prop).toBe('testKey');
- const inst2 = ReactTestUtils.renderIntoDocument(
- React.createElement(Component, {prop: null}),
- );
- expect(inst2.props.prop).toBe(null);
+ await act(() => {
+ root.render(React.createElement(Component, {prop: null}));
+ });
+ expect(instance.props.prop).toBe(null);
});
it('throws when changing a prop (in dev) after element creation', async () => {
@@ -410,13 +415,20 @@ describe('ReactElement', () => {
}
});
- it('does not warn for NaN props', () => {
+ it('does not warn for NaN props', async () => {
+ let test;
class Test extends React.Component {
+ componentDidMount() {
+ test = this;
+ }
render() {
return
;
}
}
- const test = ReactTestUtils.renderIntoDocument( );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render( );
+ });
expect(test.props.value).toBeNaN();
});
});
diff --git a/packages/react/src/__tests__/ReactElementClone-test.js b/packages/react/src/__tests__/ReactElementClone-test.js
index 1aecc91359c28..053341013a6ed 100644
--- a/packages/react/src/__tests__/ReactElementClone-test.js
+++ b/packages/react/src/__tests__/ReactElementClone-test.js
@@ -9,19 +9,20 @@
'use strict';
+let act;
let PropTypes;
let React;
-let ReactDOM;
-let ReactTestUtils;
+let ReactDOMClient;
describe('ReactElementClone', () => {
let ComponentClass;
beforeEach(() => {
+ act = require('internal-test-utils').act;
+
PropTypes = require('prop-types');
React = require('react');
- ReactDOM = require('react-dom');
- ReactTestUtils = require('react-dom/test-utils');
+ ReactDOMClient = require('react-dom/client');
// NOTE: We're explicitly not using JSX here. This is intended to test
// classic JS without JSX.
@@ -32,10 +33,15 @@ describe('ReactElementClone', () => {
};
});
- it('should clone a DOM component with new props', () => {
+ it('should clone a DOM component with new props', async () => {
+ let div;
class Grandparent extends React.Component {
render() {
- return } />;
+ return (
+ (div = node)} className="child" />}
+ />
+ );
}
}
class Parent extends React.Component {
@@ -47,14 +53,21 @@ describe('ReactElementClone', () => {
);
}
}
- const component = ReactTestUtils.renderIntoDocument( );
- expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz');
+
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render( );
+ });
+ expect(div.className).toBe('xyz');
});
- it('should clone a composite component with new props', () => {
+ it('should clone a composite component with new props', async () => {
+ let div;
class Child extends React.Component {
render() {
- return
;
+ return (
+ (div = node)} className={this.props.className} />
+ );
}
}
class Grandparent extends React.Component {
@@ -71,8 +84,11 @@ describe('ReactElementClone', () => {
);
}
}
- const component = ReactTestUtils.renderIntoDocument(
);
- expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz');
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render(
);
+ });
+ expect(div.className).toBe('xyz');
});
it('does not fail if config has no prototype', () => {
@@ -80,10 +96,15 @@ describe('ReactElementClone', () => {
React.cloneElement(
, config);
});
- it('should keep the original ref if it is not overridden', () => {
+ it('should keep the original ref if it is not overridden', async () => {
+ let component;
class Grandparent extends React.Component {
yoloRef = React.createRef();
+ componentDidMount() {
+ component = this;
+ }
+
render() {
return
} />;
}
@@ -97,7 +118,11 @@ describe('ReactElementClone', () => {
}
}
- const component = ReactTestUtils.renderIntoDocument(
);
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render(
);
+ });
+
expect(component.yoloRef.current.tagName).toBe('DIV');
});
@@ -111,7 +136,7 @@ describe('ReactElementClone', () => {
expect(clone.key).toBe('xyz');
});
- it('should transfer children', () => {
+ it('should transfer children', async () => {
class Component extends React.Component {
render() {
expect(this.props.children).toBe('xyz');
@@ -119,12 +144,13 @@ describe('ReactElementClone', () => {
}
}
- ReactTestUtils.renderIntoDocument(
- React.cloneElement(
, {children: 'xyz'}),
- );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render(React.cloneElement(
, {children: 'xyz'}));
+ });
});
- it('should shallow clone children', () => {
+ it('should shallow clone children', async () => {
class Component extends React.Component {
render() {
expect(this.props.children).toBe('xyz');
@@ -132,9 +158,10 @@ describe('ReactElementClone', () => {
}
}
- ReactTestUtils.renderIntoDocument(
- React.cloneElement(
xyz , {}),
- );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ root.render(React.cloneElement(
xyz , {}));
+ });
});
it('should accept children as rest arguments', () => {
@@ -174,7 +201,8 @@ describe('ReactElementClone', () => {
expect(element2.props.children).toBe(undefined);
});
- it('should support keys and refs', () => {
+ it('should support keys and refs', async () => {
+ let component;
class Parent extends React.Component {
xyzRef = React.createRef();
@@ -192,6 +220,10 @@ describe('ReactElementClone', () => {
class Grandparent extends React.Component {
parentRef = React.createRef();
+ componentDidMount() {
+ component = this;
+ }
+
render() {
return (
@@ -201,11 +233,13 @@ describe('ReactElementClone', () => {
}
}
- const component = ReactTestUtils.renderIntoDocument( );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render( ));
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
});
- it('should steal the ref if a new ref is specified', () => {
+ it('should steal the ref if a new ref is specified', async () => {
+ let component;
class Parent extends React.Component {
xyzRef = React.createRef();
@@ -221,6 +255,10 @@ describe('ReactElementClone', () => {
parentRef = React.createRef();
childRef = React.createRef();
+ componentDidMount() {
+ component = this;
+ }
+
render() {
return (
@@ -230,12 +268,13 @@ describe('ReactElementClone', () => {
}
}
- const component = ReactTestUtils.renderIntoDocument( );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render( ));
expect(component.childRef).toEqual({current: null});
expect(component.parentRef.current.xyzRef.current.tagName).toBe('SPAN');
});
- it('should overwrite props', () => {
+ it('should overwrite props', async () => {
class Component extends React.Component {
render() {
expect(this.props.myprop).toBe('xyz');
@@ -243,8 +282,11 @@ describe('ReactElementClone', () => {
}
}
- ReactTestUtils.renderIntoDocument(
- React.cloneElement( , {myprop: 'xyz'}),
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() =>
+ root.render(
+ React.cloneElement( , {myprop: 'xyz'}),
+ ),
);
});
@@ -287,7 +329,7 @@ describe('ReactElementClone', () => {
React.cloneElement(
, null, [{}, {}]);
});
- it('should check declared prop types after clone', () => {
+ it('should check declared prop types after clone', async () => {
class Component extends React.Component {
static propTypes = {
color: PropTypes.string.isRequired,
@@ -308,8 +350,10 @@ describe('ReactElementClone', () => {
});
}
}
- expect(() =>
- ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)),
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await expect(
+ async () =>
+ await act(() => root.render(React.createElement(GrandParent))),
).toErrorDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `Component`, ' +
diff --git a/packages/react/src/__tests__/ReactElementValidator-test.internal.js b/packages/react/src/__tests__/ReactElementValidator-test.internal.js
index c447f554a71ac..dccee54a35f62 100644
--- a/packages/react/src/__tests__/ReactElementValidator-test.internal.js
+++ b/packages/react/src/__tests__/ReactElementValidator-test.internal.js
@@ -14,8 +14,8 @@
let PropTypes;
let React;
-let ReactDOM;
-let ReactTestUtils;
+let ReactDOMClient;
+let act;
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
@@ -29,8 +29,8 @@ describe('ReactElementValidator', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
React = require('react');
- ReactDOM = require('react-dom');
- ReactTestUtils = require('react-dom/test-utils');
+ ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
ComponentClass = class extends React.Component {
render() {
return React.createElement('div');
@@ -47,7 +47,7 @@ describe('ReactElementValidator', () => {
}).toErrorDev('Each child in a list should have a unique "key" prop.');
});
- it('warns for keys for arrays of elements with owner info', () => {
+ it('warns for keys for arrays of elements with owner info', async () => {
class InnerClass extends React.Component {
render() {
return React.createElement(ComponentClass, null, this.props.childSet);
@@ -65,8 +65,9 @@ describe('ReactElementValidator', () => {
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(ComponentWrapper)));
}).toErrorDev(
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the render method of `InnerClass`. ' +
@@ -74,7 +75,7 @@ describe('ReactElementValidator', () => {
);
});
- it('warns for keys for arrays with no owner or parent info', () => {
+ it('warns for keys for arrays with no owner or parent info', async () => {
function Anonymous() {
return
;
}
@@ -82,8 +83,9 @@ describe('ReactElementValidator', () => {
const divs = [
,
];
- expect(() => {
- ReactTestUtils.renderIntoDocument({divs} );
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render({divs} ));
}).toErrorDev(
'Warning: Each child in a list should have a unique ' +
'"key" prop. See https://reactjs.org/link/warning-keys for more information.\n' +
@@ -91,11 +93,13 @@ describe('ReactElementValidator', () => {
);
});
- it('warns for keys for arrays of elements with no owner info', () => {
+ it('warns for keys for arrays of elements with no owner info', async () => {
const divs = [
,
];
- expect(() => {
- ReactTestUtils.renderIntoDocument({divs}
);
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+
+ await act(() => root.render({divs}
));
}).toErrorDev(
'Warning: Each child in a list should have a unique ' +
'"key" prop.\n\nCheck the top-level render call using . See ' +
@@ -104,7 +108,7 @@ describe('ReactElementValidator', () => {
);
});
- it('warns for keys with component stack info', () => {
+ it('warns for keys with component stack info', async () => {
function Component() {
return
;
}
@@ -117,7 +121,10 @@ describe('ReactElementValidator', () => {
return
} />;
}
- expect(() => ReactTestUtils.renderIntoDocument(
)).toErrorDev(
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(
));
+ }).toErrorDev(
'Warning: Each child in a list should have a unique ' +
'"key" prop.\n\nCheck the render method of `Component`. See ' +
'https://reactjs.org/link/warning-keys for more information.\n' +
@@ -128,7 +135,7 @@ describe('ReactElementValidator', () => {
);
});
- it('does not warn for keys when passing children down', () => {
+ it('does not warn for keys when passing children down', async () => {
function Wrapper(props) {
return (
@@ -138,11 +145,14 @@ describe('ReactElementValidator', () => {
);
}
- ReactTestUtils.renderIntoDocument(
-
-
-
- ,
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() =>
+ root.render(
+
+
+
+ ,
+ ),
);
});
@@ -208,7 +218,7 @@ describe('ReactElementValidator', () => {
React.createElement(ComponentClass, null, [{}, {}]);
});
- it('should give context for PropType errors in nested components.', () => {
+ it('should give context for PropType errors in nested components.', async () => {
// In this test, we're making sure that if a proptype error is found in a
// component, we give a small hint as to which parent instantiated that
// component as per warnings about key usage in ReactElementValidator.
@@ -221,8 +231,9 @@ describe('ReactElementValidator', () => {
function ParentComp() {
return React.createElement(MyComp, {color: 123});
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(ParentComp)));
}).toErrorDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
@@ -288,28 +299,33 @@ describe('ReactElementValidator', () => {
React.createElement('div');
});
- it('includes the owner name when passing null, undefined, boolean, or number', () => {
+ it('includes the owner name when passing null, undefined, boolean, or number', async () => {
function ParentComp() {
return React.createElement(null);
}
- expect(() => {
- expect(() => {
- ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
- }).toThrowError(
+ await expect(async () => {
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(ParentComp)));
+ }).rejects.toThrowError(
'Element type is invalid: expected a string (for built-in components) ' +
'or a class/function (for composite components) but got: null.' +
(__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
);
- }).toErrorDev(
+ }).toErrorDev([
'Warning: React.createElement: type is invalid -- expected a string ' +
'(for built-in components) or a class/function (for composite ' +
'components) but got: null.' +
'\n\nCheck the render method of `ParentComp`.\n in ParentComp',
- );
+ 'Warning: React.createElement: type is invalid -- expected a string ' +
+ '(for built-in components) or a class/function (for composite ' +
+ 'components) but got: null.' +
+ '\n\nCheck the render method of `ParentComp`.\n in ParentComp',
+ ]);
});
- it('should check default prop values', () => {
+ it('should check default prop values', async () => {
class Component extends React.Component {
static propTypes = {prop: PropTypes.string.isRequired};
static defaultProps = {prop: null};
@@ -318,16 +334,17 @@ describe('ReactElementValidator', () => {
}
}
- expect(() =>
- ReactTestUtils.renderIntoDocument(React.createElement(Component)),
- ).toErrorDev(
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(Component)));
+ }).toErrorDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
'`Component`, but its value is `null`.\n' +
' in Component',
);
});
- it('should not check the default for explicit null', () => {
+ it('should not check the default for explicit null', async () => {
class Component extends React.Component {
static propTypes = {prop: PropTypes.string.isRequired};
static defaultProps = {prop: 'text'};
@@ -336,9 +353,10 @@ describe('ReactElementValidator', () => {
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(
- React.createElement(Component, {prop: null}),
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() =>
+ root.render(React.createElement(Component, {prop: null})),
);
}).toErrorDev(
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
@@ -347,7 +365,7 @@ describe('ReactElementValidator', () => {
);
});
- it('should check declared prop types', () => {
+ it('should check declared prop types', async () => {
class Component extends React.Component {
static propTypes = {
prop: PropTypes.string.isRequired,
@@ -357,11 +375,10 @@ describe('ReactElementValidator', () => {
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(React.createElement(Component));
- ReactTestUtils.renderIntoDocument(
- React.createElement(Component, {prop: 42}),
- );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await expect(async () => {
+ await act(() => root.render(React.createElement(Component)));
+ await act(() => root.render(React.createElement(Component, {prop: 42})));
}).toErrorDev([
'Warning: Failed prop type: ' +
'The prop `prop` is marked as required in `Component`, but its value ' +
@@ -374,12 +391,12 @@ describe('ReactElementValidator', () => {
]);
// Should not error for strings
- ReactTestUtils.renderIntoDocument(
- React.createElement(Component, {prop: 'string'}),
+ await act(() =>
+ root.render(React.createElement(Component, {prop: 'string'})),
);
});
- it('should warn if a PropType creator is used as a PropType', () => {
+ it('should warn if a PropType creator is used as a PropType', async () => {
class Component extends React.Component {
static propTypes = {
myProp: PropTypes.shape,
@@ -389,9 +406,10 @@ describe('ReactElementValidator', () => {
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(
- React.createElement(Component, {myProp: {value: 'hi'}}),
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() =>
+ root.render(React.createElement(Component, {myProp: {value: 'hi'}})),
);
}).toErrorDev(
'Warning: Component: type specification of prop `myProp` is invalid; ' +
@@ -402,7 +420,7 @@ describe('ReactElementValidator', () => {
);
});
- it('should warn if component declares PropTypes instead of propTypes', () => {
+ it('should warn if component declares PropTypes instead of propTypes', async () => {
class MisspelledPropTypesComponent extends React.Component {
static PropTypes = {
prop: PropTypes.string,
@@ -412,9 +430,12 @@ describe('ReactElementValidator', () => {
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(
- React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() =>
+ root.render(
+ React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
+ ),
);
}).toErrorDev(
'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
@@ -423,15 +444,16 @@ describe('ReactElementValidator', () => {
);
});
- it('warns for fragments with illegal attributes', () => {
+ it('warns for fragments with illegal attributes', async () => {
class Foo extends React.Component {
render() {
return React.createElement(React.Fragment, {a: 1}, '123');
}
}
- expect(() => {
- ReactTestUtils.renderIntoDocument(React.createElement(Foo));
+ await expect(async () => {
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => root.render(React.createElement(Foo)));
}).toErrorDev(
'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
'can only have `key` and `children` props.',
@@ -466,19 +488,23 @@ describe('ReactElementValidator', () => {
});
}
- it('does not warn when using DOM node as children', () => {
+ it('does not warn when using DOM node as children', async () => {
class DOMContainer extends React.Component {
+ ref;
render() {
- return
;
+ return
(this.ref = n)} />;
}
componentDidMount() {
- ReactDOM.findDOMNode(this).appendChild(this.props.children);
+ this.ref.appendChild(this.props.children);
}
}
const node = document.createElement('div');
- // This shouldn't cause a stack overflow or any other problems (#3883)
- ReactTestUtils.renderIntoDocument(
{node} );
+ const root = ReactDOMClient.createRoot(document.createElement('div'));
+ await act(() => {
+ // This shouldn't cause a stack overflow or any other problems (#3883)
+ root.render(
{node} );
+ });
});
it('should not enumerate enumerable numbers (#4776)', () => {
diff --git a/packages/react/src/__tests__/ReactJSXElement-test.js b/packages/react/src/__tests__/ReactJSXElement-test.js
index 7567e2b58fec5..39f2eae0dc566 100644
--- a/packages/react/src/__tests__/ReactJSXElement-test.js
+++ b/packages/react/src/__tests__/ReactJSXElement-test.js
@@ -10,8 +10,9 @@
'use strict';
let React;
-let ReactDOM;
+let ReactDOMClient;
let ReactTestUtils;
+let act;
describe('ReactJSXElement', () => {
let Component;
@@ -20,8 +21,10 @@ describe('ReactJSXElement', () => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactTestUtils = require('react-dom/test-utils');
+ act = require('internal-test-utils').act;
+
Component = class extends React.Component {
render() {
return
;
@@ -172,14 +175,20 @@ describe('ReactJSXElement', () => {
expect(element.constructor).toBe(object.constructor);
});
- it('should use default prop value when removing a prop', () => {
+ it('should use default prop value when removing a prop', async () => {
Component.defaultProps = {fruit: 'persimmon'};
const container = document.createElement('div');
- const instance = ReactDOM.render(
, container);
+ const root = ReactDOMClient.createRoot(container);
+ let instance;
+ await act(() => {
+ root.render(
(instance = ref)} />);
+ });
expect(instance.props.fruit).toBe('mango');
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( (instance = ref)} />);
+ });
expect(instance.props.fruit).toBe('persimmon');
});
diff --git a/packages/react/src/__tests__/ReactJSXElementValidator-test.js b/packages/react/src/__tests__/ReactJSXElementValidator-test.js
index 71753ed0ace20..62d29ee68fefb 100644
--- a/packages/react/src/__tests__/ReactJSXElementValidator-test.js
+++ b/packages/react/src/__tests__/ReactJSXElementValidator-test.js
@@ -13,8 +13,10 @@
// of dynamic errors when using JSX with Flow.
let React;
let ReactDOM;
+let ReactDOMClient;
let ReactTestUtils;
let PropTypes;
+let act;
describe('ReactJSXElementValidator', () => {
let Component;
@@ -26,7 +28,9 @@ describe('ReactJSXElementValidator', () => {
PropTypes = require('prop-types');
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactTestUtils = require('react-dom/test-utils');
+ act = require('internal-test-utils').act;
Component = class extends React.Component {
render() {
@@ -172,7 +176,7 @@ describe('ReactJSXElementValidator', () => {
);
});
- it('should update component stack after receiving next element', () => {
+ it('should update component stack after receiving next element', async () => {
function MyComp() {
return null;
}
@@ -192,9 +196,14 @@ describe('ReactJSXElementValidator', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
expect(() =>
- ReactDOM.render( , container),
+ ReactDOM.flushSync(() => {
+ root.render( );
+ }),
).toErrorDev(
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
diff --git a/packages/react/src/__tests__/ReactStrictMode-test.js b/packages/react/src/__tests__/ReactStrictMode-test.js
index c0d392a61f2da..c0a4d59f3d135 100644
--- a/packages/react/src/__tests__/ReactStrictMode-test.js
+++ b/packages/react/src/__tests__/ReactStrictMode-test.js
@@ -34,19 +34,21 @@ describe('ReactStrictMode', () => {
useReducer = React.useReducer;
});
- it('should appear in the client component stack', () => {
+ it('should appear in the client component stack', async () => {
function Foo() {
return
;
}
const container = document.createElement('div');
- expect(() => {
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
}).toErrorDev(
'Invalid ARIA attribute `ariaTypo`. ' +
'ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
@@ -75,7 +77,7 @@ describe('ReactStrictMode', () => {
});
// @gate __DEV__
- it('should invoke precommit lifecycle methods twice', () => {
+ it('should invoke only precommit lifecycle methods twice in legacy roots', async () => {
let log = [];
let shouldComponentUpdate = false;
class ClassComponent extends React.Component {
@@ -162,7 +164,7 @@ describe('ReactStrictMode', () => {
]);
});
- it('should invoke setState callbacks twice', () => {
+ it('should invoke setState callbacks twice', async () => {
let instance;
class ClassComponent extends React.Component {
state = {
@@ -177,17 +179,21 @@ describe('ReactStrictMode', () => {
let setStateCount = 0;
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- instance.setState(state => {
- setStateCount++;
- return {
- count: state.count + 1,
- };
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ instance.setState(state => {
+ setStateCount++;
+ return {
+ count: state.count + 1,
+ };
+ });
});
// Callback should be invoked twice in DEV
@@ -196,7 +202,7 @@ describe('ReactStrictMode', () => {
expect(instance.state.count).toBe(2);
});
- it('should invoke precommit lifecycle methods twice in DEV', () => {
+ it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
const {StrictMode} = React;
let log = [];
@@ -303,7 +309,7 @@ describe('ReactStrictMode', () => {
}
});
- it('should invoke setState callbacks twice in DEV', () => {
+ it('should invoke setState callbacks twice in DEV', async () => {
const {StrictMode} = React;
let instance;
@@ -320,17 +326,21 @@ describe('ReactStrictMode', () => {
let setStateCount = 0;
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- instance.setState(state => {
- setStateCount++;
- return {
- count: state.count + 1,
- };
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ instance.setState(state => {
+ setStateCount++;
+ return {
+ count: state.count + 1,
+ };
+ });
});
// Callback should be invoked twice (in DEV)
@@ -522,7 +532,6 @@ describe('Concurrent Mode', () => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
});
@@ -727,7 +736,7 @@ Please update the following components: Parent`,
await act(() => root.render( ));
});
- it('should also warn inside of "strict" mode trees', () => {
+ it('should also warn inside of "strict" mode trees', async () => {
const {StrictMode} = React;
class SyncRoot extends React.Component {
@@ -765,13 +774,20 @@ Please update the following components: Parent`,
const container = document.createElement('div');
- expect(() => ReactDOM.render( , container)).toErrorDev(
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render( );
+ });
+ }).toErrorDev(
'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended',
{withoutStack: true},
);
// Dedupe
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
});
});
@@ -779,11 +795,11 @@ describe('symbol checks', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
});
- it('should switch from StrictMode to a Fragment and reset state', () => {
+ it('should switch from StrictMode to a Fragment and reset state', async () => {
const {Fragment, StrictMode} = React;
function ParentComponent({useFragment}) {
@@ -813,13 +829,18 @@ describe('symbol checks', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:1');
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:1');
});
- it('should switch from a Fragment to StrictMode and reset state', () => {
+ it('should switch from a Fragment to StrictMode and reset state', async () => {
const {Fragment, StrictMode} = React;
function ParentComponent({useFragment}) {
@@ -849,13 +870,18 @@ describe('symbol checks', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:1');
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:1');
});
- it('should update with StrictMode without losing state', () => {
+ it('should update with StrictMode without losing state', async () => {
const {StrictMode} = React;
function ParentComponent() {
@@ -881,9 +907,14 @@ describe('symbol checks', () => {
}
const container = document.createElement('div');
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:1');
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
expect(container.textContent).toBe('count:2');
});
});
@@ -894,9 +925,10 @@ describe('string refs', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
});
- it('should warn within a strict tree', () => {
+ it('should warn within a strict tree', async () => {
const {StrictMode} = React;
class OuterComponent extends React.Component {
@@ -916,8 +948,11 @@ describe('string refs', () => {
}
const container = document.createElement('div');
- expect(() => {
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render( );
+ });
}).toErrorDev(
'Warning: Component "StrictMode" contains the string ref "somestring". ' +
'Support for string refs will be removed in a future major release. ' +
@@ -926,49 +961,47 @@ describe('string refs', () => {
' in OuterComponent (at **)',
);
- // Dedup
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
});
- it('should warn within a strict tree', () => {
+ it('should warn within a strict tree', async () => {
const {StrictMode} = React;
class OuterComponent extends React.Component {
render() {
return (
-
+
);
}
}
class InnerComponent extends React.Component {
- render() {
- return ;
- }
- }
-
- class MiddleComponent extends React.Component {
render() {
return null;
}
}
const container = document.createElement('div');
- expect(() => {
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render( );
+ });
}).toErrorDev(
- 'Warning: Component "InnerComponent" contains the string ref "somestring". ' +
+ 'Warning: Component "StrictMode" contains the string ref "somestring". ' +
'Support for string refs will be removed in a future major release. ' +
'We recommend using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
- ' in InnerComponent (at **)\n' +
' in OuterComponent (at **)',
);
- // Dedup
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
});
});
@@ -976,8 +1009,8 @@ describe('context legacy', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
PropTypes = require('prop-types');
});
@@ -986,7 +1019,7 @@ describe('context legacy', () => {
});
// @gate !disableLegacyContext || !__DEV__
- it('should warn if the legacy context API have been used in strict mode', () => {
+ it('should warn if the legacy context API have been used in strict mode', async () => {
class LegacyContextProvider extends React.Component {
getChildContext() {
return {color: 'purple'};
@@ -1039,8 +1072,11 @@ describe('context legacy', () => {
};
const container = document.createElement('div');
- expect(() => {
- ReactDOM.render( , container);
+ const root = ReactDOMClient.createRoot(container);
+ await expect(async () => {
+ await act(() => {
+ root.render( );
+ });
}).toErrorDev(
'Warning: Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
@@ -1055,19 +1091,21 @@ describe('context legacy', () => {
);
// Dedupe
- ReactDOM.render( , container);
+ await act(() => {
+ root.render( );
+ });
});
describe('console logs logging', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
+ act = require('internal-test-utils').act;
});
if (ReactFeatureFlags.consoleManagedByDevToolsDuringStrictMode) {
- it('does not disable logs for class double render', () => {
+ it('does not disable logs for class double render', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1080,13 +1118,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(__DEV__ ? 2 : 1);
// Note: we should display the first log because otherwise
@@ -1095,7 +1134,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('does not disable logs for class double ctor', () => {
+ it('does not disable logs for class double ctor', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1111,13 +1150,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(__DEV__ ? 2 : 1);
// Note: we should display the first log because otherwise
@@ -1126,7 +1166,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('does not disable logs for class double getDerivedStateFromProps', () => {
+ it('does not disable logs for class double getDerivedStateFromProps', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1143,13 +1183,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(__DEV__ ? 2 : 1);
// Note: we should display the first log because otherwise
@@ -1158,7 +1199,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('does not disable logs for class double shouldComponentUpdate', () => {
+ it('does not disable logs for class double shouldComponentUpdate', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1175,19 +1216,21 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- // Trigger sCU:
- ReactDOM.render(
-
-
- ,
- container,
- );
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(__DEV__ ? 2 : 1);
@@ -1197,7 +1240,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('does not disable logs for class state updaters', () => {
+ it('does not disable logs for class state updaters', async () => {
spyOnDevAndProd(console, 'log');
let inst;
@@ -1211,16 +1254,20 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- inst.setState(() => {
- count++;
- console.log('foo ' + count);
- return {};
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ inst.setState(() => {
+ count++;
+ console.log('foo ' + count);
+ return {};
+ });
});
expect(count).toBe(__DEV__ ? 2 : 1);
@@ -1231,7 +1278,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('does not disable logs for function double render', () => {
+ it('does not disable logs for function double render', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1242,13 +1289,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(__DEV__ ? 2 : 1);
// Note: we should display the first log because otherwise
@@ -1257,7 +1305,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
} else {
- it('disable logs for class double render', () => {
+ it('disable logs for class double render', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1270,13 +1318,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(1);
// Note: we should display the first log because otherwise
@@ -1285,7 +1334,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('disables logs for class double ctor', () => {
+ it('disables logs for class double ctor', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1301,13 +1350,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(1);
// Note: we should display the first log because otherwise
@@ -1316,7 +1366,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('disable logs for class double getDerivedStateFromProps', () => {
+ it('disable logs for class double getDerivedStateFromProps', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1333,13 +1383,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(1);
// Note: we should display the first log because otherwise
@@ -1348,7 +1399,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('disable logs for class double shouldComponentUpdate', () => {
+ it('disable logs for class double shouldComponentUpdate', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1365,20 +1416,21 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- // Trigger sCU:
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(1);
// Note: we should display the first log because otherwise
@@ -1387,7 +1439,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('disable logs for class state updaters', () => {
+ it('disable logs for class state updaters', async () => {
spyOnDevAndProd(console, 'log');
let inst;
@@ -1401,16 +1453,20 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
- inst.setState(() => {
- count++;
- console.log('foo ' + count);
- return {};
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
+ await act(() => {
+ inst.setState(() => {
+ count++;
+ console.log('foo ' + count);
+ return {};
+ });
});
expect(count).toBe(__DEV__ ? 2 : 1);
@@ -1421,7 +1477,7 @@ describe('context legacy', () => {
expect(console.log).toBeCalledWith('foo 1');
});
- it('disable logs for function double render', () => {
+ it('disable logs for function double render', async () => {
spyOnDevAndProd(console, 'log');
let count = 0;
@@ -1432,13 +1488,14 @@ describe('context legacy', () => {
}
const container = document.createElement('div');
- ReactDOM.render(
-
-
- ,
- container,
- );
-
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render(
+
+
+ ,
+ );
+ });
expect(count).toBe(__DEV__ ? 2 : 1);
expect(console.log).toBeCalledTimes(1);
// Note: we should display the first log because otherwise
diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js
index dff9042a1bd21..dfba4146f443a 100644
--- a/packages/react/src/__tests__/createReactClassIntegration-test.js
+++ b/packages/react/src/__tests__/createReactClassIntegration-test.js
@@ -14,7 +14,6 @@ let act;
let PropTypes;
let React;
let ReactDOMClient;
-let ReactTestUtils;
let createReactClass;
describe('create-react-class-integration', () => {
@@ -24,7 +23,6 @@ describe('create-react-class-integration', () => {
PropTypes = require('prop-types');
React = require('react');
ReactDOMClient = require('react-dom/client');
- ReactTestUtils = require('react-dom/test-utils');
createReactClass = require('create-react-class/factory')(
React.Component,
React.isValidElement,
@@ -231,7 +229,7 @@ describe('create-react-class-integration', () => {
]);
});
- it('should support statics', () => {
+ it('should support statics', async () => {
const Component = createReactClass({
statics: {
abc: 'def',
@@ -247,8 +245,13 @@ describe('create-react-class-integration', () => {
return ;
},
});
- let instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let instance;
+ await act(() => {
+ root.render( (instance = current)} />);
+ });
+
expect(instance.constructor.abc).toBe('def');
expect(Component.abc).toBe('def');
expect(instance.constructor.def).toBe(0);
@@ -261,7 +264,7 @@ describe('create-react-class-integration', () => {
expect(Component.pqr()).toBe(Component);
});
- it('should work with object getInitialState() return values', () => {
+ it('should work with object getInitialState() return values', async () => {
const Component = createReactClass({
getInitialState: function () {
return {
@@ -272,12 +275,17 @@ describe('create-react-class-integration', () => {
return ;
},
});
- let instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let instance;
+ await act(() => {
+ root.render( (instance = current)} />);
+ });
+
expect(instance.state.occupation).toEqual('clown');
});
- it('should work with getDerivedStateFromProps() return values', () => {
+ it('should work with getDerivedStateFromProps() return values', async () => {
const Component = createReactClass({
getInitialState() {
return {};
@@ -289,8 +297,12 @@ describe('create-react-class-integration', () => {
Component.getDerivedStateFromProps = () => {
return {occupation: 'clown'};
};
- let instance = ;
- instance = ReactTestUtils.renderIntoDocument(instance);
+ let instance;
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await act(() => {
+ root.render( (instance = current)} />);
+ });
expect(instance.state.occupation).toEqual('clown');
});
@@ -328,8 +340,9 @@ describe('create-react-class-integration', () => {
expect(container.firstChild.className).toBe('foo');
});
- it('should throw with non-object getInitialState() return values', () => {
- [['an array'], 'a string', 1234].forEach(function (state) {
+ it('should throw with non-object getInitialState() return values', async () => {
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const state of [['an array'], 'a string', 1234]) {
const Component = createReactClass({
getInitialState: function () {
return state;
@@ -338,16 +351,19 @@ describe('create-react-class-integration', () => {
return ;
},
});
- let instance = ;
- expect(function () {
- instance = ReactTestUtils.renderIntoDocument(instance);
- }).toThrowError(
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ act(() => {
+ root.render( );
+ }),
+ ).rejects.toThrowError(
'Component.getInitialState(): must return an object or null',
);
- });
+ }
});
- it('should work with a null getInitialState() return value', () => {
+ it('should work with a null getInitialState() return value', async () => {
const Component = createReactClass({
getInitialState: function () {
return null;
@@ -356,9 +372,13 @@ describe('create-react-class-integration', () => {
return ;
},
});
- expect(() =>
- ReactTestUtils.renderIntoDocument( ),
- ).not.toThrow();
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ await expect(
+ act(() => {
+ root.render( );
+ }),
+ ).resolves.not.toThrow();
});
it('should throw when using legacy factories', () => {
@@ -375,7 +395,7 @@ describe('create-react-class-integration', () => {
);
});
- it('replaceState and callback works', () => {
+ it('replaceState and callback works', async () => {
const ops = [];
const Component = createReactClass({
getInitialState() {
@@ -387,10 +407,19 @@ describe('create-react-class-integration', () => {
},
});
- const instance = ReactTestUtils.renderIntoDocument( );
- instance.replaceState({step: 1}, () => {
- ops.push('Callback: ' + instance.state.step);
+ const container = document.createElement('div');
+ const root = ReactDOMClient.createRoot(container);
+ let instance;
+ await act(() => {
+ root.render( (instance = current)} />);
+ });
+
+ await act(() => {
+ instance.replaceState({step: 1}, () => {
+ ops.push('Callback: ' + instance.state.step);
+ });
});
+
expect(ops).toEqual(['Render: 0', 'Render: 1', 'Callback: 1']);
});
diff --git a/packages/react/src/jsx/ReactJSXServer.js b/packages/react/src/jsx/ReactJSXServer.js
new file mode 100644
index 0000000000000..2112cd80776a1
--- /dev/null
+++ b/packages/react/src/jsx/ReactJSXServer.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+// These are implementations of the jsx APIs for React Server runtimes.
+import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
+import {
+ jsxWithValidationStatic,
+ jsxWithValidationDynamic,
+} from './ReactJSXElementValidator';
+import {jsx as jsxProd} from './ReactJSXElement';
+const jsx: any = __DEV__ ? jsxWithValidationDynamic : jsxProd;
+// we may want to special case jsxs internally to take advantage of static children.
+// for now we can ship identical prod functions
+const jsxs: any = __DEV__ ? jsxWithValidationStatic : jsxProd;
+
+export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs};
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 2645259e21244..332e7f7bdff27 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -15,6 +15,8 @@
export const enableComponentStackLocations = true;
+export const enableServerComponentKeys = __EXPERIMENTAL__;
+
// -----------------------------------------------------------------------------
// Killswitch
//
@@ -171,13 +173,6 @@ export const enableFilterEmptyStringAttributesDOM = __NEXT_MAJOR__;
// when we plan to enable them.
// -----------------------------------------------------------------------------
-// This flag enables Strict Effects by default. We're not turning this on until
-// after 18 because it requires migration work. Recommendation is to use
-// to gradually upgrade components.
-// If TRUE, trees rendered with createRoot will be StrictEffectsMode.
-// If FALSE, these trees will be StrictLegacyMode.
-export const createRootStrictEffectsByDefault = false;
-
export const disableModulePatternComponents = false;
export const enableUseRefAccessWarning = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index a755367778cf1..2b314b6ff2a07 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -72,8 +72,6 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
-export const createRootStrictEffectsByDefault = false;
-
export const disableSchedulerTimeoutInWorkLoop = false;
export const enableLazyContextPropagation = false;
export const enableLegacyHidden = true;
@@ -95,5 +93,7 @@ export const enableFizzExternalRuntime = false;
export const enableAsyncActions = false;
export const enableUseDeferredValueInitialArg = true;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index bbf13e845205d..9bf741d74a278 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -55,7 +55,6 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
-export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
export const disableSchedulerTimeoutInWorkLoop = false;
@@ -86,5 +85,7 @@ export const useMicrotasksForSchedulingInFabric = false;
export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 3d38a4d24795f..3dcf66f535a9c 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -55,7 +55,6 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
-export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
export const disableSchedulerTimeoutInWorkLoop = false;
@@ -86,5 +85,7 @@ export const useMicrotasksForSchedulingInFabric = false;
export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
index d3c65d1679607..a0eaf1f80966d 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
@@ -49,7 +49,6 @@ export const enableCPUSuspense = false;
export const enableUseMemoCacheHook = true;
export const enableUseEffectEventHook = false;
export const enableClientRenderFallbackOnTextMismatch = true;
-export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
export const enableRetryLaneExpiration = false;
@@ -83,5 +82,7 @@ export const useMicrotasksForSchedulingInFabric = false;
export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = __EXPERIMENTAL__;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index e5c1d418c80a1..e97d010255afa 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -55,7 +55,6 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
-export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
export const disableSchedulerTimeoutInWorkLoop = false;
@@ -86,5 +85,7 @@ export const useMicrotasksForSchedulingInFabric = false;
export const passChildrenWhenCloningPersistedNodes = false;
export const enableUseDeferredValueInitialArg = true;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 8d5c0ed009fbb..f573a75b5c588 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -49,7 +49,6 @@ export const enableProfilerNestedUpdateScheduledHook: boolean =
__PROFILE__ && dynamicFeatureFlags.enableProfilerNestedUpdateScheduledHook;
export const enableUpdaterTracking = __PROFILE__;
-export const createRootStrictEffectsByDefault = false;
export const enableSuspenseAvoidThisFallback = true;
export const enableSuspenseAvoidThisFallbackFizz = false;
@@ -113,5 +112,7 @@ export const passChildrenWhenCloningPersistedNodes = false;
export const enableAsyncDebugInfo = false;
+export const enableServerComponentKeys = true;
+
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js
index 78908d91eb169..80b9efe4faac7 100644
--- a/scripts/jest/TestFlags.js
+++ b/scripts/jest/TestFlags.js
@@ -50,9 +50,6 @@ const environmentFlags = {
FIXME: false,
TODO: false,
- // Turn these flags back on (or delete) once the effect list is removed in
- // favor of a depth-first traversal using `subtreeTags`.
- dfsEffectsRefactor: true,
enableUseJSStackToTrackPassiveDurations: false,
};
diff --git a/scripts/jest/devtools/setupTests.build-devtools-regression.js b/scripts/jest/devtools/setupTests.build-devtools-regression.js
index d0126731512bd..a1221d53d2fa6 100644
--- a/scripts/jest/devtools/setupTests.build-devtools-regression.js
+++ b/scripts/jest/devtools/setupTests.build-devtools-regression.js
@@ -5,26 +5,3 @@
jest.mock('scheduler/tracing', () => {
return jest.requireActual('scheduler/tracing-profiling');
});
-
-// act doesn't exist in older versions of React, but
-// DevTools tests sometimes import and depend on act to run.
-// If act doesn't exist for a particular version of React, we will
-// mock it with a function. This should work in most tests
-// that we want to call with older versions of React.
-// TODO (luna) Refactor act in DevTools test utils to not depend
-// on act in react-dom or react-test-renderer so we don't need to do this
-jest.mock('react-test-renderer', () => {
- const reactTestRenderer = jest.requireActual('react-test-renderer');
- if (!reactTestRenderer.act) {
- reactTestRenderer.act = fn => fn();
- }
- return reactTestRenderer;
-});
-
-jest.mock('react-dom/test-utils', () => {
- const testUtils = jest.requireActual('react-dom/test-utils');
- if (!testUtils.act) {
- testUtils.act = fn => fn();
- }
- return testUtils;
-});
diff --git a/scripts/jest/shouldIgnoreConsoleError.js b/scripts/jest/shouldIgnoreConsoleError.js
index 42aae220debed..79ec7fc2ad7d6 100644
--- a/scripts/jest/shouldIgnoreConsoleError.js
+++ b/scripts/jest/shouldIgnoreConsoleError.js
@@ -1,6 +1,10 @@
'use strict';
-module.exports = function shouldIgnoreConsoleError(format, args) {
+module.exports = function shouldIgnoreConsoleError(
+ format,
+ args,
+ {TODO_ignoreHydrationErrors} = {TODO_ignoreHydrationErrors: false}
+) {
if (__DEV__) {
if (typeof format === 'string') {
if (format.indexOf('Error: Uncaught [') === 0) {
@@ -23,6 +27,15 @@ module.exports = function shouldIgnoreConsoleError(format, args) {
// We haven't finished migrating our tests to use createRoot.
return true;
}
+ if (
+ TODO_ignoreHydrationErrors &&
+ format.indexOf(
+ 'An error occurred during hydration. The server HTML was replaced with client content in'
+ ) !== -1
+ ) {
+ // This also gets logged by onRecoverableError, so we can ignore it.
+ return true;
+ }
} else if (
format != null &&
typeof format.message === 'string' &&
diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js
index a4f3f08bbd522..683208d1fd584 100644
--- a/scripts/rollup/bundles.js
+++ b/scripts/rollup/bundles.js
@@ -134,6 +134,18 @@ const bundles = [
externals: ['react', 'ReactNativeInternalFeatureFlags'],
},
+ /******* React JSX Runtime React Server *******/
+ {
+ bundleTypes: [NODE_DEV, NODE_PROD],
+ moduleType: ISOMORPHIC,
+ entry: 'react/src/jsx/ReactJSXServer.js',
+ name: 'react-jsx-runtime.react-server',
+ global: 'JSXRuntime',
+ minifyWithProdErrorCodes: false,
+ wrapWithModuleBoundaries: false,
+ externals: ['react', 'ReactNativeInternalFeatureFlags'],
+ },
+
/******* React JSX DEV Runtime *******/
{
bundleTypes: [
@@ -176,7 +188,7 @@ const bundles = [
externals: ['react'],
},
- /******* React DOM Shared Subset *******/
+ /******* React DOM React Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,