@@ -24,6 +24,7 @@ let ReactDOMServer;
24
24
let ReactServerDOMWriter ;
25
25
let ReactServerDOMReader ;
26
26
let Suspense ;
27
+ let use ;
27
28
28
29
describe ( 'ReactFlightDOMBrowser' , ( ) => {
29
30
beforeEach ( ( ) => {
@@ -39,6 +40,7 @@ describe('ReactFlightDOMBrowser', () => {
39
40
ReactServerDOMWriter = require ( 'react-server-dom-webpack/writer.browser.server' ) ;
40
41
ReactServerDOMReader = require ( 'react-server-dom-webpack' ) ;
41
42
Suspense = React . Suspense ;
43
+ use = React . experimental_use ;
42
44
} ) ;
43
45
44
46
async function waitForSuspense ( fn ) {
@@ -562,4 +564,150 @@ describe('ReactFlightDOMBrowser', () => {
562
564
563
565
expect ( reportedErrors ) . toEqual ( [ 'for reasons' ] ) ;
564
566
} ) ;
567
+
568
+ // @gate enableUseHook
569
+ it ( 'basic use(promise)' , async ( ) => {
570
+ function Server ( ) {
571
+ return (
572
+ use ( Promise . resolve ( 'A' ) ) +
573
+ use ( Promise . resolve ( 'B' ) ) +
574
+ use ( Promise . resolve ( 'C' ) )
575
+ ) ;
576
+ }
577
+
578
+ const stream = ReactServerDOMWriter . renderToReadableStream ( < Server /> ) ;
579
+ const response = ReactServerDOMReader . createFromReadableStream ( stream ) ;
580
+
581
+ function Client ( ) {
582
+ return response . readRoot ( ) ;
583
+ }
584
+
585
+ const container = document . createElement ( 'div' ) ;
586
+ const root = ReactDOMClient . createRoot ( container ) ;
587
+ await act ( async ( ) => {
588
+ root . render (
589
+ < Suspense fallback = "Loading..." >
590
+ < Client />
591
+ </ Suspense > ,
592
+ ) ;
593
+ } ) ;
594
+ expect ( container . innerHTML ) . toBe ( 'ABC' ) ;
595
+ } ) ;
596
+
597
+ // @gate enableUseHook
598
+ it ( 'use(promise) in multiple components' , async ( ) => {
599
+ function Child ( { prefix} ) {
600
+ return prefix + use ( Promise . resolve ( 'C' ) ) + use ( Promise . resolve ( 'D' ) ) ;
601
+ }
602
+
603
+ function Parent ( ) {
604
+ return (
605
+ < Child prefix = { use ( Promise . resolve ( 'A' ) ) + use ( Promise . resolve ( 'B' ) ) } />
606
+ ) ;
607
+ }
608
+
609
+ const stream = ReactServerDOMWriter . renderToReadableStream ( < Parent /> ) ;
610
+ const response = ReactServerDOMReader . createFromReadableStream ( stream ) ;
611
+
612
+ function Client ( ) {
613
+ return response . readRoot ( ) ;
614
+ }
615
+
616
+ const container = document . createElement ( 'div' ) ;
617
+ const root = ReactDOMClient . createRoot ( container ) ;
618
+ await act ( async ( ) => {
619
+ root . render (
620
+ < Suspense fallback = "Loading..." >
621
+ < Client />
622
+ </ Suspense > ,
623
+ ) ;
624
+ } ) ;
625
+ expect ( container . innerHTML ) . toBe ( 'ABCD' ) ;
626
+ } ) ;
627
+
628
+ // @gate enableUseHook
629
+ it ( 'using a rejected promise will throw' , async ( ) => {
630
+ const promiseA = Promise . resolve ( 'A' ) ;
631
+ const promiseB = Promise . reject ( new Error ( 'Oops!' ) ) ;
632
+ const promiseC = Promise . resolve ( 'C' ) ;
633
+
634
+ // Jest/Node will raise an unhandled rejected error unless we await this. It
635
+ // works fine in the browser, though.
636
+ await expect ( promiseB ) . rejects . toThrow ( 'Oops!' ) ;
637
+
638
+ // This will never suspend because the thenable already resolved
639
+ function Server ( ) {
640
+ return use ( promiseA ) + use ( promiseB ) + use ( promiseC ) ;
641
+ }
642
+
643
+ const reportedErrors = [ ] ;
644
+ const stream = ReactServerDOMWriter . renderToReadableStream (
645
+ < Server /> ,
646
+ webpackMap ,
647
+ {
648
+ onError ( x ) {
649
+ reportedErrors . push ( x ) ;
650
+ } ,
651
+ } ,
652
+ ) ;
653
+ const response = ReactServerDOMReader . createFromReadableStream ( stream ) ;
654
+
655
+ class ErrorBoundary extends React . Component {
656
+ state = { error : null } ;
657
+ static getDerivedStateFromError ( error ) {
658
+ return { error} ;
659
+ }
660
+ render ( ) {
661
+ if ( this . state . error ) {
662
+ return this . state . error . message ;
663
+ }
664
+ return this . props . children ;
665
+ }
666
+ }
667
+
668
+ function Client ( ) {
669
+ return response . readRoot ( ) ;
670
+ }
671
+
672
+ const container = document . createElement ( 'div' ) ;
673
+ const root = ReactDOMClient . createRoot ( container ) ;
674
+ await act ( async ( ) => {
675
+ root . render (
676
+ < ErrorBoundary >
677
+ < Client />
678
+ </ ErrorBoundary > ,
679
+ ) ;
680
+ } ) ;
681
+ expect ( container . innerHTML ) . toBe ( 'Oops!' ) ;
682
+ expect ( reportedErrors . length ) . toBe ( 1 ) ;
683
+ expect ( reportedErrors [ 0 ] . message ) . toBe ( 'Oops!' ) ;
684
+ } ) ;
685
+
686
+ // @gate enableUseHook
687
+ it ( "use a promise that's already been instrumented and resolved" , async ( ) => {
688
+ const thenable = {
689
+ status : 'fulfilled' ,
690
+ value : 'Hi' ,
691
+ then ( ) { } ,
692
+ } ;
693
+
694
+ // This will never suspend because the thenable already resolved
695
+ function Server ( ) {
696
+ return use ( thenable ) ;
697
+ }
698
+
699
+ const stream = ReactServerDOMWriter . renderToReadableStream ( < Server /> ) ;
700
+ const response = ReactServerDOMReader . createFromReadableStream ( stream ) ;
701
+
702
+ function Client ( ) {
703
+ return response . readRoot ( ) ;
704
+ }
705
+
706
+ const container = document . createElement ( 'div' ) ;
707
+ const root = ReactDOMClient . createRoot ( container ) ;
708
+ await act ( async ( ) => {
709
+ root . render ( < Client /> ) ;
710
+ } ) ;
711
+ expect ( container . innerHTML ) . toBe ( 'Hi' ) ;
712
+ } ) ;
565
713
} ) ;
0 commit comments