From 28cbe0570a33719727219032a3a5ea443f3f878f Mon Sep 17 00:00:00 2001 From: Ivan Babak Date: Sat, 17 Feb 2018 23:01:44 -0800 Subject: [PATCH] Add all warning cases to fixtures/ssr (#10085) Next, will add a test per every case. --- .../ssr/src/components/SSRMismatchTest.js | 307 +++++++++++++++--- fixtures/ssr/src/index.js | 4 +- 2 files changed, 266 insertions(+), 45 deletions(-) diff --git a/fixtures/ssr/src/components/SSRMismatchTest.js b/fixtures/ssr/src/components/SSRMismatchTest.js index f1fdab1d36154..22113477989d8 100644 --- a/fixtures/ssr/src/components/SSRMismatchTest.js +++ b/fixtures/ssr/src/components/SSRMismatchTest.js @@ -1,56 +1,277 @@ import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; + +// Helps to test hydration edge cases with the root node. +// Sets the passed-in `serverHTML` as `innerHTML` and hydrates the passed-in `browserReact` over it. +class SSRMismatchTestRootHydrate extends Component { + componentDidMount() { + if (this._el) { + this._el.innerHTML = this.props.serverHTML; + ReactDOM.hydrate(this.props.browserReact, this._el); + } + } -// Triggers the DOM mismatch warnings if requested via query string. -export default class SSRMismatchTest extends Component { render() { - // Default content rendered at the server. - let content = ( -
+ return ( +
{ + this._el = el; + }} + /> + ); + } +} + +const testCases = [ + { + key: 'ssr-warnForTextDifference', + renderServer: () => ( +
+ SSRMismatchTest server text +
+ ), + renderBrowser: () => ( + // The inner element type is the same as the server render, but the inner text differs. +
+ SSRMismatchTest client text +
+ ), + }, + { + key: + 'ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedContainerTextInstance', + render: () => ( + + ), + }, + { + key: + 'ssr-warnForTextDifference-warnForUnmatchedText-didNotMatchHydratedTextInstance', + renderServer: () => ( +
+ + {'SSRMismatchTest static text and '} + {'server random text ' + Math.random()} + +
+ ), + renderBrowser: () => ( +
+ + {'SSRMismatchTest static text and '} + {'client random text ' + Math.random()} + +
+ ), + }, + { + key: 'ssr-warnForPropDifference', + renderServer: () => ( +
+ SSRMismatchTest default text +
+ ), + renderBrowser: () => ( + // The inner element type is the same as the server render. + // The browser root element has an extra prop with a non-`null` value. +
SSRMismatchTest default text
+ ), + }, + { + key: 'ssr-warnForPropDifference-null-no-warning', + renderServer: () => ( +
+ SSRMismatchTest default text +
+ ), + renderBrowser: () => ( + // The inner element type is the same as the server render. + // The browser root element has an extra prop explicitly set to `null`. +
+ SSRMismatchTest default text +
+ ), + }, + { + key: 'ssr-warnForExtraAttributes', + renderServer: () => ( +
+ SSRMismatchTest default text +
+ ), + renderBrowser: () => ( + // The inner element type is the same as the server render. + // The root element is missing a server-rendered prop. +
+ SSRMismatchTest default text +
+ ), + }, + { + key: 'ssr-warnForInvalidEventListener-false', + renderServer: () =>
{}} />, + renderBrowser: () =>
, + }, + { + key: 'ssr-warnForInvalidEventListener-typeof', + renderServer: () =>
{}} />, + renderBrowser: () =>
, + }, + { + key: 'ssr-warnForDeletedHydratableElement-didNotHydrateContainerInstance', + render: () => ( + ' + + '
' + + 'SSRMismatchTest second text' + } + browserReact={[ + 'SSRMismatchTest first text', +
, + 'SSRMismatchTest second text', + ]} + /> + ), + }, + { + key: 'ssr-warnForDeletedHydratableElement-didNotHydrateInstance', + renderServer: () => ( +
+
+ +
+ ), + renderBrowser: () => ( +
+ +
+ ), + }, + { + key: 'ssr-warnForDeletedHydratableText-didNotHydrateContainerInstance', + render: () => ( + ' + + 'SSRMismatchTest default text' + } + browserReact={[
, 'SSRMismatchTest default text']} + /> + ), + }, + { + key: 'ssr-warnForDeletedHydratableText-didNotHydrateInstance', + renderServer: () => ( +
+ SSRMismatchTest server text + +
+ ), + renderBrowser: () => ( +
+ +
+ ), + }, + { + key: + 'ssr-warnForInsertedHydratedElement-didNotFindHydratableContainerInstance', + render: () => ( + // The root element type is different (text on server, span on client), the inner text is the same. + SSRMismatchTest default text} + /> + ), + }, + { + key: 'ssr-warnForInsertedHydratedElement-didNotFindHydratableInstance', + renderServer: () => ( +
+ SSRMismatchTest default text +
+ ), + renderBrowser: () => ( + // The inner element type is different from the server render, but the inner text is the same. +
+

SSRMismatchTest default text

+
+ ), + }, + { + key: + 'ssr-warnForInsertedHydratedText-didNotFindHydratableContainerTextInstance', + render: () => ( + // The root element type is different (span on server, text on client), the inner text is the same. + SSRMismatchTest default text'} + browserReact={'SSRMismatchTest default text'} + /> + ), + }, + { + key: 'ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance', + renderServer: () => ( +
+ + +
+ ), + renderBrowser: () => ( +
+ + SSRMismatchTest client text + +
+ ), + }, +]; + +// Triggers the DOM mismatch warnings if requested via query string. +export default class SSRMismatchTest extends Component { + render() { + let content = null; + const queryParams = this.props.url.replace(/^[^?]+[?]?/, '').split('&'); + const testCaseFound = testCases.find( + testCase => queryParams.indexOf(testCase.key) >= 0 ); - // In the browser where `window` is available, triggering a DOM mismatch if it's requested. - if (typeof window !== 'undefined') { - const queryString = this.props.url.replace(/^[^?]+[?]?/, ''); - if (queryString.indexOf('ssr-prop-mismatch') >= 0) { - // The inner structure is the same as the server render, but the root element has an extra prop. - content = ( -
- SSRMismatchTest default text -
- ); - } else if (queryString.indexOf('ssr-prop-extra') >= 0) { - // The inner structure is the same as the server render, but the root element is missing a server-rendered prop. - content = ( -
- SSRMismatchTest default text -
- ); - } else if (queryString.indexOf('ssr-text-mismatch') >= 0) { - // The inner structure is the same as the server render, but the inner text node content differs. - content = ( -
- SSRMismatchTest ssr-text-mismatch -
- ); - } else if (queryString.indexOf('ssr-children-mismatch') >= 0) { - // The inner structure is different from the server render. - content = ( -
-

SSRMismatchTest ssr-children-mismatch

-
- ); + if (testCaseFound) { + // In the browser where `window` is available, triggering a DOM mismatch if it's requested. + let render; + if (typeof window !== 'undefined') { + render = testCaseFound.renderBrowser || testCaseFound.render; + } else { + render = testCaseFound.renderServer || testCaseFound.render; } + content = render(); } + return (
-
- SSRMismatchTest. Open the console, select a test case:{' '} - none{' '} - ssr-prop-mismatch{' '} - ssr-prop-extra{' '} - ssr-text-mismatch{' '} - ssr-children-mismatch +
+
SSRMismatchTest. Open the console, select a test case:
+
    +
  1. + none +
  2. + {testCases.map(testCase => ( +
  3. + {testCase.key} + {testCaseFound && testCaseFound.key === testCase.key + ? ' 👈' + : ' '} +
  4. + ))} +
{content}
diff --git a/fixtures/ssr/src/index.js b/fixtures/ssr/src/index.js index 1d446ac0a6e50..71f7ea2afc588 100644 --- a/fixtures/ssr/src/index.js +++ b/fixtures/ssr/src/index.js @@ -3,6 +3,6 @@ import {hydrate} from 'react-dom'; import App from './components/App'; -// Remove the #hash (fragment identifier) component from the `url` to match the server-side value. -const url = window.location.href.replace(/[#].*/, ''); +// No host:port and #hash (fragment identifier) components in the `url` to match the server-side value. +const url = (window.location.pathname + window.location.search); hydrate(, document);