From c6f3524df585c193214e7e685a11b5f2d1953b49 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 28 Mar 2019 15:36:21 -0700 Subject: [PATCH] Adds React event component and React event target support to SSR renderer (#15242) * Adds React event component and React event target support to SSR renderer --- .../src/server/ReactPartialRenderer.js | 29 +++ .../ReactFiberEvents-test-internal.js | 222 +++++++++++++++++- 2 files changed, 250 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 6a8231e56dfb6..7eecd8dd41b03 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -22,6 +22,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import { warnAboutDeprecatedLifecycles, enableSuspenseServerRenderer, + enableEventAPI, } from 'shared/ReactFeatureFlags'; import { @@ -36,6 +37,8 @@ import { REACT_CONTEXT_TYPE, REACT_LAZY_TYPE, REACT_MEMO_TYPE, + REACT_EVENT_COMPONENT_TYPE, + REACT_EVENT_TARGET_TYPE, } from 'shared/ReactSymbols'; import { @@ -1162,6 +1165,32 @@ class ReactDOMServerRenderer { this.stack.push(frame); return ''; } + case REACT_EVENT_COMPONENT_TYPE: + case REACT_EVENT_TARGET_TYPE: { + if (enableEventAPI) { + const nextChildren = toArray( + ((nextChild: any): ReactElement).props.children, + ); + const frame: Frame = { + type: null, + domNamespace: parentNamespace, + children: nextChildren, + childIndex: 0, + context: context, + footer: '', + }; + if (__DEV__) { + ((frame: any): FrameDev).debugElementStack = []; + } + this.stack.push(frame); + return ''; + } + invariant( + false, + 'ReactDOMServer does not yet support the event API.', + ); + } + // eslint-disable-next-line-no-fallthrough case REACT_LAZY_TYPE: invariant( false, diff --git a/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js index 0740cb39a106a..40911388c71c6 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. * * @emails react-core - * @jest-environment node */ 'use strict'; @@ -16,6 +15,8 @@ let Scheduler; let ReactFeatureFlags; let EventComponent; let ReactTestRenderer; +let ReactDOM; +let ReactDOMServer; let EventTarget; let ReactEvents; @@ -51,6 +52,16 @@ function initTestRenderer() { ReactTestRenderer = require('react-test-renderer'); } +function initReactDOM() { + init(); + ReactDOM = require('react-dom'); +} + +function initReactDOMServer() { + init(); + ReactDOMServer = require('react-dom/server'); +} + // This is a new feature in Fiber so I put it in its own test file. It could // probably move to one of the other test files once it is official. describe('ReactFiberEvents', () => { @@ -358,4 +369,213 @@ describe('ReactFiberEvents', () => { ); }); }); + + describe('ReactDOM', () => { + beforeEach(() => { + initReactDOM(); + EventComponent = createReactEventComponent(); + EventTarget = ReactEvents.TouchHitTarget; + }); + + it('should render a simple event component with a single child', () => { + const Test = () => ( + +
Hello world
+
+ ); + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + expect(container.innerHTML).toBe('
Hello world
'); + }); + + it('should warn when an event component has a direct text child', () => { + const Test = () => Hello world; + + expect(() => { + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should warn when an event component has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + ); + + expect(() => { + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should render a simple event component with a single event target', () => { + const Test = () => ( + + +
Hello world
+
+
+ ); + + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + expect(container.innerHTML).toBe('
Hello world
'); + + const Test2 = () => ( + + + I am now a span + + + ); + + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + expect(container.innerHTML).toBe('I am now a span'); + }); + + it('should warn when an event target has a direct text child', () => { + const Test = () => ( + + Hello world + + ); + + expect(() => { + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + + + ); + + expect(() => { + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has more than one child', () => { + const Test = () => ( + + + Child 1 + Child 2 + + + ); + + const container = document.createElement('div'); + expect(() => { + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: must only have a single DOM element as a child. Found many children.', + ); + // This should not fire a warning, as this is now valid. + const Test2 = () => ( + + + Child 1 + + + ); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + expect(container.innerHTML).toBe('Child 1'); + }); + + it('should warn if an event target is not a direct child of an event component', () => { + const Test = () => ( + +
+ + Child 1 + +
+
+ ); + + expect(() => { + const container = document.createElement('div'); + ReactDOM.render(, container); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event targets must be direct children of event components.', + ); + }); + }); + + describe('ReactDOMServer', () => { + beforeEach(() => { + initReactDOMServer(); + EventComponent = createReactEventComponent(); + EventTarget = ReactEvents.TouchHitTarget; + }); + + it('should render a simple event component with a single child', () => { + const Test = () => ( + +
Hello world
+
+ ); + const output = ReactDOMServer.renderToString(); + expect(output).toBe('
Hello world
'); + }); + + it('should render a simple event component with a single event target', () => { + const Test = () => ( + + +
Hello world
+
+
+ ); + + let output = ReactDOMServer.renderToString(); + expect(output).toBe('
Hello world
'); + + const Test2 = () => ( + + + I am now a span + + + ); + + output = ReactDOMServer.renderToString(); + expect(output).toBe('I am now a span'); + }); + }); });