@@ -95,7 +95,7 @@ describe('ReactFlight', () => {
9595 } ;
9696 }
9797
98- it ( 'can render a server component ' , async ( ) => {
98+ it ( 'can render a Server Component ' , async ( ) => {
9999 function Bar ( { text} ) {
100100 return text . toUpperCase ( ) ;
101101 }
@@ -125,7 +125,7 @@ describe('ReactFlight', () => {
125125 } ) ;
126126 } ) ;
127127
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 ( ) => {
129129 function UserClient ( props ) {
130130 return (
131131 < span >
@@ -363,6 +363,11 @@ describe('ReactFlight', () => {
363363
364364 // @gate enableUseHook
365365 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+
366371 function EventHandlerProp ( ) {
367372 return (
368373 < div className = "foo" onClick = { function ( ) { } } >
@@ -382,6 +387,24 @@ describe('ReactFlight', () => {
382387 return < div ref = { ref } /> ;
383388 }
384389
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+
385408 const options = {
386409 onError ( x ) {
387410 return __DEV__ ? 'a dev digest' : `digest("${ x . message } ")` ;
@@ -391,26 +414,51 @@ describe('ReactFlight', () => {
391414 const fn = ReactNoopFlightServer . render ( < FunctionProp /> , options ) ;
392415 const symbol = ReactNoopFlightServer . render ( < SymbolProp /> , options ) ;
393416 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 ) ;
394430
395- function Client ( { promise} ) {
431+ function Render ( { promise} ) {
396432 return use ( promise ) ;
397433 }
398434
399435 await act ( async ( ) => {
400436 startTransition ( ( ) => {
401437 ReactNoop . render (
402438 < >
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 ) } />
405453 </ 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 ) } />
408456 </ 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 ) } />
411459 </ 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 ) } />
414462 </ ErrorBoundary >
415463 </ > ,
416464 ) ;
@@ -419,19 +467,19 @@ describe('ReactFlight', () => {
419467 } ) ;
420468
421469 // @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 ( ) => {
423471 function ServerComponent ( ) {
424- throw new Error ( 'This was thrown in the server component .' ) ;
472+ throw new Error ( 'This was thrown in the Server Component .' ) ;
425473 }
426474
427475 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.
429477 // We currently need to wrap it in a div because as it's set up right now, a lazy reference will
430478 // throw during reconciliation which will trigger the parent of the error boundary.
431479 // This is similar to how these will suspend the parent if it's a direct child of a Suspense boundary.
432480 // That's a bug.
433481 return (
434- < ErrorBoundary expectedMessage = "This was thrown in the server component ." >
482+ < ErrorBoundary expectedMessage = "This was thrown in the Server Component ." >
435483 < div > { children } </ div >
436484 </ ErrorBoundary >
437485 ) ;
@@ -475,25 +523,37 @@ describe('ReactFlight', () => {
475523 ) ;
476524 ReactNoopFlightClient . read ( transport ) ;
477525 } ) . 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.' ,
479528 { withoutStack : true } ,
480529 ) ;
481530 } ) ;
482531
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 ' , ( ) => {
484533 expect ( ( ) => {
485- const transport = ReactNoopFlightServer . render ( < input value = { Math } /> ) ;
534+ const transport = ReactNoopFlightServer . render (
535+ < div > Current date: { new Date ( ) } </ div > ,
536+ ) ;
486537 ReactNoopFlightClient . read ( transport ) ;
487538 } ) . 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+ ' ^^^^^^' ,
490542 { withoutStack : true } ,
491543 ) ;
492544 } ) ;
493545
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+ ) ;
497557 } ) ;
498558
499559 it ( 'should warn in DEV if an object with symbols is passed to a host component' , ( ) => {
@@ -503,12 +563,127 @@ describe('ReactFlight', () => {
503563 ) ;
504564 ReactNoopFlightClient . read ( transport ) ;
505565 } ) . 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 . ' +
507567 'Objects with symbol properties like Symbol.iterator are not supported.' ,
508568 { withoutStack : true } ,
509569 ) ;
510570 } ) ;
511571
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+
512687 it ( 'should warn in DEV if a class instance is passed to a host component' , ( ) => {
513688 class Foo {
514689 method ( ) { }
@@ -519,7 +694,7 @@ describe('ReactFlight', () => {
519694 ) ;
520695 ReactNoopFlightClient . read ( transport ) ;
521696 } ) . 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 . ' ,
523698 { withoutStack : true } ,
524699 ) ;
525700 } ) ;
@@ -577,9 +752,9 @@ describe('ReactFlight', () => {
577752 } ) ;
578753
579754 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
581756 // 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
583758 // 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
584759 // we just accept that it is a nuance of useId in Flight
585760 function App ( ) {
@@ -937,7 +1112,7 @@ describe('ReactFlight', () => {
9371112
9381113 expect ( ClientContext ) . toBe ( undefined ) ;
9391114
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
9411116 const flightModules = require ( 'react-noop-renderer/flight-modules' ) ;
9421117 jest . resetModules ( ) ;
9431118 jest . mock ( 'react-noop-renderer/flight-modules' , ( ) => flightModules ) ;
0 commit comments