@@ -25,6 +25,7 @@ let Suspense;
25
25
let SuspenseList ;
26
26
let useSyncExternalStore ;
27
27
let useSyncExternalStoreWithSelector ;
28
+ let use ;
28
29
let PropTypes ;
29
30
let textCache ;
30
31
let window ;
@@ -47,6 +48,7 @@ describe('ReactDOMFizzServer', () => {
47
48
ReactDOMFizzServer = require ( 'react-dom/server' ) ;
48
49
Stream = require ( 'stream' ) ;
49
50
Suspense = React . Suspense ;
51
+ use = React . use ;
50
52
if ( gate ( flags => flags . enableSuspenseList ) ) {
51
53
SuspenseList = React . SuspenseList ;
52
54
}
@@ -5166,6 +5168,216 @@ describe('ReactDOMFizzServer', () => {
5166
5168
console . error = originalConsoleError ;
5167
5169
}
5168
5170
} ) ;
5171
+
5172
+ // @gate enableUseHook
5173
+ it ( 'basic use(promise)' , async ( ) => {
5174
+ const promiseA = Promise . resolve ( 'A' ) ;
5175
+ const promiseB = Promise . resolve ( 'B' ) ;
5176
+ const promiseC = Promise . resolve ( 'C' ) ;
5177
+
5178
+ function Async ( ) {
5179
+ return use ( promiseA ) + use ( promiseB ) + use ( promiseC ) ;
5180
+ }
5181
+
5182
+ function App ( ) {
5183
+ return (
5184
+ < Suspense fallback = "Loading..." >
5185
+ < Async />
5186
+ </ Suspense >
5187
+ ) ;
5188
+ }
5189
+
5190
+ await act ( async ( ) => {
5191
+ const { pipe} = renderToPipeableStream ( < App /> ) ;
5192
+ pipe ( writable ) ;
5193
+ } ) ;
5194
+
5195
+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5196
+ // automatically. We can't use the same `act` we use for Fiber tests
5197
+ // because that relies on the mock Scheduler. Doesn't affect any public
5198
+ // API but we might want to fix this for our own internal tests.
5199
+ //
5200
+ // For now, wait for each promise in sequence.
5201
+ await act ( async ( ) => {
5202
+ await promiseA ;
5203
+ } ) ;
5204
+ await act ( async ( ) => {
5205
+ await promiseB ;
5206
+ } ) ;
5207
+ await act ( async ( ) => {
5208
+ await promiseC ;
5209
+ } ) ;
5210
+
5211
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABC' ) ;
5212
+
5213
+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5214
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5215
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABC' ) ;
5216
+ } ) ;
5217
+
5218
+ // @gate enableUseHook
5219
+ it ( 'use(promise) in multiple components' , async ( ) => {
5220
+ const promiseA = Promise . resolve ( 'A' ) ;
5221
+ const promiseB = Promise . resolve ( 'B' ) ;
5222
+ const promiseC = Promise . resolve ( 'C' ) ;
5223
+ const promiseD = Promise . resolve ( 'D' ) ;
5224
+
5225
+ function Child ( { prefix} ) {
5226
+ return prefix + use ( promiseC ) + use ( promiseD ) ;
5227
+ }
5228
+
5229
+ function Parent ( ) {
5230
+ return < Child prefix = { use ( promiseA ) + use ( promiseB ) } /> ;
5231
+ }
5232
+
5233
+ function App ( ) {
5234
+ return (
5235
+ < Suspense fallback = "Loading..." >
5236
+ < Parent />
5237
+ </ Suspense >
5238
+ ) ;
5239
+ }
5240
+
5241
+ await act ( async ( ) => {
5242
+ const { pipe} = renderToPipeableStream ( < App /> ) ;
5243
+ pipe ( writable ) ;
5244
+ } ) ;
5245
+
5246
+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5247
+ // automatically. We can't use the same `act` we use for Fiber tests
5248
+ // because that relies on the mock Scheduler. Doesn't affect any public
5249
+ // API but we might want to fix this for our own internal tests.
5250
+ //
5251
+ // For now, wait for each promise in sequence.
5252
+ await act ( async ( ) => {
5253
+ await promiseA ;
5254
+ } ) ;
5255
+ await act ( async ( ) => {
5256
+ await promiseB ;
5257
+ } ) ;
5258
+ await act ( async ( ) => {
5259
+ await promiseC ;
5260
+ } ) ;
5261
+ await act ( async ( ) => {
5262
+ await promiseD ;
5263
+ } ) ;
5264
+
5265
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABCD' ) ;
5266
+
5267
+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5268
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5269
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'ABCD' ) ;
5270
+ } ) ;
5271
+
5272
+ // @gate enableUseHook
5273
+ it ( 'using a rejected promise will throw' , async ( ) => {
5274
+ const promiseA = Promise . resolve ( 'A' ) ;
5275
+ const promiseB = Promise . reject ( new Error ( 'Oops!' ) ) ;
5276
+ const promiseC = Promise . resolve ( 'C' ) ;
5277
+
5278
+ // Jest/Node will raise an unhandled rejected error unless we await this. It
5279
+ // works fine in the browser, though.
5280
+ await expect ( promiseB ) . rejects . toThrow ( 'Oops!' ) ;
5281
+
5282
+ function Async ( ) {
5283
+ return use ( promiseA ) + use ( promiseB ) + use ( promiseC ) ;
5284
+ }
5285
+
5286
+ class ErrorBoundary extends React . Component {
5287
+ state = { error : null } ;
5288
+ static getDerivedStateFromError ( error ) {
5289
+ return { error} ;
5290
+ }
5291
+ render ( ) {
5292
+ if ( this . state . error ) {
5293
+ return this . state . error . message ;
5294
+ }
5295
+ return this . props . children ;
5296
+ }
5297
+ }
5298
+
5299
+ function App ( ) {
5300
+ return (
5301
+ < Suspense fallback = "Loading..." >
5302
+ < ErrorBoundary >
5303
+ < Async />
5304
+ </ ErrorBoundary >
5305
+ </ Suspense >
5306
+ ) ;
5307
+ }
5308
+
5309
+ const reportedServerErrors = [ ] ;
5310
+ await act ( async ( ) => {
5311
+ const { pipe} = renderToPipeableStream ( < App /> , {
5312
+ onError ( error ) {
5313
+ reportedServerErrors . push ( error ) ;
5314
+ } ,
5315
+ } ) ;
5316
+ pipe ( writable ) ;
5317
+ } ) ;
5318
+
5319
+ // TODO: The `act` implementation in this file doesn't unwrap microtasks
5320
+ // automatically. We can't use the same `act` we use for Fiber tests
5321
+ // because that relies on the mock Scheduler. Doesn't affect any public
5322
+ // API but we might want to fix this for our own internal tests.
5323
+ //
5324
+ // For now, wait for each promise in sequence.
5325
+ await act ( async ( ) => {
5326
+ await promiseA ;
5327
+ } ) ;
5328
+ await act ( async ( ) => {
5329
+ await expect ( promiseB ) . rejects . toThrow ( 'Oops!' ) ;
5330
+ } ) ;
5331
+ await act ( async ( ) => {
5332
+ await promiseC ;
5333
+ } ) ;
5334
+
5335
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Loading...' ) ;
5336
+ expect ( reportedServerErrors . length ) . toBe ( 1 ) ;
5337
+ expect ( reportedServerErrors [ 0 ] . message ) . toBe ( 'Oops!' ) ;
5338
+
5339
+ const reportedClientErrors = [ ] ;
5340
+ ReactDOMClient . hydrateRoot ( container , < App /> , {
5341
+ onRecoverableError ( error ) {
5342
+ reportedClientErrors . push ( error ) ;
5343
+ } ,
5344
+ } ) ;
5345
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5346
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Oops!' ) ;
5347
+ expect ( reportedClientErrors . length ) . toBe ( 1 ) ;
5348
+ if ( __DEV__ ) {
5349
+ expect ( reportedClientErrors [ 0 ] . message ) . toBe ( 'Oops!' ) ;
5350
+ } else {
5351
+ expect ( reportedClientErrors [ 0 ] . message ) . toBe (
5352
+ 'The server could not finish this Suspense boundary, likely due to ' +
5353
+ 'an error during server rendering. Switched to client rendering.' ,
5354
+ ) ;
5355
+ }
5356
+ } ) ;
5357
+
5358
+ // @gate enableUseHook
5359
+ it ( "use a promise that's already been instrumented and resolved" , async ( ) => {
5360
+ const thenable = {
5361
+ status : 'fulfilled' ,
5362
+ value : 'Hi' ,
5363
+ then ( ) { } ,
5364
+ } ;
5365
+
5366
+ // This will never suspend because the thenable already resolved
5367
+ function App ( ) {
5368
+ return use ( thenable ) ;
5369
+ }
5370
+
5371
+ await act ( async ( ) => {
5372
+ const { pipe} = renderToPipeableStream ( < App /> ) ;
5373
+ pipe ( writable ) ;
5374
+ } ) ;
5375
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Hi' ) ;
5376
+
5377
+ ReactDOMClient . hydrateRoot ( container , < App /> ) ;
5378
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
5379
+ expect ( getVisibleChildren ( container ) ) . toEqual ( 'Hi' ) ;
5380
+ } ) ;
5169
5381
} ) ;
5170
5382
5171
5383
describe ( 'useEvent' , ( ) => {
0 commit comments