.',
+ 'Text content did not match. Server: "x" Client: "y"',
+ ]
+ : ['Text content did not match. Server: "x" Client: "y"'],
+ {withoutStack: enableClientRenderFallbackOnTextMismatch ? 1 : 0},
+ );
expect(mountCount).toEqual(4);
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
@@ -164,25 +208,46 @@ describe('ReactDOMServerHydration', () => {
});
// Regression test for /~https://github.com/facebook/react/issues/11726
- it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', () => {
+ it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', async () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
,
);
expect(element.firstChild.autofocus).toBe(false);
+ const onFocusBeforeHydration = jest.fn();
+ const onFocusAfterHydration = jest.fn();
+ element.firstChild.focus = onFocusBeforeHydration;
- element.firstChild.focus = jest.fn();
-
- expect(() =>
- ReactDOM.hydrate(
, element),
- ).toErrorDev(
- 'Warning: Text content did not match. Server: "server" Client: "client"',
+ const enableClientRenderFallbackOnTextMismatch = gate(
+ flags => flags.enableClientRenderFallbackOnTextMismatch,
+ );
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ element,
+
,
+ {onRecoverableError: error => {}},
+ );
+ });
+ }).toErrorDev(
+ enableClientRenderFallbackOnTextMismatch
+ ? [
+ 'An error occurred during hydration. The server HTML was replaced with client content in
.',
+ 'Warning: Text content did not match. Server: "server" Client: "client"',
+ ]
+ : [
+ 'Warning: Text content did not match. Server: "server" Client: "client"',
+ ],
+ {withoutStack: enableClientRenderFallbackOnTextMismatch ? 1 : 0},
);
- expect(element.firstChild.focus).not.toHaveBeenCalled();
+ expect(onFocusBeforeHydration).not.toHaveBeenCalled();
+ expect(onFocusAfterHydration).not.toHaveBeenCalled();
});
- it('should warn when the style property differs', () => {
+ it('should warn when the style property differs', async () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
,
@@ -190,26 +255,27 @@ describe('ReactDOMServerHydration', () => {
expect(element.firstChild.style.textDecoration).toBe('none');
expect(element.firstChild.style.color).toBe('black');
- expect(() =>
- ReactDOM.hydrate(
-
,
- element,
- ),
- ).toErrorDev(
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ element,
+
,
+ );
+ });
+ }).toErrorDev(
'Warning: Prop `style` did not match. Server: ' +
'"text-decoration:none;color:black;height:10px" Client: ' +
'"text-decoration:none;color:white;height:10px"',
);
});
- // @gate !disableIEWorkarounds || !__DEV__
- it('should not warn when the style property differs on whitespace or order in IE', () => {
+ it('should not warn when the style property differs on whitespace or order in IE', async () => {
document.documentMode = 11;
jest.resetModules();
React = require('react');
- ReactDOM = require('react-dom');
+ ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
try {
const element = document.createElement('div');
@@ -219,33 +285,35 @@ describe('ReactDOMServerHydration', () => {
element.innerHTML =
'
';
- // We don't expect to see false positive warnings.
- // /~https://github.com/facebook/react/issues/11807
- ReactDOM.hydrate(
-
,
- element,
- );
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ element,
+
,
+ );
+ });
} finally {
delete document.documentMode;
}
});
- it('should warn when the style property differs on whitespace in non-IE browsers', () => {
+ it('should warn when the style property differs on whitespace in non-IE browsers', async () => {
const element = document.createElement('div');
element.innerHTML =
'
';
- expect(() =>
- ReactDOM.hydrate(
-
,
- element,
- ),
- ).toErrorDev(
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ element,
+
,
+ );
+ });
+ }).toErrorDev(
'Warning: Prop `style` did not match. Server: ' +
'"text-decoration: none; color: black; height: 10px;" Client: ' +
'"text-decoration:none;color:black;height:10px"',
@@ -264,7 +332,7 @@ describe('ReactDOMServerHydration', () => {
);
});
- it('should be able to render and hydrate Mode components', () => {
+ it('should be able to render and hydrate Mode components', async () => {
class ComponentWithWarning extends React.Component {
componentWillMount() {
// Expected warning
@@ -286,15 +354,17 @@ describe('ReactDOMServerHydration', () => {
}).toWarnDev('componentWillMount has been renamed');
expect(element.textContent).toBe('Hi');
- expect(() => {
- ReactDOM.hydrate(markup, element);
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, markup);
+ });
}).toWarnDev('componentWillMount has been renamed', {
withoutStack: true,
});
expect(element.textContent).toBe('Hi');
});
- it('should be able to render and hydrate forwardRef components', () => {
+ it('should be able to render and hydrate forwardRef components', async () => {
const FunctionComponent = ({label, forwardedRef}) => (
{label}
);
@@ -310,12 +380,14 @@ describe('ReactDOMServerHydration', () => {
expect(element.textContent).toBe('Hi');
expect(ref.current).toBe(null);
- ReactDOM.hydrate(markup, element);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, markup);
+ });
expect(element.textContent).toBe('Hi');
expect(ref.current.tagName).toBe('DIV');
});
- it('should be able to render and hydrate Profiler components', () => {
+ it('should be able to render and hydrate Profiler components', async () => {
const callback = jest.fn();
const markup = (
@@ -328,7 +400,9 @@ describe('ReactDOMServerHydration', () => {
expect(element.textContent).toBe('Hi');
expect(callback).not.toHaveBeenCalled();
- ReactDOM.hydrate(markup, element);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, markup);
+ });
expect(element.textContent).toBe('Hi');
if (__DEV__) {
expect(callback).toHaveBeenCalledTimes(1);
@@ -341,7 +415,7 @@ describe('ReactDOMServerHydration', () => {
});
// Regression test for /~https://github.com/facebook/react/issues/11423
- it('should ignore noscript content on the client and not warn about mismatches', () => {
+ it('should ignore noscript content on the client and not warn about mismatches', async () => {
const callback = jest.fn();
const TestComponent = ({onRender}) => {
onRender();
@@ -360,10 +434,9 @@ describe('ReactDOMServerHydration', () => {
'Enable JavaScript to run this app.
',
);
- // On the client we want to keep the existing markup, but not render the
- // actual elements for performance reasons and to avoid for example
- // downloading images. This should also not warn for hydration mismatches.
- ReactDOM.hydrate(markup, element);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, markup);
+ });
expect(callback).toHaveBeenCalledTimes(1);
expect(element.textContent).toBe(
'Enable JavaScript to run this app.
',
@@ -371,18 +444,17 @@ describe('ReactDOMServerHydration', () => {
});
it('should be able to use lazy components after hydrating', async () => {
+ let resolveLazy;
const Lazy = React.lazy(
() =>
new Promise(resolve => {
- setTimeout(
- () =>
- resolve({
- default: function World() {
- return 'world';
- },
- }),
- 1000,
- );
+ resolveLazy = () => {
+ resolve({
+ default: function World() {
+ return 'world';
+ },
+ });
+ };
}),
);
class HelloWorld extends React.Component {
@@ -410,11 +482,13 @@ describe('ReactDOMServerHydration', () => {
element.innerHTML = ReactDOMServer.renderToString();
expect(element.textContent).toBe('Hello ');
- ReactDOM.hydrate(, element);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, );
+ });
expect(element.textContent).toBe('Hello loading');
// Resolve Lazy component
- await act(() => jest.runAllTimers());
+ await act(() => resolveLazy());
expect(element.textContent).toBe('Hello world');
});
@@ -497,7 +571,7 @@ describe('ReactDOMServerHydration', () => {
});
// regression test for /~https://github.com/facebook/react/issues/17170
- it('should not warn if dangerouslySetInnerHtml=undefined', () => {
+ it('should not warn if dangerouslySetInnerHtml=undefined', async () => {
const domElement = document.createElement('div');
const reactElement = (
@@ -507,49 +581,75 @@ describe('ReactDOMServerHydration', () => {
const markup = ReactDOMServer.renderToStaticMarkup(reactElement);
domElement.innerHTML = markup;
- ReactDOM.hydrate(reactElement, domElement);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(domElement, reactElement);
+ });
expect(domElement.innerHTML).toEqual(markup);
});
- it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined and children on the client', () => {
+ it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined and children on the client', async () => {
const domElement = document.createElement('div');
const markup = ReactDOMServer.renderToStaticMarkup(
server'}} />,
);
domElement.innerHTML = markup;
- expect(() => {
- ReactDOM.hydrate(
-
,
- domElement,
- );
+ const enableClientRenderFallbackOnTextMismatch = gate(
+ flags => flags.enableClientRenderFallbackOnTextMismatch,
+ );
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ domElement,
+
,
+ {onRecoverableError: error => {}},
+ );
+ });
expect(domElement.innerHTML).not.toEqual(markup);
}).toErrorDev(
- 'Warning: Text content did not match. Server: "server" Client: "client"',
+ enableClientRenderFallbackOnTextMismatch
+ ? [
+ 'An error occurred during hydration. The server HTML was replaced with client content in
.',
+ 'Warning: Text content did not match. Server: "server" Client: "client"',
+ ]
+ : [
+ 'Warning: Text content did not match. Server: "server" Client: "client"',
+ ],
+ {withoutStack: enableClientRenderFallbackOnTextMismatch ? 1 : 0},
);
});
- it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined on the client', () => {
+ it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined on the client', async () => {
const domElement = document.createElement('div');
const markup = ReactDOMServer.renderToStaticMarkup(
server'}} />,
);
domElement.innerHTML = markup;
- expect(() => {
- ReactDOM.hydrate(
, domElement);
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(
+ domElement,
+
,
+ {onRecoverableError: error => {}},
+ );
+ });
expect(domElement.innerHTML).not.toEqual(markup);
}).toErrorDev(
- 'Warning: Did not expect server HTML to contain a
in
',
+ [
+ 'An error occurred during hydration. The server HTML was replaced with client content in
.',
+ 'Warning: Did not expect server HTML to contain a
in
.',
+ ],
+ {withoutStack: 1},
);
});
- it('should warn when hydrating read-only properties', () => {
+ it('should warn when hydrating read-only properties', async () => {
const readOnlyProperties = [
'offsetParent',
'offsetTop',
@@ -560,24 +660,31 @@ describe('ReactDOMServerHydration', () => {
'outerText',
'outerHTML',
];
- readOnlyProperties.forEach(readOnlyProperty => {
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const readOnlyProperty of readOnlyProperties) {
const props = {};
props[readOnlyProperty] = 'hello';
const jsx = React.createElement('my-custom-element', props);
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(jsx);
if (gate(flags => flags.enableCustomElementPropertySupport)) {
- expect(() => ReactDOM.hydrate(jsx, element)).toErrorDev(
+ await expect(async () => {
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, jsx);
+ });
+ }).toErrorDev(
`Warning: Assignment to read-only property will result in a no-op: \`${readOnlyProperty}\``,
);
} else {
- ReactDOM.hydrate(jsx, element);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(element, jsx);
+ });
}
- });
+ }
});
// @gate enableCustomElementPropertySupport
- it('should not re-assign properties on hydration', () => {
+ it('should not re-assign properties on hydration', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
@@ -607,7 +714,9 @@ describe('ReactDOMServerHydration', () => {
},
});
- ReactDOM.hydrate(jsx, container);
+ await act(() => {
+ ReactDOMClient.hydrateRoot(container, jsx);
+ });
expect(customElement.getAttribute('str')).toBe('string');
expect(customElement.getAttribute('obj')).toBe(null);