From 0c5cc69ffd40b2fdd61c682d174a987e965dd002 Mon Sep 17 00:00:00 2001 From: Ivan Babak Date: Sun, 18 Feb 2018 00:27:59 -0800 Subject: [PATCH] Add tests for all hydrate warning cases like in fixtures/ssr (#10085) --- .../src/__tests__/ReactMount-test.js | 316 +++++++++++++++++- 1 file changed, 306 insertions(+), 10 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactMount-test.js b/packages/react-dom/src/__tests__/ReactMount-test.js index ee8cab50d4fe5..0734185914235 100644 --- a/packages/react-dom/src/__tests__/ReactMount-test.js +++ b/packages/react-dom/src/__tests__/ReactMount-test.js @@ -157,6 +157,8 @@ describe('ReactMount', () => { }); it('should warn when a hydrated element has inner text mismatch', () => { + // See fixtures/ssr: ssr-warnForTextDifference + class Component extends React.Component { render() { return this.props.children; @@ -187,7 +189,69 @@ describe('ReactMount', () => { ); }); + it('should warn when hydrating a text node over a mismatching text node', () => { + // See fixtures/ssr: ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedContainerTextInstance + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString('server text'); + div.innerHTML = markup; + + expect(() => ReactDOM.hydrate('client text', div)).toWarnDev( + 'Text content did not match. ' + + 'Server: "server text" ' + + 'Client: "client text"', + ); + }); + + it('should warn when a hydrated element has first text match but second text mismatch', () => { + // See fixtures/ssr: ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedTextInstance + + class Component extends React.Component { + render() { + return this.props.children; + } + } + + const serverRandom = Math.random(); + const clientRandom = Math.random(); + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( + + + {'SSRMismatchTest static text and '} + {'server random text ' + serverRandom} + + , + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( + + + {'SSRMismatchTest static text and '} + {'client random text ' + clientRandom} + + , + div, + ), + ).toWarnDev( + 'Text content did not match. ' + + 'Server: "server random text ' + + serverRandom + + '" ' + + 'Client: "client random text ' + + clientRandom + + '"\n' + + ' in em (at **)\n' + + ' in Component (at **)', + ); + }); + it('should warn when a hydrated element has children mismatch', () => { + // See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance + class Component extends React.Component { render() { return this.props.children; @@ -242,43 +306,275 @@ describe('ReactMount', () => { ); }); - it('should warn when a hydrated element has extra props', () => { + it('should warn when a hydrated element has extra props with non-null values', () => { + // See fixtures/ssr: ssr-warnForPropDifference + const div = document.createElement('div'); - const markup = ReactDOMServer.renderToString(
); + const markup = ReactDOMServer.renderToString( +
+ SSRMismatchTest default text +
, + ); div.innerHTML = markup; expect(() => ReactDOM.hydrate( -
, +
+ SSRMismatchTest default text +
, div, ), ).toWarnDev( - 'Prop `data-ssr-prop-mismatch` did not match. ' + + 'Prop `data-ssr-extra-prop` did not match. ' + 'Server: "null" ' + 'Client: "true"\n' + ' in div (at **)', ); }); - it('should not warn when a hydrated element has an extra prop explicitly set to null', () => { + it('should not warn when a hydrated element has extra props explicitly set to null', () => { + // See fixtures/ssr: ssr-warnForPropDifference-null-no-warning + const div = document.createElement('div'); - const markup = ReactDOMServer.renderToString(
); + const markup = ReactDOMServer.renderToString( +
+ SSRMismatchTest default text +
, + ); div.innerHTML = markup; expect(() => - ReactDOM.hydrate(
, div), + ReactDOM.hydrate( +
+ SSRMismatchTest default text +
, + div, + ), ).toWarnDev([]); }); it('should warn when a server element has extra props', () => { + // See fixtures/ssr: ssr-warnForExtraAttributes + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( +
+ SSRMismatchTest default text +
, + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( +
+ SSRMismatchTest default text +
, + div, + ), + ).toWarnDev( + 'Extra attributes from the server: data-ssr-extra-prop,data-ssr-extra-prop-2\n' + + ' in div (at **)', + ); + }); + + it('should warn when a browser element has an event handler which is set to false', () => { + // See fixtures/ssr: ssr-warnForInvalidEventListener-false + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString(
{}} />); + div.innerHTML = markup; + + expect(() => ReactDOM.hydrate(
, div)).toWarnDev( + 'Expected `onClick` listener to be a function, instead got `false`.\n\n' + + 'If you used to conditionally omit it with onClick={condition && value}, ' + + 'pass onClick={condition ? value : undefined} instead.\n' + + ' in div (at **)', + ); + }); + + it('should warn when a browser element has an event handler which is set to a non-function, non-false value', () => { + // See fixtures/ssr: ssr-warnForInvalidEventListener-typeof + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString(
{}} />); + div.innerHTML = markup; + + expect(() => ReactDOM.hydrate(
, div)).toWarnDev( + 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' + + ' in div (at **)', + ); + }); + + it('should warn when hydrate removes an element from a server-rendered sequence in the root container', () => { + // See fixtures/ssr: ssr-warnForDeletedHydratableElement-didNotHydrateContainerInstance + + const div = document.createElement('div'); + const markup = + 'SSRMismatchTest first text' + + '
' + + '
' + + 'SSRMismatchTest second text'; + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( + [ + 'SSRMismatchTest first text', +
, + 'SSRMismatchTest second text', + ], + div, + ), + ).toWarnDev('Did not expect server HTML to contain a
in
.'); + }); + + it('should warn when hydrate removes an element from a server-rendered sequence', () => { + // See fixtures/ssr: ssr-warnForDeletedHydratableElement-didNotHydrateInstance + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( +
+
+ +
, + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( +
+ +
, + div, + ), + ).toWarnDev( + 'Did not expect server HTML to contain a
in
.\n' + + ' in span (at **)\n' + + ' in div (at **)', + ); + }); + + it('should warn when hydrate removes a text node from a server-rendered sequence in the root container', () => { + // See fixtures/ssr: ssr-warnForDeletedHydratableText-didNotHydrateContainerInstance + + const div = document.createElement('div'); + const markup = + 'SSRMismatchTest server text' + '
' + 'SSRMismatchTest default text'; + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate([
, 'SSRMismatchTest default text'], div), + ).toWarnDev( + 'Did not expect server HTML to contain the text node "SSRMismatchTest server text" in
.\n' + + ' in br (at **)', + ); + }); + + it('should warn when hydrate removes a text node from a server-rendered sequence', () => { + // See fixtures/ssr: ssr-warnForDeletedHydratableText-didNotHydrateInstance + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( +
+ SSRMismatchTest server text + +
, + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( +
+ +
, + div, + ), + ).toWarnDev( + 'Did not expect server HTML to contain the text node "SSRMismatchTest server text" in
.\n' + + ' in span (at **)\n' + + ' in div (at **)', + ); + }); + + it('should warn when hydrate inserts an element to replace a text node in the root container', () => { + // See fixtures/ssr: ssr-warnForInsertedHydratedElement-didNotFindHydratableContainerInstance + + const div = document.createElement('div'); + const markup = 'SSRMismatchTest default text'; + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate(SSRMismatchTest default text, div), + ).toWarnDev( + 'Expected server HTML to contain a matching in
.\n' + + ' in span (at **)', + ); + }); + + it('should warn when hydrate inserts an element to replace a different element', () => { + // See fixtures/ssr: ssr-warnForInsertedHydratedElement-didNotFindHydratableInstance + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( +
+ SSRMismatchTest default text +
, + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( +
+

SSRMismatchTest default text

+
, + div, + ), + ).toWarnDev( + 'Expected server HTML to contain a matching

in

.\n' + + ' in p (at **)\n' + + ' in div (at **)', + ); + }); + + it('should warn when hydrate inserts a text node to replace an element in the root container', () => { + // See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableContainerTextInstance + const div = document.createElement('div'); const markup = ReactDOMServer.renderToString( -
, + SSRMismatchTest default text, ); div.innerHTML = markup; - expect(() => ReactDOM.hydrate(
, div)).toWarnDev( - 'Extra attributes from the server: data-ssr-prop-extra,data-ssr-prop-extra-2\n' + + expect(() => + ReactDOM.hydrate('SSRMismatchTest default text', div), + ).toWarnDev( + 'Expected server HTML to contain a matching text node for "SSRMismatchTest default text" in
.', + ); + }); + + it('should warn when hydrate inserts a text node between matching elements', () => { + // See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance + + const div = document.createElement('div'); + const markup = ReactDOMServer.renderToString( +
+ + +
, + ); + div.innerHTML = markup; + + expect(() => + ReactDOM.hydrate( +
+ + SSRMismatchTest client text + +
, + div, + ), + ).toWarnDev( + 'Expected server HTML to contain a matching text node for "SSRMismatchTest client text" in
.\n' + ' in div (at **)', ); });