12
12
let React ;
13
13
let ReactDOM ;
14
14
let Suspense ;
15
- let ReactCache ;
16
15
let Scheduler ;
17
- let TextResource ;
18
16
let act ;
17
+ let textCache ;
19
18
20
19
describe ( 'ReactDOMSuspensePlaceholder' , ( ) => {
21
20
let container ;
@@ -24,48 +23,78 @@ describe('ReactDOMSuspensePlaceholder', () => {
24
23
jest . resetModules ( ) ;
25
24
React = require ( 'react' ) ;
26
25
ReactDOM = require ( 'react-dom' ) ;
27
- ReactCache = require ( 'react-cache' ) ;
28
26
Scheduler = require ( 'scheduler' ) ;
29
27
act = require ( 'internal-test-utils' ) . act ;
30
28
Suspense = React . Suspense ;
31
29
container = document . createElement ( 'div' ) ;
32
30
document . body . appendChild ( container ) ;
33
31
34
- TextResource = ReactCache . unstable_createResource (
35
- ( [ text , ms = 0 ] ) => {
36
- return new Promise ( ( resolve , reject ) =>
37
- setTimeout ( ( ) => {
38
- resolve ( text ) ;
39
- } , ms ) ,
40
- ) ;
41
- } ,
42
- ( [ text , ms ] ) => text ,
43
- ) ;
32
+ textCache = new Map ( ) ;
44
33
} ) ;
45
34
46
35
afterEach ( ( ) => {
47
36
document . body . removeChild ( container ) ;
48
37
} ) ;
49
38
50
- function advanceTimers ( ms ) {
51
- // Note: This advances Jest's virtual time but not React's. Use
52
- // ReactNoop.expire for that.
53
- if ( typeof ms !== 'number' ) {
54
- throw new Error ( 'Must specify ms' ) ;
39
+ function resolveText ( text ) {
40
+ const record = textCache . get ( text ) ;
41
+ if ( record === undefined ) {
42
+ const newRecord = {
43
+ status : 'resolved' ,
44
+ value : text ,
45
+ } ;
46
+ textCache . set ( text , newRecord ) ;
47
+ } else if ( record . status === 'pending' ) {
48
+ const thenable = record . value ;
49
+ record . status = 'resolved' ;
50
+ record . value = text ;
51
+ thenable . pings . forEach ( t => t ( ) ) ;
52
+ }
53
+ }
54
+
55
+ function readText ( text ) {
56
+ const record = textCache . get ( text ) ;
57
+ if ( record !== undefined ) {
58
+ switch ( record . status ) {
59
+ case 'pending' :
60
+ Scheduler . log ( `Suspend! [${ text } ]` ) ;
61
+ throw record . value ;
62
+ case 'rejected' :
63
+ throw record . value ;
64
+ case 'resolved' :
65
+ return record . value ;
66
+ }
67
+ } else {
68
+ Scheduler . log ( `Suspend! [${ text } ]` ) ;
69
+ const thenable = {
70
+ pings : [ ] ,
71
+ then ( resolve ) {
72
+ if ( newRecord . status === 'pending' ) {
73
+ thenable . pings . push ( resolve ) ;
74
+ } else {
75
+ Promise . resolve ( ) . then ( ( ) => resolve ( newRecord . value ) ) ;
76
+ }
77
+ } ,
78
+ } ;
79
+
80
+ const newRecord = {
81
+ status : 'pending' ,
82
+ value : thenable ,
83
+ } ;
84
+ textCache . set ( text , newRecord ) ;
85
+
86
+ throw thenable ;
55
87
}
56
- jest . advanceTimersByTime ( ms ) ;
57
- // Wait until the end of the current tick
58
- // We cannot use a timer since we're faking them
59
- return Promise . resolve ( ) . then ( ( ) => { } ) ;
60
88
}
61
89
62
- function Text ( props ) {
63
- return props . text ;
90
+ function Text ( { text} ) {
91
+ Scheduler . log ( text ) ;
92
+ return text ;
64
93
}
65
94
66
- function AsyncText ( props ) {
67
- const text = props . text ;
68
- TextResource . read ( [ props . text , props . ms ] ) ;
95
+ function AsyncText ( { text } ) {
96
+ readText ( text ) ;
97
+ Scheduler . log ( text ) ;
69
98
return text ;
70
99
}
71
100
@@ -82,7 +111,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
82
111
< Text text = "A" />
83
112
</ div >
84
113
< div ref = { divs [ 1 ] } >
85
- < AsyncText ms = { 500 } text = "B" />
114
+ < AsyncText text = "B" />
86
115
</ div >
87
116
< div style = { { display : 'inline' } } ref = { divs [ 2 ] } >
88
117
< Text text = "C" />
@@ -95,9 +124,9 @@ describe('ReactDOMSuspensePlaceholder', () => {
95
124
expect ( window . getComputedStyle ( divs [ 1 ] . current ) . display ) . toEqual ( 'none' ) ;
96
125
expect ( window . getComputedStyle ( divs [ 2 ] . current ) . display ) . toEqual ( 'none' ) ;
97
126
98
- await advanceTimers ( 500 ) ;
99
-
100
- Scheduler . unstable_flushAll ( ) ;
127
+ await act ( async ( ) => {
128
+ await resolveText ( 'B' ) ;
129
+ } ) ;
101
130
102
131
expect ( window . getComputedStyle ( divs [ 0 ] . current ) . display ) . toEqual ( 'block' ) ;
103
132
expect ( window . getComputedStyle ( divs [ 1 ] . current ) . display ) . toEqual ( 'block' ) ;
@@ -110,17 +139,17 @@ describe('ReactDOMSuspensePlaceholder', () => {
110
139
return (
111
140
< Suspense fallback = { < Text text = "Loading..." /> } >
112
141
< Text text = "A" />
113
- < AsyncText ms = { 500 } text = "B" />
142
+ < AsyncText text = "B" />
114
143
< Text text = "C" />
115
144
</ Suspense >
116
145
) ;
117
146
}
118
147
ReactDOM . render ( < App /> , container ) ;
119
148
expect ( container . textContent ) . toEqual ( 'Loading...' ) ;
120
149
121
- await advanceTimers ( 500 ) ;
122
-
123
- Scheduler . unstable_flushAll ( ) ;
150
+ await act ( async ( ) => {
151
+ await resolveText ( 'B' ) ;
152
+ } ) ;
124
153
125
154
expect ( container . textContent ) . toEqual ( 'ABC' ) ;
126
155
} ) ;
@@ -147,7 +176,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
147
176
< Suspense fallback = { < Text text = "Loading..." /> } >
148
177
< Sibling > Sibling</ Sibling >
149
178
< span >
150
- < AsyncText ms = { 500 } text = "Async" />
179
+ < AsyncText text = "Async" />
151
180
</ span >
152
181
</ Suspense >
153
182
) ;
@@ -161,8 +190,16 @@ describe('ReactDOMSuspensePlaceholder', () => {
161
190
'"display: none;"></span>Loading...' ,
162
191
) ;
163
192
193
+ // Update the inline display style. It will be overridden because it's
194
+ // inside a hidden fallback.
164
195
await act ( async ( ) => setIsVisible ( true ) ) ;
196
+ expect ( container . innerHTML ) . toEqual (
197
+ '<span style="display: none;">Sibling</span><span style=' +
198
+ '"display: none;"></span>Loading...' ,
199
+ ) ;
165
200
201
+ // Unsuspend. The style should now match the inline prop.
202
+ await act ( async ( ) => resolveText ( 'Async' ) ) ;
166
203
expect ( container . innerHTML ) . toEqual (
167
204
'<span style="display: inline;">Sibling</span><span style="">Async</span>' ,
168
205
) ;
0 commit comments