From c8897f8382f10ac390f8f7f4f15c8ffaeccb6a8c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 23 Mar 2017 08:16:52 -0700 Subject: [PATCH 001/153] Updated ReactNative findNodeHandle() to handle number case (#9238) --- src/renderers/native/ReactNativeFiber.js | 5 ++++- src/renderers/native/ReactNativeStack.js | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js index 783ae91397dfa..014b4dfa85de8 100644 --- a/src/renderers/native/ReactNativeFiber.js +++ b/src/renderers/native/ReactNativeFiber.js @@ -382,7 +382,10 @@ const ReactNative = { // See NativeMethodsMixin#setNativeProps for more info on why this is done. findNodeHandle(componentOrHandle: any): ?number { const instance: any = findNodeHandle(componentOrHandle); - return instance ? instance._nativeTag : null; + if (instance == null || typeof instance === 'number') { + return instance; + } + return instance._nativeTag; }, render(element: Element, containerTag: any, callback: ?Function) { diff --git a/src/renderers/native/ReactNativeStack.js b/src/renderers/native/ReactNativeStack.js index 37d8b89f561eb..2a5b8f509b707 100644 --- a/src/renderers/native/ReactNativeStack.js +++ b/src/renderers/native/ReactNativeStack.js @@ -37,7 +37,11 @@ var ReactNative = { // The injected findNodeHandle() strategy returns the instance wrapper though. // See NativeMethodsMixin#setNativeProps for more info on why this is done. findNodeHandle(componentOrHandle: any): ?number { - return findNodeHandle(componentOrHandle).getHostNode(); + const nodeHandle = findNodeHandle(componentOrHandle); + if (nodeHandle == null || typeof nodeHandle === 'number') { + return nodeHandle; + } + return nodeHandle.getHostNode(); }, render: render, From 1c9dcd53d5a5e194afc0d953a3341ad32fe311dc Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 23 Mar 2017 18:45:38 +0000 Subject: [PATCH 002/153] Add dynamic injection to ReactErrorUtils (#9246) --- src/renderers/shared/utils/ReactErrorUtils.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js index b3910a0dd8953..b57db95527abb 100644 --- a/src/renderers/shared/utils/ReactErrorUtils.js +++ b/src/renderers/shared/utils/ReactErrorUtils.js @@ -12,6 +12,8 @@ 'use strict'; +const invariant = require('invariant'); + let caughtError = null; /** @@ -24,6 +26,21 @@ let caughtError = null; * @param {...*} args Arguments for function */ const ReactErrorUtils = { + injection: { + injectErrorUtils(injectedErrorUtils: Object) { + invariant( + typeof injectedErrorUtils.invokeGuardedCallback === 'function', + 'Injected invokeGuardedCallback() must be a function.', + ); + invariant( + typeof injectedErrorUtils.rethrowCaughtError === 'function', + 'Injected rethrowCaughtError() must be a function.', + ); + ReactErrorUtils.invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; + ReactErrorUtils.rethrowCaughtError = injectedErrorUtils.rethrowCaughtError; + }, + }, + invokeGuardedCallback: function( name: string | null, func: (a: A, b: B, c: C, d: D, e: E, f: F) => void, From d6a2c669d22e0bf0bc839493b863673df09c99c9 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 23 Mar 2017 19:13:07 +0000 Subject: [PATCH 003/153] Fix ReactErrorUtils injection (#9247) --- src/renderers/shared/utils/ReactErrorUtils.js | 130 +++++++++--------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js index b57db95527abb..fe3017bbc9fcc 100644 --- a/src/renderers/shared/utils/ReactErrorUtils.js +++ b/src/renderers/shared/utils/ReactErrorUtils.js @@ -16,6 +16,66 @@ const invariant = require('invariant'); let caughtError = null; +let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) { + const funcArgs = Array.prototype.slice.call(arguments, 3); + try { + func.apply(context, funcArgs); + } catch (error) { + return error; + } + return null; +}; + +if (__DEV__) { + /** + * To help development we can get better devtools integration by simulating a + * real browser event. + */ + if ( + typeof window !== 'undefined' && + typeof window.dispatchEvent === 'function' && + typeof document !== 'undefined' && + typeof document.createEvent === 'function' + ) { + const fakeNode = document.createElement('react'); + let depth = 0; + + invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) { + depth++; + const thisDepth = depth; + const funcArgs = Array.prototype.slice.call(arguments, 3); + const boundFunc = function() { + func.apply(context, funcArgs); + }; + let fakeEventError = null; + const onFakeEventError = function(event) { + // Don't capture nested errors + if (depth === thisDepth) { + fakeEventError = event.error; + } + }; + const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`; + window.addEventListener('error', onFakeEventError); + fakeNode.addEventListener(evtType, boundFunc, false); + const evt = document.createEvent('Event'); + evt.initEvent(evtType, false, false); + fakeNode.dispatchEvent(evt); + fakeNode.removeEventListener(evtType, boundFunc, false); + window.removeEventListener('error', onFakeEventError); + depth--; + return fakeEventError; + }; + } +} + +let rethrowCaughtError = function() { + if (caughtError) { + const error = caughtError; + caughtError = null; + throw error; + } +}; + /** * Call a function while guarding against errors that happens within it. * Returns an error if it throws, otherwise null. @@ -36,8 +96,8 @@ const ReactErrorUtils = { typeof injectedErrorUtils.rethrowCaughtError === 'function', 'Injected rethrowCaughtError() must be a function.', ); - ReactErrorUtils.invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; - ReactErrorUtils.rethrowCaughtError = injectedErrorUtils.rethrowCaughtError; + invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; + rethrowCaughtError = injectedErrorUtils.rethrowCaughtError; }, }, @@ -52,13 +112,7 @@ const ReactErrorUtils = { e: E, f: F, ): Error | null { - const funcArgs = Array.prototype.slice.call(arguments, 3); - try { - func.apply(context, funcArgs); - } catch (error) { - return error; - } - return null; + return invokeGuardedCallback.apply(this, arguments); }, /** @@ -92,64 +146,8 @@ const ReactErrorUtils = { * we will rethrow to be handled by the top level error handler. */ rethrowCaughtError: function() { - if (caughtError) { - const error = caughtError; - caughtError = null; - throw error; - } + return rethrowCaughtError.apply(this, arguments); }, }; -if (__DEV__) { - /** - * To help development we can get better devtools integration by simulating a - * real browser event. - */ - if ( - typeof window !== 'undefined' && - typeof window.dispatchEvent === 'function' && - typeof document !== 'undefined' && - typeof document.createEvent === 'function' - ) { - const fakeNode = document.createElement('react'); - let depth = 0; - - ReactErrorUtils.invokeGuardedCallback = function( - name, - func, - context, - a, - b, - c, - d, - e, - f, - ) { - depth++; - const thisDepth = depth; - const funcArgs = Array.prototype.slice.call(arguments, 3); - const boundFunc = function() { - func.apply(context, funcArgs); - }; - let fakeEventError = null; - const onFakeEventError = function(event) { - // Don't capture nested errors - if (depth === thisDepth) { - fakeEventError = event.error; - } - }; - const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`; - window.addEventListener('error', onFakeEventError); - fakeNode.addEventListener(evtType, boundFunc, false); - const evt = document.createEvent('Event'); - evt.initEvent(evtType, false, false); - fakeNode.dispatchEvent(evt); - fakeNode.removeEventListener(evtType, boundFunc, false); - window.removeEventListener('error', onFakeEventError); - depth--; - return fakeEventError; - }; - } -} - module.exports = ReactErrorUtils; From f082e35b36ece6bf301051296380aa15897d7431 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 23 Mar 2017 19:45:10 +0000 Subject: [PATCH 004/153] Fix up invariant import to be from fbjs/lib/ --- src/renderers/shared/utils/ReactErrorUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js index fe3017bbc9fcc..71ed3063cdf97 100644 --- a/src/renderers/shared/utils/ReactErrorUtils.js +++ b/src/renderers/shared/utils/ReactErrorUtils.js @@ -12,7 +12,7 @@ 'use strict'; -const invariant = require('invariant'); +const invariant = require('fbjs/lib/invariant'); let caughtError = null; From 38d8f3ee6b4493fb1cce78e2a4e54fcc87724703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Tingl=C3=B6f?= Date: Fri, 24 Mar 2017 15:37:32 +0100 Subject: [PATCH 005/153] Updated Router import for example (#9233) Updated import in order to be a correct working example as in accordance with the docs --- docs/docs/context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/context.md b/docs/docs/context.md index c5703d3545254..cfe86556f599d 100644 --- a/docs/docs/context.md +++ b/docs/docs/context.md @@ -111,7 +111,7 @@ If `contextTypes` is not defined, then `context` will be an empty object. Context can also let you build an API where parents and children communicate. For example, one library that works this way is [React Router V4](https://reacttraining.com/react-router): ```javascript -import { Router, Route, Link } from 'react-router-dom'; +import { BrowserRouter as Router, Route, Link } from 'react-router-dom'; const BasicExample = () => ( From 0a41890eaa018a3509912c12df6c4f24fc0a3200 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 24 Mar 2017 18:19:12 +0000 Subject: [PATCH 006/153] Remove rethrowCaughtError injection (#9252) --- src/renderers/shared/utils/ReactErrorUtils.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/renderers/shared/utils/ReactErrorUtils.js b/src/renderers/shared/utils/ReactErrorUtils.js index 71ed3063cdf97..2a08fb1124f18 100644 --- a/src/renderers/shared/utils/ReactErrorUtils.js +++ b/src/renderers/shared/utils/ReactErrorUtils.js @@ -92,12 +92,7 @@ const ReactErrorUtils = { typeof injectedErrorUtils.invokeGuardedCallback === 'function', 'Injected invokeGuardedCallback() must be a function.', ); - invariant( - typeof injectedErrorUtils.rethrowCaughtError === 'function', - 'Injected rethrowCaughtError() must be a function.', - ); invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback; - rethrowCaughtError = injectedErrorUtils.rethrowCaughtError; }, }, From 25deff6203d4cce3e8a9e557c84590052455e22d Mon Sep 17 00:00:00 2001 From: Sasha Aickin Date: Fri, 24 Mar 2017 13:31:15 -0700 Subject: [PATCH 007/153] Add more SSR unit tests for elements and children. (#9221) * Adding more SSR unit tests for elements and children. * Some of my SSR tests were testing for react-text and react-empty elements that no longer exist in Fiber. Fixed the tests so that they expect correct markup in Fiber. * Tweaked some test names after @gaearon review comment https://github.com/facebook/react/pull/9221#discussion_r107045673 . Also realized that one of the tests was essentially a direct copy of another, so deleted it. * Responding to code review https://github.com/facebook/react/pull/9221#pullrequestreview-28996315 . Thanks @spicyj! --- scripts/fiber/tests-failing.txt | 56 ++ scripts/fiber/tests-passing-except-dev.txt | 48 ++ scripts/fiber/tests-passing.txt | 88 +++ .../ReactDOMServerIntegration-test.js | 586 ++++++++++++++++++ 4 files changed, 778 insertions(+) diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index 4a4444a784390..c583af694fb07 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -46,6 +46,62 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders unknown attributes for custom elements with client render on top of good server markup * renders unknown attributes for custom elements using is with client render on top of good server markup * renders no HTML events with client render on top of good server markup +* renders a div with text with client render on top of good server markup +* renders a div with text with flanking whitespace with client render on top of good server markup +* renders a div with an empty text child with client render on top of good server markup +* renders a div with multiple empty text children with server string render +* renders a div with multiple empty text children with client render on top of good server markup +* renders a div with multiple whitespace children with server string render +* renders a div with multiple whitespace children with client render on top of good server markup +* renders a div with text sibling to a node with server string render +* renders a div with text sibling to a node with client render on top of good server markup +* renders a non-standard element with text with client render on top of good server markup +* renders a custom element with text with client render on top of good server markup +* renders a leading blank child with a text sibling with server string render +* renders a leading blank child with a text sibling with client render on top of good server markup +* renders a trailing blank child with a text sibling with server string render +* renders a trailing blank child with a text sibling with client render on top of good server markup +* renders an element with two text children with server string render +* renders an element with two text children with client render on top of good server markup +* renders a number as single child with client render on top of good server markup +* renders zero as single child with client render on top of good server markup +* renders an element with number and text children with server string render +* renders an element with number and text children with client render on top of good server markup +* renders null single child as blank with client render on top of good server markup +* renders false single child as blank with client render on top of good server markup +* renders undefined single child as blank with client render on top of good server markup +* renders a null component children as empty with server string render +* renders a null component children as empty with client render on top of good server markup +* renders null children as blank with server string render +* renders null children as blank with client render on top of good server markup +* renders false children as blank with server string render +* renders false children as blank with client render on top of good server markup +* renders null and false children together as blank with server string render +* renders null and false children together as blank with client render on top of good server markup +* renders only null and false children as blank with client render on top of good server markup +* renders an svg element with client render on top of good server markup +* renders svg element with an xlink with client render on top of good server markup +* renders a math element with client render on top of good server markup +* renders an img with client render on top of good server markup +* renders a button with client render on top of good server markup +* renders a div with dangerouslySetInnerHTML with client render on top of good server markup +* renders a newline-eating tag with content not starting with \n with client render on top of good server markup +* renders a newline-eating tag with content starting with \n with client render on top of good server markup +* renders a normal tag with content starting with \n with client render on top of good server markup +* renders stateless components with client render on top of good server markup +* renders ES6 class components with client render on top of good server markup +* renders factory components with client render on top of good server markup +* renders single child hierarchies of components with client render on top of good server markup +* renders multi-child hierarchies of components with client render on top of good server markup +* renders a div with a child with client render on top of good server markup +* renders a div with multiple children with client render on top of good server markup +* renders a div with multiple children separated by whitespace with server string render +* renders a div with multiple children separated by whitespace with client render on top of good server markup +* renders a div with a single child surrounded by whitespace with server string render +* renders a div with a single child surrounded by whitespace with client render on top of good server markup +* renders >,<, and & as single child with client render on top of good server markup +* renders >,<, and & as multiple children with server string render +* renders >,<, and & as multiple children with client render on top of good server markup src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * can reconcile text merged by Node.normalize() alongside other elements diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index 98dda4bba0127..4ab8bafc5e95d 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -47,6 +47,54 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders unknown attributes for custom elements with client render on top of bad server markup * renders unknown attributes for custom elements using is with client render on top of bad server markup * renders no HTML events with client render on top of bad server markup +* renders a div with text with client render on top of bad server markup +* renders a div with text with flanking whitespace with client render on top of bad server markup +* renders a div with an empty text child with client render on top of bad server markup +* renders a div with multiple empty text children with client render on top of bad server markup +* renders a div with multiple whitespace children with client render on top of bad server markup +* renders a div with text sibling to a node with client render on top of bad server markup +* renders a non-standard element with text with client render on top of bad server markup +* renders a custom element with text with client render on top of bad server markup +* renders a leading blank child with a text sibling with client render on top of bad server markup +* renders a trailing blank child with a text sibling with client render on top of bad server markup +* renders an element with two text children with client render on top of bad server markup +* renders a number as single child with client render on top of bad server markup +* renders zero as single child with client render on top of bad server markup +* renders an element with number and text children with client render on top of bad server markup +* renders null single child as blank with client render on top of bad server markup +* renders false single child as blank with client render on top of bad server markup +* renders undefined single child as blank with client render on top of bad server markup +* renders a null component children as empty with client render on top of bad server markup +* renders null children as blank with client render on top of bad server markup +* renders false children as blank with client render on top of bad server markup +* renders null and false children together as blank with client render on top of bad server markup +* renders only null and false children as blank with client render on top of bad server markup +* renders an svg element with client render on top of bad server markup +* renders svg element with an xlink with client render on top of bad server markup +* renders a math element with client render on top of bad server markup +* renders an img with client render on top of bad server markup +* renders a button with client render on top of bad server markup +* renders a div with dangerouslySetInnerHTML with client render on top of bad server markup +* renders a newline-eating tag with content not starting with \n with client render on top of bad server markup +* renders a newline-eating tag with content starting with \n with client render on top of bad server markup +* renders a normal tag with content starting with \n with client render on top of bad server markup +* renders stateless components with client render on top of bad server markup +* renders ES6 class components with client render on top of bad server markup +* renders factory components with client render on top of bad server markup +* renders single child hierarchies of components with client render on top of bad server markup +* renders multi-child hierarchies of components with client render on top of bad server markup +* renders a div with a child with client render on top of bad server markup +* renders a div with multiple children with client render on top of bad server markup +* renders a div with multiple children separated by whitespace with client render on top of bad server markup +* renders a div with a single child surrounded by whitespace with client render on top of bad server markup +* renders >,<, and & as single child with client render on top of bad server markup +* renders >,<, and & as multiple children with client render on top of bad server markup +* throws when rendering a string component with clean client render +* throws when rendering a string component with client render on top of bad server markup +* throws when rendering an undefined component with clean client render +* throws when rendering an undefined component with client render on top of bad server markup +* throws when rendering a number component with clean client render +* throws when rendering a number component with client render on top of bad server markup src/renderers/dom/shared/__tests__/ReactMount-test.js * should warn if mounting into dirty rendered markup diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 6a88c7cf95c86..16aab7d56eaaf 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1066,6 +1066,94 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders unknown attributes for custom elements using is with clean client render * renders no HTML events with server string render * renders no HTML events with clean client render +* renders a div with text with server string render +* renders a div with text with clean client render +* renders a div with text with flanking whitespace with server string render +* renders a div with text with flanking whitespace with clean client render +* renders a div with an empty text child with server string render +* renders a div with an empty text child with clean client render +* renders a div with multiple empty text children with clean client render +* renders a div with multiple whitespace children with clean client render +* renders a div with text sibling to a node with clean client render +* renders a non-standard element with text with server string render +* renders a non-standard element with text with clean client render +* renders a custom element with text with server string render +* renders a custom element with text with clean client render +* renders a leading blank child with a text sibling with clean client render +* renders a trailing blank child with a text sibling with clean client render +* renders an element with two text children with clean client render +* renders a number as single child with server string render +* renders a number as single child with clean client render +* renders zero as single child with server string render +* renders zero as single child with clean client render +* renders an element with number and text children with clean client render +* renders null single child as blank with server string render +* renders null single child as blank with clean client render +* renders false single child as blank with server string render +* renders false single child as blank with clean client render +* renders undefined single child as blank with server string render +* renders undefined single child as blank with clean client render +* renders a null component children as empty with clean client render +* renders null children as blank with clean client render +* renders false children as blank with clean client render +* renders null and false children together as blank with clean client render +* renders only null and false children as blank with server string render +* renders only null and false children as blank with clean client render +* renders an svg element with server string render +* renders an svg element with clean client render +* renders svg element with an xlink with server string render +* renders svg element with an xlink with clean client render +* renders a math element with server string render +* renders a math element with clean client render +* renders an img with server string render +* renders an img with clean client render +* renders a button with server string render +* renders a button with clean client render +* renders a div with dangerouslySetInnerHTML with server string render +* renders a div with dangerouslySetInnerHTML with clean client render +* renders a newline-eating tag with content not starting with \n with server string render +* renders a newline-eating tag with content not starting with \n with clean client render +* renders a newline-eating tag with content starting with \n with server string render +* renders a newline-eating tag with content starting with \n with clean client render +* renders a normal tag with content starting with \n with server string render +* renders a normal tag with content starting with \n with clean client render +* renders stateless components with server string render +* renders stateless components with clean client render +* renders ES6 class components with server string render +* renders ES6 class components with clean client render +* renders factory components with server string render +* renders factory components with clean client render +* renders single child hierarchies of components with server string render +* renders single child hierarchies of components with clean client render +* renders multi-child hierarchies of components with server string render +* renders multi-child hierarchies of components with clean client render +* renders a div with a child with server string render +* renders a div with a child with clean client render +* renders a div with multiple children with server string render +* renders a div with multiple children with clean client render +* renders a div with multiple children separated by whitespace with clean client render +* renders a div with a single child surrounded by whitespace with clean client render +* renders >,<, and & as single child with server string render +* renders >,<, and & as single child with clean client render +* renders >,<, and & as multiple children with clean client render +* throws when rendering a string component with server string render +* throws when rendering an undefined component with server string render +* throws when rendering a number component with server string render +* throws when rendering null with server string render +* throws when rendering null with clean client render +* throws when rendering null with client render on top of bad server markup +* throws when rendering false with server string render +* throws when rendering false with clean client render +* throws when rendering false with client render on top of bad server markup +* throws when rendering undefined with server string render +* throws when rendering undefined with clean client render +* throws when rendering undefined with client render on top of bad server markup +* throws when rendering number with server string render +* throws when rendering number with clean client render +* throws when rendering number with client render on top of bad server markup +* throws when rendering string with server string render +* throws when rendering string with clean client render +* throws when rendering string with client render on top of bad server markup src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * updates a mounted text component in place diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index f3aa90ce904c3..eb3c5e25b1592 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -15,6 +15,7 @@ let ExecutionEnvironment; let React; let ReactDOM; let ReactDOMServer; +let ReactDOMFeatureFlags; // Helper functions for rendering tests // ==================================== @@ -148,6 +149,31 @@ function itClientRenders(desc, testFn) { testFn(clientRenderOnBadMarkup)); } +function itThrows(desc, testFn) { + it(`throws ${desc}`, () => { + return testFn() + .then(() => + expect(false).toBe('The promise resolved and should not have.')) + .catch(() => {}); + }); +} + +function itThrowsWhenRendering(desc, testFn) { + itThrows(`when rendering ${desc} with server string render`, () => + testFn(serverRender)); + itThrows(`when rendering ${desc} with clean client render`, () => + testFn(clientCleanRender)); + + // we subtract one from the warning count here because the throw means that it won't + // get the usual markup mismatch warning. + itThrows( + `when rendering ${desc} with client render on top of bad server markup`, + () => + testFn((element, warningCount = 0) => + clientRenderOnBadMarkup(element, warningCount - 1)), + ); +} + // When there is a test that renders on server and then on client and expects a logged // error, you want to see the error show up both on server and client. Unfortunately, // React refuses to issue the same error twice to avoid clogging up the console. @@ -157,6 +183,7 @@ function resetModules() { React = require('React'); ReactDOM = require('ReactDOM'); ReactDOMServer = require('ReactDOMServer'); + ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ExecutionEnvironment = require('ExecutionEnvironment'); } @@ -410,4 +437,563 @@ describe('ReactDOMServerIntegration', () => { expect(e.getAttribute('click')).toBe(null); }); }); + + describe('elements and children', function() { + // helper functions. + const TEXT_NODE_TYPE = 3; + const COMMENT_NODE_TYPE = 8; + + function expectNode(node, type, value) { + expect(node).not.toBe(null); + expect(node.nodeType).toBe(type); + expect(node.nodeValue).toMatch(value); + } + + function expectTextNode(node, text) { + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, a React text node is just a DOM text node. + expectNode(node, TEXT_NODE_TYPE, text); + } else { + // with Stack, a React text node is a DOM text node surrounded by + // react-text comment nodes. + expectNode(node, COMMENT_NODE_TYPE, / react-text: [0-9]+ /); + if (text.length > 0) { + node = node.nextSibling; + expectNode(node, TEXT_NODE_TYPE, text); + } + expectNode(node.nextSibling, COMMENT_NODE_TYPE, / \/react-text /); + } + } + + function expectEmptyNode(node) { + expectNode(node, COMMENT_NODE_TYPE, / react-empty: [0-9]+ /); + } + + describe('text children', function() { + itRenders('a div with text', async render => { + const e = await render(
Text
); + expect(e.tagName).toBe('DIV'); + expect(e.childNodes.length).toBe(1); + expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text'); + }); + + itRenders('a div with text with flanking whitespace', async render => { + // prettier-ignore + const e = await render(
Text
); + expect(e.childNodes.length).toBe(1); + expectNode(e.childNodes[0], TEXT_NODE_TYPE, ' Text '); + }); + + itRenders('a div with an empty text child', async render => { + const e = await render(
{''}
); + expect(e.childNodes.length).toBe(0); + }); + + itRenders('a div with multiple empty text children', async render => { + const e = await render(
{''}{''}{''}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just three separate text node children, + // each of which is blank. + expect(e.childNodes.length).toBe(3); + expectTextNode(e.childNodes[0], ''); + expectTextNode(e.childNodes[1], ''); + expectTextNode(e.childNodes[2], ''); + } else { + // with Stack, there are six react-text comment nodes. + expect(e.childNodes.length).toBe(6); + expectTextNode(e.childNodes[0], ''); + expectTextNode(e.childNodes[2], ''); + expectTextNode(e.childNodes[4], ''); + } + }); + + itRenders('a div with multiple whitespace children', async render => { + const e = await render(
{' '}{' '}{' '}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just three text nodes. + expect(e.childNodes.length).toBe(3); + expectTextNode(e.childNodes[0], ' '); + expectTextNode(e.childNodes[1], ' '); + expectTextNode(e.childNodes[2], ' '); + } else { + // with Stack, each of the text nodes is surrounded by react-text + // comment nodes, making 9 nodes in total. + expect(e.childNodes.length).toBe(9); + expectTextNode(e.childNodes[0], ' '); + expectTextNode(e.childNodes[3], ' '); + expectTextNode(e.childNodes[6], ' '); + } + }); + + itRenders('a div with text sibling to a node', async render => { + const e = await render(
TextMore Text
); + let spanNode; + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are only two children, the "Text" text node and + // the span element. + expect(e.childNodes.length).toBe(2); + spanNode = e.childNodes[1]; + } else { + // with Stack, there are four children, a "Text" text node surrounded + // by react-text comment nodes, and the span element. + expect(e.childNodes.length).toBe(4); + spanNode = e.childNodes[3]; + } + expectTextNode(e.childNodes[0], 'Text'); + expect(spanNode.tagName).toBe('SPAN'); + expect(spanNode.childNodes.length).toBe(1); + expectNode(spanNode.firstChild, TEXT_NODE_TYPE, 'More Text'); + }); + + itRenders('a non-standard element with text', async render => { + const e = await render(Text); + expect(e.tagName).toBe('NONSTANDARD'); + expect(e.childNodes.length).toBe(1); + expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text'); + }); + + itRenders('a custom element with text', async render => { + const e = await render(Text); + expect(e.tagName).toBe('CUSTOM-ELEMENT'); + expect(e.childNodes.length).toBe(1); + expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text'); + }); + + itRenders('a leading blank child with a text sibling', async render => { + const e = await render(
{''}foo
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just two text nodes. + expect(e.childNodes.length).toBe(2); + expectTextNode(e.childNodes[0], ''); + expectTextNode(e.childNodes[1], 'foo'); + } else { + // with Stack, there are five nodes: two react-text comment nodes + // without any text between them, and the text node foo surrounded + // by react-text comment nodes. + expect(e.childNodes.length).toBe(5); + expectTextNode(e.childNodes[0], ''); + expectTextNode(e.childNodes[2], 'foo'); + } + }); + + itRenders('a trailing blank child with a text sibling', async render => { + const e = await render(
foo{''}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just two text nodes. + expect(e.childNodes.length).toBe(2); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[1], ''); + } else { + // with Stack, there are five nodes: the text node foo surrounded + // by react-text comment nodes, and two react-text comment nodes + // without any text between them. + expect(e.childNodes.length).toBe(5); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[3], ''); + } + }); + + itRenders('an element with two text children', async render => { + const e = await render(
{'foo'}{'bar'}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just two text nodes. + expect(e.childNodes.length).toBe(2); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[1], 'bar'); + } else { + // with Stack, there are six nodes: two text nodes, each surrounded + // by react-text comment nodes. + expect(e.childNodes.length).toBe(6); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[3], 'bar'); + } + }); + }); + + describe('number children', function() { + itRenders('a number as single child', async render => { + const e = await render(
{3}
); + expect(e.textContent).toBe('3'); + }); + + // zero is falsey, so it could look like no children if the code isn't careful. + itRenders('zero as single child', async render => { + const e = await render(
{0}
); + expect(e.textContent).toBe('0'); + }); + + itRenders('an element with number and text children', async render => { + const e = await render(
{'foo'}{40}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there are just two text nodes. + expect(e.childNodes.length).toBe(2); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[1], '40'); + } else { + // with Stack, there are six nodes: two text nodes, each surrounded + // by react-text comment nodes. + expect(e.childNodes.length).toBe(6); + expectTextNode(e.childNodes[0], 'foo'); + expectTextNode(e.childNodes[3], '40'); + } + }); + }); + + describe('null, false, and undefined children', function() { + itRenders('null single child as blank', async render => { + const e = await render(
{null}
); + expect(e.childNodes.length).toBe(0); + }); + + itRenders('false single child as blank', async render => { + const e = await render(
{false}
); + expect(e.childNodes.length).toBe(0); + }); + + itRenders('undefined single child as blank', async render => { + const e = await render(
{undefined}
); + expect(e.childNodes.length).toBe(0); + }); + + itRenders('a null component children as empty', async render => { + const NullComponent = () => null; + const e = await render(
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, an empty component results in no markup. + expect(e.childNodes.length).toBe(0); + } else { + // with Stack, an empty component results in one react-empty comment + // node. + expect(e.childNodes.length).toBe(1); + expectEmptyNode(e.firstChild); + } + }); + + itRenders('null children as blank', async render => { + const e = await render(
{null}foo
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there is just one text node. + expect(e.childNodes.length).toBe(1); + } else { + // with Stack, there's a text node surronded by react-text comment nodes. + expect(e.childNodes.length).toBe(3); + } + expectTextNode(e.childNodes[0], 'foo'); + }); + + itRenders('false children as blank', async render => { + const e = await render(
{false}foo
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there is just one text node. + expect(e.childNodes.length).toBe(1); + } else { + // with Stack, there's a text node surronded by react-text comment nodes. + expect(e.childNodes.length).toBe(3); + } + expectTextNode(e.childNodes[0], 'foo'); + }); + + itRenders('null and false children together as blank', async render => { + const e = await render(
{false}{null}foo{null}{false}
); + if (ReactDOMFeatureFlags.useFiber) { + // with Fiber, there is just one text node. + expect(e.childNodes.length).toBe(1); + } else { + // with Stack, there's a text node surronded by react-text comment nodes. + expect(e.childNodes.length).toBe(3); + } + expectTextNode(e.childNodes[0], 'foo'); + }); + + itRenders('only null and false children as blank', async render => { + const e = await render(
{false}{null}{null}{false}
); + expect(e.childNodes.length).toBe(0); + }); + }); + + describe('elements with implicit namespaces', function() { + itRenders('an svg element', async render => { + const e = await render(); + expect(e.childNodes.length).toBe(0); + expect(e.tagName).toBe('svg'); + expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg'); + }); + + itRenders('svg element with an xlink', async render => { + let e = await render( + , + ); + e = e.firstChild; + expect(e.childNodes.length).toBe(0); + expect(e.tagName).toBe('image'); + expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg'); + expect(e.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe( + 'http://i.imgur.com/w7GCRPb.png', + ); + }); + + itRenders('a math element', async render => { + const e = await render(); + expect(e.childNodes.length).toBe(0); + expect(e.tagName).toBe('math'); + expect(e.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML'); + }); + }); + // specially wrapped components + // (see the big switch near the beginning ofReactDOMComponent.mountComponent) + itRenders('an img', async render => { + const e = await render(); + expect(e.childNodes.length).toBe(0); + expect(e.nextSibling).toBe(null); + expect(e.tagName).toBe('IMG'); + }); + + itRenders('a button', async render => { + const e = await render( +
+
+ + {this.state.seconds} + +
); From c51411c812d60072a1e5f1b654d7408727093821 Mon Sep 17 00:00:00 2001 From: Sasha Aickin Date: Sun, 26 Mar 2017 13:32:23 -0700 Subject: [PATCH 013/153] Adding new SSR tests for context, refs, and reviving markup. (#9257) --- scripts/fiber/tests-failing.txt | 11 + scripts/fiber/tests-passing-except-dev.txt | 29 ++ scripts/fiber/tests-passing.txt | 40 ++ .../ReactDOMServerIntegration-test.js | 469 ++++++++++++++++++ 4 files changed, 549 insertions(+) diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index c583af694fb07..b99355d063d4e 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -102,6 +102,17 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders >,<, and & as single child with client render on top of good server markup * renders >,<, and & as multiple children with server string render * renders >,<, and & as multiple children with client render on top of good server markup +* renders class child with context with client render on top of good server markup +* renders stateless child with context with client render on top of good server markup +* renders class child without context with client render on top of good server markup +* renders stateless child without context with client render on top of good server markup +* renders class child with wrong context with client render on top of good server markup +* renders stateless child with wrong context with client render on top of good server markup +* renders with context passed through to a grandchild with client render on top of good server markup +* renders a child context overriding a parent context with client render on top of good server markup +* renders a child context merged with a parent context with client render on top of good server markup +* renders with a call to componentWillMount before getChildContext with client render on top of good server markup +* should send the correct element to ref functions on client src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * can reconcile text merged by Node.normalize() alongside other elements diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index 4ab8bafc5e95d..5b7ce16d9231d 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -95,6 +95,35 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering an undefined component with client render on top of bad server markup * throws when rendering a number component with clean client render * throws when rendering a number component with client render on top of bad server markup +* renders class child with context with client render on top of bad server markup +* renders stateless child with context with client render on top of bad server markup +* renders class child without context with client render on top of bad server markup +* renders stateless child without context with client render on top of bad server markup +* renders class child with wrong context with client render on top of bad server markup +* renders stateless child with wrong context with client render on top of bad server markup +* renders with context passed through to a grandchild with client render on top of bad server markup +* renders a child context overriding a parent context with client render on top of bad server markup +* renders a child context merged with a parent context with client render on top of bad server markup +* renders with a call to componentWillMount before getChildContext with client render on top of bad server markup +* should error reconnecting different element types +* should error reconnecting missing attributes +* should error reconnecting added attributes +* should error reconnecting different attribute values +* should error reconnecting different text +* should error reconnecting different numbers +* should error reconnecting different number from text +* should error reconnecting different text in two code blocks +* should error reconnecting missing children +* should error reconnecting added children +* should error reconnecting more children +* should error reconnecting fewer children +* should error reconnecting reordered children +* should error reconnecting a div with children separated by whitespace on the client +* should error reconnecting a div with children separated by different whitespace on the server +* should error reconnecting a div with children separated by different whitespace +* can distinguish an empty component from a dom node +* can distinguish an empty component from an empty text component +* should error reconnecting a div with different dangerouslySetInnerHTML src/renderers/dom/shared/__tests__/ReactMount-test.js * should warn if mounting into dirty rendered markup diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 16aab7d56eaaf..05ca4a1b2bf44 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1154,6 +1154,46 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering string with server string render * throws when rendering string with clean client render * throws when rendering string with client render on top of bad server markup +* renders class child with context with server string render +* renders class child with context with clean client render +* renders stateless child with context with server string render +* renders stateless child with context with clean client render +* renders class child without context with server string render +* renders class child without context with clean client render +* renders stateless child without context with server string render +* renders stateless child without context with clean client render +* renders class child with wrong context with server string render +* renders class child with wrong context with clean client render +* renders stateless child with wrong context with server string render +* renders stateless child with wrong context with clean client render +* renders with context passed through to a grandchild with server string render +* renders with context passed through to a grandchild with clean client render +* renders a child context overriding a parent context with server string render +* renders a child context overriding a parent context with clean client render +* renders a child context merged with a parent context with server string render +* renders a child context merged with a parent context with clean client render +* renders with a call to componentWillMount before getChildContext with server string render +* renders with a call to componentWillMount before getChildContext with clean client render +* throws when rendering if getChildContext exists without childContextTypes with server string render +* throws when rendering if getChildContext exists without childContextTypes with clean client render +* throws when rendering if getChildContext exists without childContextTypes with client render on top of bad server markup +* throws when rendering if getChildContext returns a value not in childContextTypes with server string render +* throws when rendering if getChildContext returns a value not in childContextTypes with clean client render +* throws when rendering if getChildContext returns a value not in childContextTypes with client render on top of bad server markup +* should not run ref code on server +* should run ref code on client +* should have string refs on client when rendered over server markup +* should reconnect ES6 Class to ES6 Class +* should reconnect Pure Component to ES6 Class +* should reconnect Bare Element to ES6 Class +* should reconnect ES6 Class to Pure Component +* should reconnect Pure Component to Pure Component +* should reconnect Bare Element to Pure Component +* should reconnect ES6 Class to Bare Element +* should reconnect Pure Component to Bare Element +* should reconnect Bare Element to Bare Element +* should reconnect a div with a number and string version of number +* should reconnect if component trees differ but resulting markup is the same src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * updates a mounted text component in place diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index eb3c5e25b1592..2eda9c38fc87a 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -174,6 +174,31 @@ function itThrowsWhenRendering(desc, testFn) { ); } +// renders serverElement to a string, sticks it into a DOM element, and then +// tries to render clientElement on top of it. shouldMatch is a boolean +// telling whether we should expect the markup to match or not. +async function testMarkupMatch(serverElement, clientElement, shouldMatch) { + const domElement = await serverRender(serverElement); + resetModules(); + return renderIntoDom( + clientElement, + domElement.parentNode, + shouldMatch ? 0 : 1, + ); +} + +// expects that rendering clientElement on top of a server-rendered +// serverElement does NOT raise a markup mismatch warning. +function expectMarkupMatch(serverElement, clientElement) { + return testMarkupMatch(serverElement, clientElement, true); +} + +// expects that rendering clientElement on top of a server-rendered +// serverElement DOES raise a markup mismatch warning. +function expectMarkupMismatch(serverElement, clientElement) { + return testMarkupMatch(serverElement, clientElement, false); +} + // When there is a test that renders on server and then on client and expects a logged // error, you want to see the error show up both on server and client. Unfortunately, // React refuses to issue the same error twice to avoid clogging up the console. @@ -996,4 +1021,448 @@ describe('ReactDOMServerIntegration', () => { itThrowsWhenRendering('string', render => render('foo')); }); }); + + describe('context', function() { + let PurpleContext, RedContext; + beforeEach(() => { + class Parent extends React.Component { + getChildContext() { + return {text: this.props.text}; + } + render() { + return this.props.children; + } + } + Parent.childContextTypes = {text: React.PropTypes.string}; + + PurpleContext = props => {props.children}; + RedContext = props => {props.children}; + }); + + itRenders('class child with context', async render => { + class ClassChildWithContext extends React.Component { + render() { + return
{this.context.text}
; + } + } + ClassChildWithContext.contextTypes = {text: React.PropTypes.string}; + + const e = await render( + , + ); + expect(e.textContent).toBe('purple'); + }); + + itRenders('stateless child with context', async render => { + function StatelessChildWithContext(props, context) { + return
{context.text}
; + } + StatelessChildWithContext.contextTypes = {text: React.PropTypes.string}; + + const e = await render( + , + ); + expect(e.textContent).toBe('purple'); + }); + + itRenders('class child without context', async render => { + class ClassChildWithoutContext extends React.Component { + render() { + // this should render blank; context isn't passed to this component. + return
{this.context.text}
; + } + } + + const e = await render( + , + ); + expect(e.textContent).toBe(''); + }); + + itRenders('stateless child without context', async render => { + function StatelessChildWithoutContext(props, context) { + // this should render blank; context isn't passed to this component. + return
{context.text}
; + } + + const e = await render( + , + ); + expect(e.textContent).toBe(''); + }); + + itRenders('class child with wrong context', async render => { + class ClassChildWithWrongContext extends React.Component { + render() { + // this should render blank; context.text isn't passed to this component. + return
{this.context.text}
; + } + } + ClassChildWithWrongContext.contextTypes = {foo: React.PropTypes.string}; + + const e = await render( + , + ); + expect(e.textContent).toBe(''); + }); + + itRenders('stateless child with wrong context', async render => { + function StatelessChildWithWrongContext(props, context) { + // this should render blank; context.text isn't passed to this component. + return
{context.text}
; + } + StatelessChildWithWrongContext.contextTypes = { + foo: React.PropTypes.string, + }; + + const e = await render( + , + ); + expect(e.textContent).toBe(''); + }); + + itRenders('with context passed through to a grandchild', async render => { + function Grandchild(props, context) { + return
{context.text}
; + } + Grandchild.contextTypes = {text: React.PropTypes.string}; + + const Child = props => ; + + const e = await render(); + expect(e.textContent).toBe('purple'); + }); + + itRenders('a child context overriding a parent context', async render => { + const Grandchild = (props, context) => { + return
{context.text}
; + }; + Grandchild.contextTypes = {text: React.PropTypes.string}; + + const e = await render( + , + ); + expect(e.textContent).toBe('red'); + }); + + itRenders('a child context merged with a parent context', async render => { + class Parent extends React.Component { + getChildContext() { + return {text1: 'purple'}; + } + render() { + return ; + } + } + Parent.childContextTypes = {text1: React.PropTypes.string}; + + class Child extends React.Component { + getChildContext() { + return {text2: 'red'}; + } + render() { + return ; + } + } + Child.childContextTypes = {text2: React.PropTypes.string}; + + const Grandchild = (props, context) => { + return ( +
+
{context.text1}
+
{context.text2}
+
+ ); + }; + Grandchild.contextTypes = { + text1: React.PropTypes.string, + text2: React.PropTypes.string, + }; + + const e = await render(); + expect(e.querySelector('#first').textContent).toBe('purple'); + expect(e.querySelector('#second').textContent).toBe('red'); + }); + + itRenders( + 'with a call to componentWillMount before getChildContext', + async render => { + class WillMountContext extends React.Component { + getChildContext() { + return {text: this.state.text}; + } + componentWillMount() { + this.setState({text: 'foo'}); + } + render() { + return ; + } + } + WillMountContext.childContextTypes = {text: React.PropTypes.string}; + + const Child = (props, context) => { + return
{context.text}
; + }; + Child.contextTypes = {text: React.PropTypes.string}; + + const e = await render(); + expect(e.textContent).toBe('foo'); + }, + ); + + itThrowsWhenRendering( + 'if getChildContext exists without childContextTypes', + render => { + class Component extends React.Component { + render() { + return
; + } + getChildContext() { + return {foo: 'bar'}; + } + } + return render(); + }, + ); + + itThrowsWhenRendering( + 'if getChildContext returns a value not in childContextTypes', + render => { + class Component extends React.Component { + render() { + return
; + } + getChildContext() { + return {value1: 'foo', value2: 'bar'}; + } + } + Component.childContextTypes = {value1: React.PropTypes.string}; + return render(); + }, + ); + }); + + describe('refs', function() { + it('should not run ref code on server', async () => { + let refCount = 0; + class RefsComponent extends React.Component { + render() { + return
refCount++} />; + } + } + await expectMarkupMatch(,
); + expect(refCount).toBe(0); + }); + + it('should run ref code on client', async () => { + let refCount = 0; + class RefsComponent extends React.Component { + render() { + return
refCount++} />; + } + } + await expectMarkupMatch(
, ); + expect(refCount).toBe(1); + }); + + it('should send the correct element to ref functions on client', async () => { + let refElement = null; + class RefsComponent extends React.Component { + render() { + return
refElement = e} />; + } + } + const e = await clientRenderOnServerString(); + expect(refElement).not.toBe(null); + expect(refElement).toBe(e); + }); + + it('should have string refs on client when rendered over server markup', async () => { + class RefsComponent extends React.Component { + render() { + return
; + } + } + + const markup = ReactDOMServer.renderToString(); + const root = document.createElement('div'); + root.innerHTML = markup; + let component = null; + resetModules(); + await asyncReactDOMRender( + component = e} />, + root, + ); + expect(component.refs.myDiv).toBe(root.firstChild); + }); + }); + + describe('reconnecting to server markup', function() { + var EmptyComponent; + beforeEach(() => { + EmptyComponent = class extends React.Component { + render() { + return null; + } + }; + }); + + describe('elements', function() { + describe('reconnecting different component implementations', function() { + let ES6ClassComponent, PureComponent, bareElement; + beforeEach(() => { + // try each type of component on client and server. + ES6ClassComponent = class extends React.Component { + render() { + return
; + } + }; + PureComponent = props =>
; + bareElement =
; + }); + + it('should reconnect ES6 Class to ES6 Class', () => + expectMarkupMatch( + , + , + )); + + it('should reconnect Pure Component to ES6 Class', () => + expectMarkupMatch( + , + , + )); + + it('should reconnect Bare Element to ES6 Class', () => + expectMarkupMatch(, bareElement)); + + it('should reconnect ES6 Class to Pure Component', () => + expectMarkupMatch( + , + , + )); + + it('should reconnect Pure Component to Pure Component', () => + expectMarkupMatch( + , + , + )); + + it('should reconnect Bare Element to Pure Component', () => + expectMarkupMatch(, bareElement)); + + it('should reconnect ES6 Class to Bare Element', () => + expectMarkupMatch(bareElement, )); + + it('should reconnect Pure Component to Bare Element', () => + expectMarkupMatch(bareElement, )); + + it('should reconnect Bare Element to Bare Element', () => + expectMarkupMatch(bareElement, bareElement)); + }); + + it('should error reconnecting different element types', () => + expectMarkupMismatch(
, )); + + it('should error reconnecting missing attributes', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting added attributes', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting different attribute values', () => + expectMarkupMismatch(
,
)); + }); + + describe('text nodes', function() { + it('should error reconnecting different text', () => + expectMarkupMismatch(
Text
,
Other Text
)); + + it('should reconnect a div with a number and string version of number', () => + expectMarkupMatch(
{2}
,
2
)); + + it('should error reconnecting different numbers', () => + expectMarkupMismatch(
{2}
,
{3}
)); + + it('should error reconnecting different number from text', () => + expectMarkupMismatch(
{2}
,
3
)); + + it('should error reconnecting different text in two code blocks', () => + expectMarkupMismatch( +
{'Text1'}{'Text2'}
, +
{'Text1'}{'Text3'}
, + )); + }); + + describe('element trees and children', function() { + it('should error reconnecting missing children', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting added children', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting more children', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting fewer children', () => + expectMarkupMismatch(
,
)); + + it('should error reconnecting reordered children', () => + expectMarkupMismatch( +
, +
, + )); + + it('should error reconnecting a div with children separated by whitespace on the client', () => + expectMarkupMismatch( +
, + // prettier-ignore +
, // eslint-disable-line no-multi-spaces + )); + + it('should error reconnecting a div with children separated by different whitespace on the server', () => + expectMarkupMismatch( + // prettier-ignore +
, // eslint-disable-line no-multi-spaces +
, + )); + + it('should error reconnecting a div with children separated by different whitespace', () => + expectMarkupMismatch( +
, + // prettier-ignore +
, // eslint-disable-line no-multi-spaces + )); + + it('can distinguish an empty component from a dom node', () => + expectMarkupMismatch( +
, +
, + )); + + it('can distinguish an empty component from an empty text component', () => + expectMarkupMismatch(
,
{''}
)); + + it('should reconnect if component trees differ but resulting markup is the same', () => { + class Component1 extends React.Component { + render() { + return ; + } + } + class Component2 extends React.Component { + render() { + return ; + } + } + return expectMarkupMatch(, ); + }); + }); + + // Markup Mismatches: misc + it('should error reconnecting a div with different dangerouslySetInnerHTML', () => + expectMarkupMismatch( +
"}} />, +
"}} />, + )); + }); }); From 14863c9a8722281b6bf1d0d9fc7b95b44e189354 Mon Sep 17 00:00:00 2001 From: Sasha Aickin Date: Sun, 26 Mar 2017 20:51:40 -0700 Subject: [PATCH 014/153] Adding SSR test for form fields. --- scripts/fiber/tests-failing.txt | 36 ++ scripts/fiber/tests-passing-except-dev.txt | 30 + scripts/fiber/tests-passing.txt | 57 +- .../ReactDOMServerIntegration-test.js | 562 +++++++++++++++++- 4 files changed, 670 insertions(+), 15 deletions(-) diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index b99355d063d4e..3bd47469acf98 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -102,6 +102,42 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * renders >,<, and & as single child with client render on top of good server markup * renders >,<, and & as multiple children with server string render * renders >,<, and & as multiple children with client render on top of good server markup +* renders an input with a value and an onChange with client render on top of good server markup +* renders an input with a value and readOnly with client render on top of good server markup +* renders an input with a value and no onChange/readOnly with client render on top of good server markup +* renders an input with a defaultValue with client render on top of good server markup +* renders an input value overriding defaultValue with client render on top of good server markup +* renders an input value overriding defaultValue no matter the prop order with client render on top of good server markup +* renders a checkbox that is checked with an onChange with client render on top of good server markup +* renders a checkbox that is checked with readOnly with client render on top of good server markup +* renders a checkbox that is checked and no onChange/readOnly with client render on top of good server markup +* renders a checkbox with defaultChecked with client render on top of good server markup +* renders a checkbox checked overriding defaultChecked with client render on top of good server markup +* renders a checkbox checked overriding defaultChecked no matter the prop order with client render on top of good server markup +* renders a textarea with a value and an onChange with client render on top of good server markup +* renders a textarea with a value and readOnly with client render on top of good server markup +* renders a textarea with a value and no onChange/readOnly with client render on top of good server markup +* renders a textarea with a defaultValue with client render on top of good server markup +* renders a textarea value overriding defaultValue with client render on top of good server markup +* renders a textarea value overriding defaultValue no matter the prop order with client render on top of good server markup +* renders a select with a value and an onChange with client render on top of good server markup +* renders a select with a value and readOnly with client render on top of good server markup +* renders a select with a multiple values and an onChange with client render on top of good server markup +* renders a select with a multiple values and readOnly with client render on top of good server markup +* renders a select with a value and no onChange/readOnly with client render on top of good server markup +* renders a select with a defaultValue with client render on top of good server markup +* renders a select value overriding defaultValue with client render on top of good server markup +* renders a select value overriding defaultValue no matter the prop order with client render on top of good server markup +* renders a controlled text input with client render on top of good server markup +* renders a controlled textarea with client render on top of good server markup +* renders a controlled checkbox with client render on top of good server markup +* renders a controlled select with client render on top of good server markup +* should not blow away user-entered text on successful reconnect to an uncontrolled input +* should not blow away user-entered text on successful reconnect to a controlled input +* should not blow away user-entered text on successful reconnect to an uncontrolled checkbox +* should not blow away user-entered text on successful reconnect to a controlled checkbox +* should not blow away user-selected value on successful reconnect to an uncontrolled select +* should not blow away user-selected value on successful reconnect to an controlled select * renders class child with context with client render on top of good server markup * renders stateless child with context with client render on top of good server markup * renders class child without context with client render on top of good server markup diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index 5b7ce16d9231d..203bd2f0e59d1 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -95,6 +95,36 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering an undefined component with client render on top of bad server markup * throws when rendering a number component with clean client render * throws when rendering a number component with client render on top of bad server markup +* renders an input with a value and an onChange with client render on top of bad server markup +* renders an input with a value and readOnly with client render on top of bad server markup +* renders an input with a value and no onChange/readOnly with client render on top of bad server markup +* renders an input with a defaultValue with client render on top of bad server markup +* renders an input value overriding defaultValue with client render on top of bad server markup +* renders an input value overriding defaultValue no matter the prop order with client render on top of bad server markup +* renders a checkbox that is checked with an onChange with client render on top of bad server markup +* renders a checkbox that is checked with readOnly with client render on top of bad server markup +* renders a checkbox that is checked and no onChange/readOnly with client render on top of bad server markup +* renders a checkbox with defaultChecked with client render on top of bad server markup +* renders a checkbox checked overriding defaultChecked with client render on top of bad server markup +* renders a checkbox checked overriding defaultChecked no matter the prop order with client render on top of bad server markup +* renders a textarea with a value and an onChange with client render on top of bad server markup +* renders a textarea with a value and readOnly with client render on top of bad server markup +* renders a textarea with a value and no onChange/readOnly with client render on top of bad server markup +* renders a textarea with a defaultValue with client render on top of bad server markup +* renders a textarea value overriding defaultValue with client render on top of bad server markup +* renders a textarea value overriding defaultValue no matter the prop order with client render on top of bad server markup +* renders a select with a value and an onChange with client render on top of bad server markup +* renders a select with a value and readOnly with client render on top of bad server markup +* renders a select with a multiple values and an onChange with client render on top of bad server markup +* renders a select with a multiple values and readOnly with client render on top of bad server markup +* renders a select with a value and no onChange/readOnly with client render on top of bad server markup +* renders a select with a defaultValue with client render on top of bad server markup +* renders a select value overriding defaultValue with client render on top of bad server markup +* renders a select value overriding defaultValue no matter the prop order with client render on top of bad server markup +* renders a controlled text input with client render on top of bad server markup +* renders a controlled textarea with client render on top of bad server markup +* renders a controlled checkbox with client render on top of bad server markup +* renders a controlled select with client render on top of bad server markup * renders class child with context with client render on top of bad server markup * renders stateless child with context with client render on top of bad server markup * renders class child without context with client render on top of bad server markup diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 05ca4a1b2bf44..79e243d6644fc 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1154,6 +1154,62 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * throws when rendering string with server string render * throws when rendering string with clean client render * throws when rendering string with client render on top of bad server markup +* renders an input with a value and an onChange with server string render +* renders an input with a value and an onChange with clean client render +* renders an input with a value and readOnly with server string render +* renders an input with a value and readOnly with clean client render +* renders an input with a value and no onChange/readOnly with server string render +* renders an input with a value and no onChange/readOnly with clean client render +* renders an input with a defaultValue with server string render +* renders an input with a defaultValue with clean client render +* renders an input value overriding defaultValue with server string render +* renders an input value overriding defaultValue with clean client render +* renders an input value overriding defaultValue no matter the prop order with server string render +* renders an input value overriding defaultValue no matter the prop order with clean client render +* renders a checkbox that is checked with an onChange with server string render +* renders a checkbox that is checked with an onChange with clean client render +* renders a checkbox that is checked with readOnly with server string render +* renders a checkbox that is checked with readOnly with clean client render +* renders a checkbox that is checked and no onChange/readOnly with server string render +* renders a checkbox that is checked and no onChange/readOnly with clean client render +* renders a checkbox with defaultChecked with server string render +* renders a checkbox with defaultChecked with clean client render +* renders a checkbox checked overriding defaultChecked with server string render +* renders a checkbox checked overriding defaultChecked with clean client render +* renders a checkbox checked overriding defaultChecked no matter the prop order with server string render +* renders a checkbox checked overriding defaultChecked no matter the prop order with clean client render +* renders a textarea with a value and an onChange with server string render +* renders a textarea with a value and an onChange with clean client render +* renders a textarea with a value and readOnly with server string render +* renders a textarea with a value and readOnly with clean client render +* renders a textarea with a value and no onChange/readOnly with server string render +* renders a textarea with a value and no onChange/readOnly with clean client render +* renders a textarea with a defaultValue with server string render +* renders a textarea with a defaultValue with clean client render +* renders a textarea value overriding defaultValue with server string render +* renders a textarea value overriding defaultValue with clean client render +* renders a textarea value overriding defaultValue no matter the prop order with server string render +* renders a textarea value overriding defaultValue no matter the prop order with clean client render +* renders a select with a value and an onChange with server string render +* renders a select with a value and an onChange with clean client render +* renders a select with a value and readOnly with server string render +* renders a select with a value and readOnly with clean client render +* renders a select with a multiple values and an onChange with server string render +* renders a select with a multiple values and an onChange with clean client render +* renders a select with a multiple values and readOnly with server string render +* renders a select with a multiple values and readOnly with clean client render +* renders a select with a value and no onChange/readOnly with server string render +* renders a select with a value and no onChange/readOnly with clean client render +* renders a select with a defaultValue with server string render +* renders a select with a defaultValue with clean client render +* renders a select value overriding defaultValue with server string render +* renders a select value overriding defaultValue with clean client render +* renders a select value overriding defaultValue no matter the prop order with server string render +* renders a select value overriding defaultValue no matter the prop order with clean client render +* renders a controlled text input with clean client render +* renders a controlled textarea with clean client render +* renders a controlled checkbox with clean client render +* renders a controlled select with clean client render * renders class child with context with server string render * renders class child with context with clean client render * renders stateless child with context with server string render @@ -1193,7 +1249,6 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js * should reconnect Pure Component to Bare Element * should reconnect Bare Element to Bare Element * should reconnect a div with a number and string version of number -* should reconnect if component trees differ but resulting markup is the same src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js * updates a mounted text component in place diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js index 2eda9c38fc87a..403254c66ff91 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js @@ -16,6 +16,7 @@ let React; let ReactDOM; let ReactDOMServer; let ReactDOMFeatureFlags; +let ReactTestUtils; // Helper functions for rendering tests // ==================================== @@ -209,6 +210,7 @@ function resetModules() { ReactDOM = require('ReactDOM'); ReactDOMServer = require('ReactDOMServer'); ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); + ReactTestUtils = require('ReactTestUtils'); ExecutionEnvironment = require('ExecutionEnvironment'); } @@ -1022,6 +1024,552 @@ describe('ReactDOMServerIntegration', () => { }); }); + describe('form controls', function() { + describe('inputs', function() { + itRenders('an input with a value and an onChange', async render => { + const e = await render( {}} />); + expect(e.value).toBe('foo'); + }); + + itRenders('an input with a value and readOnly', async render => { + const e = await render(); + expect(e.value).toBe('foo'); + }); + + itRenders( + 'an input with a value and no onChange/readOnly', + async render => { + // this configuration should raise a dev warning that value without + // onChange or readOnly is a mistake. + const e = await render(, 1); + expect(e.value).toBe('foo'); + }, + ); + + itRenders('an input with a defaultValue', async render => { + const e = await render(); + expect(e.value).toBe('foo'); + expect(e.getAttribute('defaultValue')).toBe(null); + }); + + itRenders('an input value overriding defaultValue', async render => { + const e = await render( + , + 1, + ); + expect(e.value).toBe('foo'); + expect(e.getAttribute('defaultValue')).toBe(null); + }); + + itRenders( + 'an input value overriding defaultValue no matter the prop order', + async render => { + const e = await render( + , + 1, + ); + expect(e.value).toBe('foo'); + expect(e.getAttribute('defaultValue')).toBe(null); + }, + ); + }); + + describe('checkboxes', function() { + itRenders('a checkbox that is checked with an onChange', async render => { + const e = await render( + {}} />, + ); + expect(e.checked).toBe(true); + }); + + itRenders('a checkbox that is checked with readOnly', async render => { + const e = await render( + , + ); + expect(e.checked).toBe(true); + }); + + itRenders( + 'a checkbox that is checked and no onChange/readOnly', + async render => { + // this configuration should raise a dev warning that checked without + // onChange or readOnly is a mistake. + const e = await render(, 1); + expect(e.checked).toBe(true); + }, + ); + + itRenders('a checkbox with defaultChecked', async render => { + const e = await render(); + expect(e.checked).toBe(true); + expect(e.getAttribute('defaultChecked')).toBe(null); + }); + + itRenders( + 'a checkbox checked overriding defaultChecked', + async render => { + const e = await render( + , + 1, + ); + expect(e.checked).toBe(true); + expect(e.getAttribute('defaultChecked')).toBe(null); + }, + ); + + itRenders( + 'a checkbox checked overriding defaultChecked no matter the prop order', + async render => { + const e = await render( + , + 1, + ); + expect(e.checked).toBe(true); + expect(e.getAttribute('defaultChecked')).toBe(null); + }, + ); + }); + + describe('textareas', function() { + // textareas + // --------- + itRenders('a textarea with a value and an onChange', async render => { + const e = await render(