@@ -95,7 +95,7 @@ describe('ReactFlight', () => {
95
95
} ;
96
96
}
97
97
98
- it ( 'can render a server component ' , async ( ) => {
98
+ it ( 'can render a Server Component ' , async ( ) => {
99
99
function Bar ( { text} ) {
100
100
return text . toUpperCase ( ) ;
101
101
}
@@ -125,7 +125,7 @@ describe('ReactFlight', () => {
125
125
} ) ;
126
126
} ) ;
127
127
128
- it ( 'can render a client component using a module reference and render there' , async ( ) => {
128
+ it ( 'can render a Client Component using a module reference and render there' , async ( ) => {
129
129
function UserClient ( props ) {
130
130
return (
131
131
< span >
@@ -363,6 +363,11 @@ describe('ReactFlight', () => {
363
363
364
364
// @gate enableUseHook
365
365
it ( 'should error if a non-serializable value is passed to a host component' , async ( ) => {
366
+ function ClientImpl ( { children} ) {
367
+ return children ;
368
+ }
369
+ const Client = moduleReference ( ClientImpl ) ;
370
+
366
371
function EventHandlerProp ( ) {
367
372
return (
368
373
< div className = "foo" onClick = { function ( ) { } } >
@@ -382,6 +387,24 @@ describe('ReactFlight', () => {
382
387
return < div ref = { ref } /> ;
383
388
}
384
389
390
+ function EventHandlerPropClient ( ) {
391
+ return (
392
+ < Client className = "foo" onClick = { function ( ) { } } >
393
+ Test
394
+ </ Client >
395
+ ) ;
396
+ }
397
+ function FunctionPropClient ( ) {
398
+ return < Client > { ( ) => { } } </ Client > ;
399
+ }
400
+ function SymbolPropClient ( ) {
401
+ return < Client foo = { Symbol ( 'foo' ) } /> ;
402
+ }
403
+
404
+ function RefPropClient ( ) {
405
+ return < Client ref = { ref } /> ;
406
+ }
407
+
385
408
const options = {
386
409
onError ( x ) {
387
410
return __DEV__ ? 'a dev digest' : `digest("${ x . message } ")` ;
@@ -391,26 +414,51 @@ describe('ReactFlight', () => {
391
414
const fn = ReactNoopFlightServer . render ( < FunctionProp /> , options ) ;
392
415
const symbol = ReactNoopFlightServer . render ( < SymbolProp /> , options ) ;
393
416
const refs = ReactNoopFlightServer . render ( < RefProp /> , options ) ;
417
+ const eventClient = ReactNoopFlightServer . render (
418
+ < EventHandlerPropClient /> ,
419
+ options ,
420
+ ) ;
421
+ const fnClient = ReactNoopFlightServer . render (
422
+ < FunctionPropClient /> ,
423
+ options ,
424
+ ) ;
425
+ const symbolClient = ReactNoopFlightServer . render (
426
+ < SymbolPropClient /> ,
427
+ options ,
428
+ ) ;
429
+ const refsClient = ReactNoopFlightServer . render ( < RefPropClient /> , options ) ;
394
430
395
- function Client ( { promise} ) {
431
+ function Render ( { promise} ) {
396
432
return use ( promise ) ;
397
433
}
398
434
399
435
await act ( async ( ) => {
400
436
startTransition ( ( ) => {
401
437
ReactNoop . render (
402
438
< >
403
- < ErrorBoundary expectedMessage = "Event handlers cannot be passed to client component props." >
404
- < Client promise = { ReactNoopFlightClient . read ( event ) } />
439
+ < ErrorBoundary expectedMessage = "Event handlers cannot be passed to Client Component props." >
440
+ < Render promise = { ReactNoopFlightClient . read ( event ) } />
441
+ </ ErrorBoundary >
442
+ < ErrorBoundary expectedMessage = "Functions cannot be passed directly to Client Components because they're not serializable." >
443
+ < Render promise = { ReactNoopFlightClient . read ( fn ) } />
444
+ </ ErrorBoundary >
445
+ < ErrorBoundary expectedMessage = "Only global symbols received from Symbol.for(...) can be passed to Client Components." >
446
+ < Render promise = { ReactNoopFlightClient . read ( symbol ) } />
447
+ </ ErrorBoundary >
448
+ < ErrorBoundary expectedMessage = "Refs cannot be used in Server Components, nor passed to Client Components." >
449
+ < Render promise = { ReactNoopFlightClient . read ( refs ) } />
450
+ </ ErrorBoundary >
451
+ < ErrorBoundary expectedMessage = "Event handlers cannot be passed to Client Component props." >
452
+ < Render promise = { ReactNoopFlightClient . read ( eventClient ) } />
405
453
</ ErrorBoundary >
406
- < ErrorBoundary expectedMessage = "Functions cannot be passed directly to client components because they're not serializable." >
407
- < Client promise = { ReactNoopFlightClient . read ( fn ) } />
454
+ < ErrorBoundary expectedMessage = "Functions cannot be passed directly to Client Components because they're not serializable." >
455
+ < Render promise = { ReactNoopFlightClient . read ( fnClient ) } />
408
456
</ ErrorBoundary >
409
- < ErrorBoundary expectedMessage = "Only global symbols received from Symbol.for(...) can be passed to client components ." >
410
- < Client promise = { ReactNoopFlightClient . read ( symbol ) } />
457
+ < ErrorBoundary expectedMessage = "Only global symbols received from Symbol.for(...) can be passed to Client Components ." >
458
+ < Render promise = { ReactNoopFlightClient . read ( symbolClient ) } />
411
459
</ ErrorBoundary >
412
- < ErrorBoundary expectedMessage = "Refs cannot be used in server components , nor passed to client components ." >
413
- < Client promise = { ReactNoopFlightClient . read ( refs ) } />
460
+ < ErrorBoundary expectedMessage = "Refs cannot be used in Server Components , nor passed to Client Components ." >
461
+ < Render promise = { ReactNoopFlightClient . read ( refsClient ) } />
414
462
</ ErrorBoundary >
415
463
</ > ,
416
464
) ;
@@ -419,19 +467,19 @@ describe('ReactFlight', () => {
419
467
} ) ;
420
468
421
469
// @gate enableUseHook
422
- it ( 'should trigger the inner most error boundary inside a client component ' , async ( ) => {
470
+ it ( 'should trigger the inner most error boundary inside a Client Component ' , async ( ) => {
423
471
function ServerComponent ( ) {
424
- throw new Error ( 'This was thrown in the server component .' ) ;
472
+ throw new Error ( 'This was thrown in the Server Component .' ) ;
425
473
}
426
474
427
475
function ClientComponent ( { children} ) {
428
- // This should catch the error thrown by the server component , even though it has already happened.
476
+ // This should catch the error thrown by the Server Component , even though it has already happened.
429
477
// We currently need to wrap it in a div because as it's set up right now, a lazy reference will
430
478
// throw during reconciliation which will trigger the parent of the error boundary.
431
479
// This is similar to how these will suspend the parent if it's a direct child of a Suspense boundary.
432
480
// That's a bug.
433
481
return (
434
- < ErrorBoundary expectedMessage = "This was thrown in the server component ." >
482
+ < ErrorBoundary expectedMessage = "This was thrown in the Server Component ." >
435
483
< div > { children } </ div >
436
484
</ ErrorBoundary >
437
485
) ;
@@ -475,25 +523,37 @@ describe('ReactFlight', () => {
475
523
) ;
476
524
ReactNoopFlightClient . read ( transport ) ;
477
525
} ) . toErrorDev (
478
- 'Only plain objects can be passed to client components from server components. ' ,
526
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
527
+ 'Date objects are not supported.' ,
479
528
{ withoutStack : true } ,
480
529
) ;
481
530
} ) ;
482
531
483
- it ( 'should warn in DEV if a special object is passed to a host component' , ( ) => {
532
+ it ( 'should warn in DEV if a toJSON instance is passed to a host component child ' , ( ) => {
484
533
expect ( ( ) => {
485
- const transport = ReactNoopFlightServer . render ( < input value = { Math } /> ) ;
534
+ const transport = ReactNoopFlightServer . render (
535
+ < div > Current date: { new Date ( ) } </ div > ,
536
+ ) ;
486
537
ReactNoopFlightClient . read ( transport ) ;
487
538
} ) . toErrorDev (
488
- 'Only plain objects can be passed to client components from server components. ' +
489
- 'Built-ins like Math are not supported.' ,
539
+ 'Date objects cannot be rendered as text children. Try formatting it using toString().\n' +
540
+ ' <div>Current date: {Date}</div>\n' +
541
+ ' ^^^^^^' ,
490
542
{ withoutStack : true } ,
491
543
) ;
492
544
} ) ;
493
545
494
- it ( 'should NOT warn in DEV for key getters' , ( ) => {
495
- const transport = ReactNoopFlightServer . render ( < div key = "a" /> ) ;
496
- ReactNoopFlightClient . read ( transport ) ;
546
+ it ( 'should warn in DEV if a special object is passed to a host component' , ( ) => {
547
+ expect ( ( ) => {
548
+ const transport = ReactNoopFlightServer . render ( < input value = { Math } /> ) ;
549
+ ReactNoopFlightClient . read ( transport ) ;
550
+ } ) . toErrorDev (
551
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
552
+ 'Math objects are not supported.\n' +
553
+ ' <input value={Math}>\n' +
554
+ ' ^^^^^^' ,
555
+ { withoutStack : true } ,
556
+ ) ;
497
557
} ) ;
498
558
499
559
it ( 'should warn in DEV if an object with symbols is passed to a host component' , ( ) => {
@@ -503,12 +563,127 @@ describe('ReactFlight', () => {
503
563
) ;
504
564
ReactNoopFlightClient . read ( transport ) ;
505
565
} ) . toErrorDev (
506
- 'Only plain objects can be passed to client components from server components . ' +
566
+ 'Only plain objects can be passed to Client Components from Server Components . ' +
507
567
'Objects with symbol properties like Symbol.iterator are not supported.' ,
508
568
{ withoutStack : true } ,
509
569
) ;
510
570
} ) ;
511
571
572
+ it ( 'should warn in DEV if a toJSON instance is passed to a Client Component' , ( ) => {
573
+ function ClientImpl ( { value} ) {
574
+ return < div > { value } </ div > ;
575
+ }
576
+ const Client = moduleReference ( ClientImpl ) ;
577
+ expect ( ( ) => {
578
+ const transport = ReactNoopFlightServer . render (
579
+ < Client value = { new Date ( ) } /> ,
580
+ ) ;
581
+ ReactNoopFlightClient . read ( transport ) ;
582
+ } ) . toErrorDev (
583
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
584
+ 'Date objects are not supported.' ,
585
+ { withoutStack : true } ,
586
+ ) ;
587
+ } ) ;
588
+
589
+ it ( 'should warn in DEV if a toJSON instance is passed to a Client Component child' , ( ) => {
590
+ function ClientImpl ( { children} ) {
591
+ return < div > { children } </ div > ;
592
+ }
593
+ const Client = moduleReference ( ClientImpl ) ;
594
+ expect ( ( ) => {
595
+ const transport = ReactNoopFlightServer . render (
596
+ < Client > Current date: { new Date ( ) } </ Client > ,
597
+ ) ;
598
+ ReactNoopFlightClient . read ( transport ) ;
599
+ } ) . toErrorDev (
600
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
601
+ 'Date objects are not supported.\n' +
602
+ ' <>Current date: {Date}</>\n' +
603
+ ' ^^^^^^' ,
604
+ { withoutStack : true } ,
605
+ ) ;
606
+ } ) ;
607
+
608
+ it ( 'should warn in DEV if a special object is passed to a Client Component' , ( ) => {
609
+ function ClientImpl ( { value} ) {
610
+ return < div > { value } </ div > ;
611
+ }
612
+ const Client = moduleReference ( ClientImpl ) ;
613
+ expect ( ( ) => {
614
+ const transport = ReactNoopFlightServer . render ( < Client value = { Math } /> ) ;
615
+ ReactNoopFlightClient . read ( transport ) ;
616
+ } ) . toErrorDev (
617
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
618
+ 'Math objects are not supported.\n' +
619
+ ' <... value={Math}>\n' +
620
+ ' ^^^^^^' ,
621
+ { withoutStack : true } ,
622
+ ) ;
623
+ } ) ;
624
+
625
+ it ( 'should warn in DEV if an object with symbols is passed to a Client Component' , ( ) => {
626
+ function ClientImpl ( { value} ) {
627
+ return < div > { value } </ div > ;
628
+ }
629
+ const Client = moduleReference ( ClientImpl ) ;
630
+ expect ( ( ) => {
631
+ const transport = ReactNoopFlightServer . render (
632
+ < Client value = { { [ Symbol . iterator ] : { } } } /> ,
633
+ ) ;
634
+ ReactNoopFlightClient . read ( transport ) ;
635
+ } ) . toErrorDev (
636
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
637
+ 'Objects with symbol properties like Symbol.iterator are not supported.' ,
638
+ { withoutStack : true } ,
639
+ ) ;
640
+ } ) ;
641
+
642
+ it ( 'should warn in DEV if a special object is passed to a nested object in Client Component' , ( ) => {
643
+ function ClientImpl ( { value} ) {
644
+ return < div > { value } </ div > ;
645
+ }
646
+ const Client = moduleReference ( ClientImpl ) ;
647
+ expect ( ( ) => {
648
+ const transport = ReactNoopFlightServer . render (
649
+ < Client value = { { hello : Math , title : < h1 > hi</ h1 > } } /> ,
650
+ ) ;
651
+ ReactNoopFlightClient . read ( transport ) ;
652
+ } ) . toErrorDev (
653
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
654
+ 'Math objects are not supported.\n' +
655
+ ' {hello: Math, title: <h1/>}\n' +
656
+ ' ^^^^' ,
657
+ { withoutStack : true } ,
658
+ ) ;
659
+ } ) ;
660
+
661
+ it ( 'should warn in DEV if a special object is passed to a nested array in Client Component' , ( ) => {
662
+ function ClientImpl ( { value} ) {
663
+ return < div > { value } </ div > ;
664
+ }
665
+ const Client = moduleReference ( ClientImpl ) ;
666
+ expect ( ( ) => {
667
+ const transport = ReactNoopFlightServer . render (
668
+ < Client
669
+ value = { [ 'looooong string takes up noise' , Math , < h1 > hi</ h1 > ] }
670
+ /> ,
671
+ ) ;
672
+ ReactNoopFlightClient . read ( transport ) ;
673
+ } ) . toErrorDev (
674
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
675
+ 'Math objects are not supported.\n' +
676
+ ' [..., Math, <h1/>]\n' +
677
+ ' ^^^^' ,
678
+ { withoutStack : true } ,
679
+ ) ;
680
+ } ) ;
681
+
682
+ it ( 'should NOT warn in DEV for key getters' , ( ) => {
683
+ const transport = ReactNoopFlightServer . render ( < div key = "a" /> ) ;
684
+ ReactNoopFlightClient . read ( transport ) ;
685
+ } ) ;
686
+
512
687
it ( 'should warn in DEV if a class instance is passed to a host component' , ( ) => {
513
688
class Foo {
514
689
method ( ) { }
@@ -519,7 +694,7 @@ describe('ReactFlight', () => {
519
694
) ;
520
695
ReactNoopFlightClient . read ( transport ) ;
521
696
} ) . toErrorDev (
522
- 'Only plain objects can be passed to client components from server components . ' ,
697
+ 'Only plain objects can be passed to Client Components from Server Components . ' ,
523
698
{ withoutStack : true } ,
524
699
) ;
525
700
} ) ;
@@ -577,9 +752,9 @@ describe('ReactFlight', () => {
577
752
} ) ;
578
753
579
754
it ( '[TODO] it does not warn if you render a server element passed to a client module reference twice on the client when using useId' , async ( ) => {
580
- // @TODO Today if you render a server component with useId and pass it to a client component and that client component renders the element in two or more
755
+ // @TODO Today if you render a Server Component with useId and pass it to a Client Component and that Client Component renders the element in two or more
581
756
// places the id used on the server will be duplicated in the client. This is a deviation from the guarantees useId makes for Fizz/Client and is a consequence
582
- // of the fact that the server component is actually rendered on the server and is reduced to a set of host elements before being passed to the Client component
757
+ // of the fact that the Server Component is actually rendered on the server and is reduced to a set of host elements before being passed to the Client component
583
758
// so the output passed to the Client has no knowledge of the useId use. In the future we would like to add a DEV warning when this happens. For now
584
759
// we just accept that it is a nuance of useId in Flight
585
760
function App ( ) {
@@ -937,7 +1112,7 @@ describe('ReactFlight', () => {
937
1112
938
1113
expect ( ClientContext ) . toBe ( undefined ) ;
939
1114
940
- // Reset all modules, except flight-modules which keeps the registry of client components
1115
+ // Reset all modules, except flight-modules which keeps the registry of Client Components
941
1116
const flightModules = require ( 'react-noop-renderer/flight-modules' ) ;
942
1117
jest . resetModules ( ) ;
943
1118
jest . mock ( 'react-noop-renderer/flight-modules' , ( ) => flightModules ) ;
0 commit comments