in
.',
+ ],
+ {withoutStack: 1},
);
+
+ assertLog([
+ 'Log recoverable error: Hydration failed because the initial UI does not match what was rendered on the server.',
+ 'Log recoverable error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
+ ]);
expect(container.textContent).toBe('parsnip');
});
- it('should give helpful errors on state desync', () => {
+ it('should give helpful errors on state desync', async () => {
class Component extends React.Component {
render() {
return (
@@ -203,13 +263,45 @@ describe('rendering React components at document', () => {
);
const testDocument = getTestDocument(markup);
- expect(() =>
- ReactDOM.hydrate(, testDocument),
- ).toErrorDev('Warning: Text content did not match.');
+ const enableClientRenderFallbackOnTextMismatch = gate(
+ flags => flags.enableClientRenderFallbackOnTextMismatch,
+ );
+ expect(() => {
+ ReactDOM.flushSync(() => {
+ ReactDOMClient.hydrateRoot(
+ testDocument,
+ ,
+ {
+ onRecoverableError: error => {
+ Scheduler.log('Log recoverable error: ' + error.message);
+ },
+ },
+ );
+ });
+ }).toErrorDev(
+ enableClientRenderFallbackOnTextMismatch
+ ? [
+ 'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
+ 'Warning: Text content did not match.',
+ ]
+ : ['Warning: Text content did not match.'],
+ {
+ withoutStack: enableClientRenderFallbackOnTextMismatch ? 1 : 0,
+ },
+ );
+
+ assertLog(
+ enableClientRenderFallbackOnTextMismatch
+ ? [
+ 'Log recoverable error: Text content does not match server-rendered HTML.',
+ 'Log recoverable error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
+ ]
+ : [],
+ );
expect(testDocument.body.innerHTML).toBe('Hello world');
});
- it('should render w/ no markup to full document', () => {
+ it('should render w/ no markup to full document', async () => {
const testDocument = getTestDocument();
class Component extends React.Component {
@@ -227,23 +319,59 @@ describe('rendering React components at document', () => {
if (gate(flags => flags.enableFloat)) {
// with float the title no longer is a hydration mismatch so we get an error on the body mismatch
- expect(() =>
- ReactDOM.hydrate(, testDocument),
- ).toErrorDev(
- 'Expected server HTML to contain a matching text node for "Hello world" in ',
+ expect(() => {
+ ReactDOM.flushSync(() => {
+ ReactDOMClient.hydrateRoot(
+ testDocument,
+ ,
+ {
+ onRecoverableError: error => {
+ Scheduler.log('Log recoverable error: ' + error.message);
+ },
+ },
+ );
+ });
+ }).toErrorDev(
+ [
+ 'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
+ 'Expected server HTML to contain a matching text node for "Hello world" in ',
+ ],
+ {withoutStack: 1},
);
+ assertLog([
+ 'Log recoverable error: Hydration failed because the initial UI does not match what was rendered on the server.',
+ 'Log recoverable error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
+ ]);
} else {
// getTestDocument() has an extra that we didn't render.
- expect(() =>
- ReactDOM.hydrate(, testDocument),
- ).toErrorDev(
- 'Did not expect server HTML to contain a in .',
+ expect(() => {
+ ReactDOM.flushSync(() => {
+ ReactDOMClient.hydrateRoot(
+ testDocument,
+ ,
+ {
+ onRecoverableError: error => {
+ Scheduler.log('Log recoverable error: ' + error.message);
+ },
+ },
+ );
+ });
+ }).toErrorDev(
+ [
+ 'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
+ 'Warning: Text content did not match. Server: "test doc" Client: "Hello World"',
+ ],
+ {withoutStack: 1},
);
+ assertLog([
+ 'Log recoverable error: Text content does not match server-rendered HTML.',
+ 'Log recoverable error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
+ ]);
}
expect(testDocument.body.innerHTML).toBe('Hello world');
});
- it('supports findDOMNode on full-page components', () => {
+ it('supports findDOMNode on full-page components in legacy mode', () => {
const tree = (