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
Dominic Gannaway committed
Mar 28, 2019 at 15:36 UTC
c6f3524df585c193214e7e685a11b5f2d1953b49
2 files changed
+250
-1
packages/react-dom/src/server/ReactPartialRenderer.js
+29
@@ -22,6 +22,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
22
import {
23
warnAboutDeprecatedLifecycles,
24
enableSuspenseServerRenderer,
25
+ enableEventAPI,
26
} from 'shared/ReactFeatureFlags';
27
28
import {
@@ -36,6 +37,8 @@ import {
37
REACT_CONTEXT_TYPE,
38
REACT_LAZY_TYPE,
39
REACT_MEMO_TYPE,
40
+ REACT_EVENT_COMPONENT_TYPE,
41
+ REACT_EVENT_TARGET_TYPE,
42
} from 'shared/ReactSymbols';
43
44
import {
@@ -1162,6 +1165,32 @@ class ReactDOMServerRenderer {
1165
this.stack.push(frame);
1166
return '';
1167
}
1168
+ case REACT_EVENT_COMPONENT_TYPE:
1169
+ case REACT_EVENT_TARGET_TYPE: {
1170
+ if (enableEventAPI) {
1171
+ const nextChildren = toArray(
1172
+ ((nextChild: any): ReactElement).props.children,
1173
+ );
1174
+ const frame: Frame = {
1175
+ type: null,
1176
+ domNamespace: parentNamespace,
1177
+ children: nextChildren,
1178
+ childIndex: 0,
1179
+ context: context,
1180
+ footer: '',
1181
+ };
1182
+ if (__DEV__) {
1183
+ ((frame: any): FrameDev).debugElementStack = [];
1184
+ }
1185
+ this.stack.push(frame);
1186
+ return '';
1187
+ }
1188
+ invariant(
1189
+ false,
1190
+ 'ReactDOMServer does not yet support the event API.',
1191
+ );
1192
+ }
1193
+ // eslint-disable-next-line-no-fallthrough
1194
case REACT_LAZY_TYPE:
1195
invariant(
1196
false,
packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js
+221
-1
@@ -5,7 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*
7
* @emails react-core
8
- * @jest-environment node
8
*/
9
10
'use strict';
@@ -16,6 +15,8 @@ let Scheduler;
15
let ReactFeatureFlags;
16
let EventComponent;
17
let ReactTestRenderer;
18
+let ReactDOM;
19
+let ReactDOMServer;
20
let EventTarget;
21
let ReactEvents;
22
@@ -51,6 +52,16 @@ function initTestRenderer() {
52
ReactTestRenderer = require('react-test-renderer');
53
}
54
55
+function initReactDOM() {
56
+ init();
57
+ ReactDOM = require('react-dom');
58
+}
59
+
60
+function initReactDOMServer() {
61
+ init();
62
+ ReactDOMServer = require('react-dom/server');
63
+}
64
+
65
// This is a new feature in Fiber so I put it in its own test file. It could
66
// probably move to one of the other test files once it is official.
67
describe('ReactFiberEvents', () => {
@@ -358,4 +369,213 @@ describe('ReactFiberEvents', () => {
369
);
370
});
371
});
372
+
373
+ describe('ReactDOM', () => {
374
+ beforeEach(() => {
375
+ initReactDOM();
376
+ EventComponent = createReactEventComponent();
377
+ EventTarget = ReactEvents.TouchHitTarget;
378
+ });
379
+
380
+ it('should render a simple event component with a single child', () => {
381
+ const Test = () => (
382
+ <EventComponent>
383
+ <div>Hello world</div>
384
+ </EventComponent>
385
+ );
386
+ const container = document.createElement('div');
387
+ ReactDOM.render(<Test />, container);
388
+ expect(Scheduler).toFlushWithoutYielding();
389
+ expect(container.innerHTML).toBe('<div>Hello world</div>');
390
+ });
391
+
392
+ it('should warn when an event component has a direct text child', () => {
393
+ const Test = () => <EventComponent>Hello world</EventComponent>;
394
+
395
+ expect(() => {
396
+ const container = document.createElement('div');
397
+ ReactDOM.render(<Test />, container);
398
+ expect(Scheduler).toFlushWithoutYielding();
399
+ }).toWarnDev(
400
+ 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
401
+ 'Wrap the child text "Hello world" in an element.',
402
+ );
403
+ });
404
+
405
+ it('should warn when an event component has a direct text child #2', () => {
406
+ const ChildWrapper = () => 'Hello world';
407
+ const Test = () => (
408
+ <EventComponent>
409
+ <ChildWrapper />
410
+ </EventComponent>
411
+ );
412
+
413
+ expect(() => {
414
+ const container = document.createElement('div');
415
+ ReactDOM.render(<Test />, container);
416
+ expect(Scheduler).toFlushWithoutYielding();
417
+ }).toWarnDev(
418
+ 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
419
+ 'Wrap the child text "Hello world" in an element.',
420
+ );
421
+ });
422
+
423
+ it('should render a simple event component with a single event target', () => {
424
+ const Test = () => (
425
+ <EventComponent>
426
+ <EventTarget>
427
+ <div>Hello world</div>
428
+ </EventTarget>
429
+ </EventComponent>
430
+ );
431
+
432
+ const container = document.createElement('div');
433
+ ReactDOM.render(<Test />, container);
434
+ expect(Scheduler).toFlushWithoutYielding();
435
+ expect(container.innerHTML).toBe('<div>Hello world</div>');
436
+
437
+ const Test2 = () => (
438
+ <EventComponent>
439
+ <EventTarget>
440
+ <span>I am now a span</span>
441
+ </EventTarget>
442
+ </EventComponent>
443
+ );
444
+
445
+ ReactDOM.render(<Test2 />, container);
446
+ expect(Scheduler).toFlushWithoutYielding();
447
+ expect(container.innerHTML).toBe('<span>I am now a span</span>');
448
+ });
449
+
450
+ it('should warn when an event target has a direct text child', () => {
451
+ const Test = () => (
452
+ <EventComponent>
453
+ <EventTarget>Hello world</EventTarget>
454
+ </EventComponent>
455
+ );
456
+
457
+ expect(() => {
458
+ const container = document.createElement('div');
459
+ ReactDOM.render(<Test />, container);
460
+ expect(Scheduler).toFlushWithoutYielding();
461
+ }).toWarnDev([
462
+ 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
463
+ 'Wrap the child text "Hello world" in an element.',
464
+ 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
465
+ ]);
466
+ });
467
+
468
+ it('should warn when an event target has a direct text child #2', () => {
469
+ const ChildWrapper = () => 'Hello world';
470
+ const Test = () => (
471
+ <EventComponent>
472
+ <EventTarget>
473
+ <ChildWrapper />
474
+ </EventTarget>
475
+ </EventComponent>
476
+ );
477
+
478
+ expect(() => {
479
+ const container = document.createElement('div');
480
+ ReactDOM.render(<Test />, container);
481
+ expect(Scheduler).toFlushWithoutYielding();
482
+ }).toWarnDev([
483
+ 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' +
484
+ 'Wrap the child text "Hello world" in an element.',
485
+ 'Warning: <TouchHitTarget> must have a single DOM element as a child. Found no children.',
486
+ ]);
487
+ });
488
+
489
+ it('should warn when an event target has more than one child', () => {
490
+ const Test = () => (
491
+ <EventComponent>
492
+ <EventTarget>
493
+ <span>Child 1</span>
494
+ <span>Child 2</span>
495
+ </EventTarget>
496
+ </EventComponent>
497
+ );
498
+
499
+ const container = document.createElement('div');
500
+ expect(() => {
501
+ ReactDOM.render(<Test />, container);
502
+ expect(Scheduler).toFlushWithoutYielding();
503
+ }).toWarnDev(
504
+ 'Warning: <TouchHitTarget> must only have a single DOM element as a child. Found many children.',
505
+ );
506
+ // This should not fire a warning, as this is now valid.
507
+ const Test2 = () => (
508
+ <EventComponent>
509
+ <EventTarget>
510
+ <span>Child 1</span>
511
+ </EventTarget>
512
+ </EventComponent>
513
+ );
514
+ ReactDOM.render(<Test2 />, container);
515
+ expect(Scheduler).toFlushWithoutYielding();
516
+ expect(container.innerHTML).toBe('<span>Child 1</span>');
517
+ });
518
+
519
+ it('should warn if an event target is not a direct child of an event component', () => {
520
+ const Test = () => (
521
+ <EventComponent>
522
+ <div>
523
+ <EventTarget>
524
+ <span>Child 1</span>
525
+ </EventTarget>
526
+ </div>
527
+ </EventComponent>
528
+ );
529
+
530
+ expect(() => {
531
+ const container = document.createElement('div');
532
+ ReactDOM.render(<Test />, container);
533
+ expect(Scheduler).toFlushWithoutYielding();
534
+ }).toWarnDev(
535
+ 'Warning: validateDOMNesting: React event targets must be direct children of event components.',
536
+ );
537
+ });
538
+ });
539
+
540
+ describe('ReactDOMServer', () => {
541
+ beforeEach(() => {
542
+ initReactDOMServer();
543
+ EventComponent = createReactEventComponent();
544
+ EventTarget = ReactEvents.TouchHitTarget;
545
+ });
546
+
547
+ it('should render a simple event component with a single child', () => {
548
+ const Test = () => (
549
+ <EventComponent>
550
+ <div>Hello world</div>
551
+ </EventComponent>
552
+ );
553
+ const output = ReactDOMServer.renderToString(<Test />);
554
+ expect(output).toBe('<div>Hello world</div>');
555
+ });
556
+
557
+ it('should render a simple event component with a single event target', () => {
558
+ const Test = () => (
559
+ <EventComponent>
560
+ <EventTarget>
561
+ <div>Hello world</div>
562
+ </EventTarget>
563
+ </EventComponent>
564
+ );
565
+
566
+ let output = ReactDOMServer.renderToString(<Test />);
567
+ expect(output).toBe('<div>Hello world</div>');
568
+
569
+ const Test2 = () => (
570
+ <EventComponent>
571
+ <EventTarget>
572
+ <span>I am now a span</span>
573
+ </EventTarget>
574
+ </EventComponent>
575
+ );
576
+
577
+ output = ReactDOMServer.renderToString(<Test2 />);
578
+ expect(output).toBe('<span>I am now a span</span>');
579
+ });
580
+ });
581
});