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');
+ });
+ });
});