@@ -19,6 +19,7 @@ let Suspense;
1919let SuspenseList ;
2020let useSyncExternalStore ;
2121let useSyncExternalStoreWithSelector ;
22+ let use ;
2223let PropTypes ;
2324let textCache ;
2425let window ;
@@ -42,6 +43,7 @@ describe('ReactDOMFizzServer', () => {
4243 Suspense = React . Suspense ;
4344 if ( gate ( flags => flags . enableSuspenseList ) ) {
4445 SuspenseList = React . SuspenseList ;
46+ use = React . experimental_use ;
4547 }
4648
4749 PropTypes = require ( 'prop-types' ) ;
@@ -5243,5 +5245,215 @@ describe('ReactDOMFizzServer', () => {
52435245 console . error = originalConsoleError ;
52445246 }
52455247 } ) ;
5248+
5249+ // @gate enableUseHook
5250+ it ( 'basic use(promise)' , async ( ) => {
5251+ const promiseA = Promise . resolve ( 'A' ) ;
5252+ const promiseB = Promise . resolve ( 'B' ) ;
5253+ const promiseC = Promise . resolve ( 'C' ) ;
5254+
5255+ function Async ( ) {
5256+ return use ( promiseA ) + use ( promiseB ) + use ( promiseC ) ;
5257+ }
5258+
5259+ function App ( ) {
5260+ return (
5261+ < Suspense fallback = "Loading..." >
5262+ < Async />
5263+ </ Suspense >
5264+ ) ;
5265+ }
5266+
5267+ await act ( async ( ) => {
5268+ const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> ) ;
5269+ pipe ( writable ) ;
5270+ } ) ;
5271+
5272+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5273+ // automatically. We can't use the same `act` we use for Fiber tests
5274+ // because that relies on the mock Scheduler. Doesn't affect any public
5275+ // API but we might want to fix this for our own internal tests.
5276+ //
5277+ // For now, wait for each promise in sequence.
5278+ await act ( async ( ) => {
5279+ await promiseA ;
5280+ } ) ;
5281+ await act ( async ( ) => {
5282+ await promiseB ;
5283+ } ) ;
5284+ await act ( async ( ) => {
5285+ await promiseC ;
5286+ } ) ;
5287+
5288+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABC' ) ;
5289+
5290+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5291+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5292+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABC' ) ;
5293+ } ) ;
5294+
5295+ // @gate enableUseHook
5296+ it ( 'use(promise) in multiple components' , async ( ) => {
5297+ const promiseA = Promise . resolve ( 'A' ) ;
5298+ const promiseB = Promise . resolve ( 'B' ) ;
5299+ const promiseC = Promise . resolve ( 'C' ) ;
5300+ const promiseD = Promise . resolve ( 'D' ) ;
5301+
5302+ function Child ( { prefix} ) {
5303+ return prefix + use ( promiseC ) + use ( promiseD ) ;
5304+ }
5305+
5306+ function Parent ( ) {
5307+ return < Child prefix = { use ( promiseA ) + use ( promiseB ) } /> ;
5308+ }
5309+
5310+ function App ( ) {
5311+ return (
5312+ < Suspense fallback = "Loading..." >
5313+ < Parent />
5314+ </ Suspense >
5315+ ) ;
5316+ }
5317+
5318+ await act ( async ( ) => {
5319+ const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> ) ;
5320+ pipe ( writable ) ;
5321+ } ) ;
5322+
5323+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5324+ // automatically. We can't use the same `act` we use for Fiber tests
5325+ // because that relies on the mock Scheduler. Doesn't affect any public
5326+ // API but we might want to fix this for our own internal tests.
5327+ //
5328+ // For now, wait for each promise in sequence.
5329+ await act ( async ( ) => {
5330+ await promiseA ;
5331+ } ) ;
5332+ await act ( async ( ) => {
5333+ await promiseB ;
5334+ } ) ;
5335+ await act ( async ( ) => {
5336+ await promiseC ;
5337+ } ) ;
5338+ await act ( async ( ) => {
5339+ await promiseD ;
5340+ } ) ;
5341+
5342+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABCD' ) ;
5343+
5344+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5345+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5346+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABCD' ) ;
5347+ } ) ;
5348+
5349+ // @gate enableUseHook
5350+ it ( 'using a rejected promise will throw' , async ( ) => {
5351+ const promiseA = Promise . resolve ( 'A' ) ;
5352+ const promiseB = Promise . reject ( new Error ( 'Oops!' ) ) ;
5353+ const promiseC = Promise . resolve ( 'C' ) ;
5354+
5355+ // Jest/Node will raise an unhandled rejected error unless we await this. It
5356+ // works fine in the browser, though.
5357+ await expect ( promiseB ) . rejects . toThrow ( 'Oops!' ) ;
5358+
5359+ function Async ( ) {
5360+ return use ( promiseA ) + use ( promiseB ) + use ( promiseC ) ;
5361+ }
5362+
5363+ class ErrorBoundary extends React . Component {
5364+ state = { error : null } ;
5365+ static getDerivedStateFromError ( error ) {
5366+ return { error} ;
5367+ }
5368+ render ( ) {
5369+ if ( this . state . error ) {
5370+ return this . state . error . message ;
5371+ }
5372+ return this . props . children ;
5373+ }
5374+ }
5375+
5376+ function App ( ) {
5377+ return (
5378+ < Suspense fallback = "Loading..." >
5379+ < ErrorBoundary >
5380+ < Async />
5381+ </ ErrorBoundary >
5382+ </ Suspense >
5383+ ) ;
5384+ }
5385+
5386+ const reportedServerErrors = [ ] ;
5387+ await act ( async ( ) => {
5388+ const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> , {
5389+ onError ( error ) {
5390+ reportedServerErrors . push ( error ) ;
5391+ } ,
5392+ } ) ;
5393+ pipe ( writable ) ;
5394+ } ) ;
5395+
5396+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5397+ // automatically. We can't use the same `act` we use for Fiber tests
5398+ // because that relies on the mock Scheduler. Doesn't affect any public
5399+ // API but we might want to fix this for our own internal tests.
5400+ //
5401+ // For now, wait for each promise in sequence.
5402+ await act ( async ( ) => {
5403+ await promiseA ;
5404+ } ) ;
5405+ await act ( async ( ) => {
5406+ await expect ( promiseB ) . rejects . toThrow ( 'Oops!' ) ;
5407+ } ) ;
5408+ await act ( async ( ) => {
5409+ await promiseC ;
5410+ } ) ;
5411+
5412+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Loading...' ) ;
5413+ expect ( reportedServerErrors . length ) . toBe ( 1 ) ;
5414+ expect ( reportedServerErrors [ 0 ] . message ) . toBe ( 'Oops!' ) ;
5415+
5416+ const reportedClientErrors = [ ] ;
5417+ ReactDOMClient . hydrateRoot ( container , < App /> , {
5418+ onRecoverableError ( error ) {
5419+ reportedClientErrors . push ( error ) ;
5420+ } ,
5421+ } ) ;
5422+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5423+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Oops!' ) ;
5424+ expect ( reportedClientErrors . length ) . toBe ( 1 ) ;
5425+ if ( __DEV__ ) {
5426+ expect ( reportedClientErrors [ 0 ] . message ) . toBe ( 'Oops!' ) ;
5427+ } else {
5428+ expect ( reportedClientErrors [ 0 ] . message ) . toBe (
5429+ 'The server could not finish this Suspense boundary, likely due to ' +
5430+ 'an error during server rendering. Switched to client rendering.' ,
5431+ ) ;
5432+ }
5433+ } ) ;
5434+
5435+ // @gate enableUseHook
5436+ it ( "use a promise that's already been instrumented and resolved" , async ( ) => {
5437+ const thenable = {
5438+ status : 'fulfilled' ,
5439+ value : 'Hi' ,
5440+ then ( ) { } ,
5441+ } ;
5442+
5443+ // This will never suspend because the thenable already resolved
5444+ function App ( ) {
5445+ return use ( thenable ) ;
5446+ }
5447+
5448+ await act ( async ( ) => {
5449+ const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> ) ;
5450+ pipe ( writable ) ;
5451+ } ) ;
5452+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Hi' ) ;
5453+
5454+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5455+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5456+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Hi' ) ;
5457+ } ) ;
52465458 } ) ;
52475459} ) ;
0 commit comments