@@ -19,6 +19,7 @@ let Suspense;
19
19
let SuspenseList ;
20
20
let useSyncExternalStore ;
21
21
let useSyncExternalStoreWithSelector ;
22
+ let use ;
22
23
let PropTypes ;
23
24
let textCache ;
24
25
let window ;
@@ -42,6 +43,7 @@ describe('ReactDOMFizzServer', () => {
42
43
Suspense = React . Suspense ;
43
44
if ( gate ( flags => flags . enableSuspenseList ) ) {
44
45
SuspenseList = React . SuspenseList ;
46
+ use = React . experimental_use ;
45
47
}
46
48
47
49
PropTypes = require ( 'prop-types' ) ;
@@ -5243,5 +5245,215 @@ describe('ReactDOMFizzServer', () => {
5243
5245
console . error = originalConsoleError ;
5244
5246
}
5245
5247
} ) ;
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
+ } ) ;
5246
5458
} ) ;
5247
5459
} ) ;
0 commit comments