@@ -2,6 +2,9 @@ let React;
2
2
let ReactFeatureFlags ;
3
3
let ReactNoop ;
4
4
let Scheduler ;
5
+ let ReactCache ;
6
+ let Suspense ;
7
+ let TextResource ;
5
8
6
9
describe ( 'ReactBatchedMode' , ( ) => {
7
10
beforeEach ( ( ) => {
@@ -12,13 +15,40 @@ describe('ReactBatchedMode', () => {
12
15
React = require ( 'react' ) ;
13
16
ReactNoop = require ( 'react-noop-renderer' ) ;
14
17
Scheduler = require ( 'scheduler' ) ;
18
+ ReactCache = require ( 'react-cache' ) ;
19
+ Suspense = React . Suspense ;
20
+
21
+ TextResource = ReactCache . unstable_createResource ( ( [ text , ms = 0 ] ) => {
22
+ return new Promise ( ( resolve , reject ) =>
23
+ setTimeout ( ( ) => {
24
+ Scheduler . yieldValue ( `Promise resolved [${ text } ]` ) ;
25
+ resolve ( text ) ;
26
+ } , ms ) ,
27
+ ) ;
28
+ } , ( [ text , ms ] ) => text ) ;
15
29
} ) ;
16
30
17
31
function Text ( props ) {
18
32
Scheduler . yieldValue ( props . text ) ;
19
33
return props . text ;
20
34
}
21
35
36
+ function AsyncText ( props ) {
37
+ const text = props . text ;
38
+ try {
39
+ TextResource . read ( [ props . text , props . ms ] ) ;
40
+ Scheduler . yieldValue ( text ) ;
41
+ return props . text ;
42
+ } catch ( promise ) {
43
+ if ( typeof promise . then === 'function' ) {
44
+ Scheduler . yieldValue ( `Suspend! [${ text } ]` ) ;
45
+ } else {
46
+ Scheduler . yieldValue ( `Error! [${ text } ]` ) ;
47
+ }
48
+ throw promise ;
49
+ }
50
+ }
51
+
22
52
it ( 'updates flush without yielding in the next event' , ( ) => {
23
53
const root = ReactNoop . createSyncRoot ( ) ;
24
54
@@ -55,4 +85,38 @@ describe('ReactBatchedMode', () => {
55
85
expect ( Scheduler ) . toFlushExpired ( [ 'Hi' , 'Layout effect' ] ) ;
56
86
expect ( root ) . toMatchRenderedOutput ( 'Hi' ) ;
57
87
} ) ;
88
+
89
+ it ( 'uses proper Suspense semantics, not legacy ones' , async ( ) => {
90
+ const root = ReactNoop . createSyncRoot ( ) ;
91
+ root . render (
92
+ < Suspense fallback = { < Text text = "Loading..." /> } >
93
+ < span >
94
+ < Text text = "A" />
95
+ </ span >
96
+ < span >
97
+ < AsyncText text = "B" />
98
+ </ span >
99
+ < span >
100
+ < Text text = "C" />
101
+ </ span >
102
+ </ Suspense > ,
103
+ ) ;
104
+
105
+ expect ( Scheduler ) . toFlushExpired ( [ 'A' , 'Suspend! [B]' , 'C' , 'Loading...' ] ) ;
106
+ // In Legacy Mode, A and B would mount in a hidden primary tree. In Batched
107
+ // and Concurrent Mode, nothing in the primary tree should mount. But the
108
+ // fallback should mount immediately.
109
+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
110
+
111
+ await jest . advanceTimersByTime ( 1000 ) ;
112
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [B]' ] ) ;
113
+ expect ( Scheduler ) . toFlushExpired ( [ 'A' , 'B' , 'C' ] ) ;
114
+ expect ( root ) . toMatchRenderedOutput (
115
+ < React . Fragment >
116
+ < span > A</ span >
117
+ < span > B</ span >
118
+ < span > C</ span >
119
+ </ React . Fragment > ,
120
+ ) ;
121
+ } ) ;
58
122
} ) ;
0 commit comments