@@ -2111,6 +2111,183 @@ describe('ReactHooksWithNoopRenderer', () => {
2111
2111
] ) ;
2112
2112
} ,
2113
2113
) ;
2114
+
2115
+ it . experimental ( 'always returns the same startTransition' , async ( ) => {
2116
+ let transition ;
2117
+ function App ( ) {
2118
+ const [ step , setStep ] = useState ( 0 ) ;
2119
+ const [ startTransition , isPending ] = useTransition ( {
2120
+ busyDelayMs : 1000 ,
2121
+ busyMinDurationMs : 2000 ,
2122
+ } ) ;
2123
+ // Log whenever startTransition changes
2124
+ useEffect (
2125
+ ( ) => {
2126
+ Scheduler . unstable_yieldValue ( 'New startTransition function' ) ;
2127
+ } ,
2128
+ [ startTransition ] ,
2129
+ ) ;
2130
+ transition = ( ) => {
2131
+ startTransition ( ( ) => {
2132
+ setStep ( n => n + 1 ) ;
2133
+ } ) ;
2134
+ } ;
2135
+ return (
2136
+ < Suspense fallback = { < Text text = "Loading..." /> } >
2137
+ < AsyncText key = { step } ms = { 2000 } text = { `Step: ${ step } ` } />
2138
+ { isPending && < Text text = "(pending...)" /> }
2139
+ </ Suspense >
2140
+ ) ;
2141
+ }
2142
+
2143
+ const root = ReactNoop . createRoot ( ) ;
2144
+ await ReactNoop . act ( async ( ) => {
2145
+ root . render ( < App /> ) ;
2146
+ } ) ;
2147
+ expect ( Scheduler ) . toHaveYielded ( [
2148
+ 'Suspend! [Step: 0]' ,
2149
+ 'Loading...' ,
2150
+ // Initial mount. This should never be logged again.
2151
+ 'New startTransition function' ,
2152
+ ] ) ;
2153
+ await ReactNoop . act ( async ( ) => {
2154
+ await advanceTimers ( 2000 ) ;
2155
+ } ) ;
2156
+ expect ( Scheduler ) . toHaveYielded ( [
2157
+ 'Promise resolved [Step: 0]' ,
2158
+ 'Step: 0' ,
2159
+ ] ) ;
2160
+
2161
+ // Update. The effect should not fire.
2162
+ await ReactNoop . act ( async ( ) => {
2163
+ Scheduler . unstable_runWithPriority (
2164
+ Scheduler . unstable_UserBlockingPriority ,
2165
+ transition ,
2166
+ ) ;
2167
+ } ) ;
2168
+ expect ( Scheduler ) . toHaveYielded ( [
2169
+ 'Step: 0' ,
2170
+ '(pending...)' ,
2171
+ 'Suspend! [Step: 1]' ,
2172
+ 'Loading...' ,
2173
+ // No log effect, because startTransition did not change
2174
+ ] ) ;
2175
+ } ) ;
2176
+
2177
+ it . experimental (
2178
+ 'can update suspense config (without changing startTransition)' ,
2179
+ async ( ) => {
2180
+ let transition ;
2181
+ function App ( { timeoutMs} ) {
2182
+ const [ step , setStep ] = useState ( 0 ) ;
2183
+ const [ startTransition , isPending ] = useTransition ( { timeoutMs} ) ;
2184
+ // Log whenever startTransition changes
2185
+ useEffect (
2186
+ ( ) => {
2187
+ Scheduler . unstable_yieldValue ( 'New startTransition function' ) ;
2188
+ } ,
2189
+ [ startTransition ] ,
2190
+ ) ;
2191
+ transition = ( ) => {
2192
+ startTransition ( ( ) => {
2193
+ setStep ( n => n + 1 ) ;
2194
+ } ) ;
2195
+ } ;
2196
+ return (
2197
+ < Suspense fallback = { < Text text = "Loading..." /> } >
2198
+ < AsyncText key = { step } ms = { 2000 } text = { `Step: ${ step } ` } />
2199
+ { isPending && < Text text = "(pending...)" /> }
2200
+ </ Suspense >
2201
+ ) ;
2202
+ }
2203
+
2204
+ const root = ReactNoop . createRoot ( ) ;
2205
+ await ReactNoop . act ( async ( ) => {
2206
+ root . render ( < App timeoutMs = { 500 } /> ) ;
2207
+ } ) ;
2208
+ expect ( Scheduler ) . toHaveYielded ( [
2209
+ 'Suspend! [Step: 0]' ,
2210
+ 'Loading...' ,
2211
+ // Initial mount. This should never be logged again.
2212
+ 'New startTransition function' ,
2213
+ ] ) ;
2214
+ await ReactNoop . act ( async ( ) => {
2215
+ await advanceTimers ( 2000 ) ;
2216
+ } ) ;
2217
+ expect ( Scheduler ) . toHaveYielded ( [
2218
+ 'Promise resolved [Step: 0]' ,
2219
+ 'Step: 0' ,
2220
+ ] ) ;
2221
+
2222
+ // Schedule a transition. Should timeout quickly.
2223
+ await ReactNoop . act ( async ( ) => {
2224
+ Scheduler . unstable_runWithPriority (
2225
+ Scheduler . unstable_UserBlockingPriority ,
2226
+ transition ,
2227
+ ) ;
2228
+
2229
+ expect ( Scheduler ) . toFlushAndYield ( [
2230
+ 'Step: 0' ,
2231
+ '(pending...)' ,
2232
+ 'Suspend! [Step: 1]' ,
2233
+ 'Loading...' ,
2234
+ // No log effect, because startTransition did not change
2235
+ ] ) ;
2236
+
2237
+ // Advance time. This should be sufficient to timeout.
2238
+ await advanceTimers ( 1000 ) ;
2239
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
2240
+ // Show placeholder.
2241
+ expect ( root ) . toMatchRenderedOutput (
2242
+ < >
2243
+ < span prop = "Step: 0" hidden = { true } />
2244
+ < span prop = "(pending...)" hidden = { true } />
2245
+ < span prop = "Loading..." />
2246
+ </ > ,
2247
+ ) ;
2248
+ // Resolve the promise
2249
+ await advanceTimers ( 10000 ) ;
2250
+ } ) ;
2251
+ expect ( Scheduler ) . toHaveYielded ( [
2252
+ 'Promise resolved [Step: 1]' ,
2253
+ 'Step: 1' ,
2254
+ ] ) ;
2255
+
2256
+ // Increase the timeout threshold
2257
+ await ReactNoop . act ( async ( ) => {
2258
+ root . render ( < App timeoutMs = { 5000 } /> ) ;
2259
+ } ) ;
2260
+ expect ( Scheduler ) . toHaveYielded ( [ 'Step: 1' ] ) ;
2261
+
2262
+ // Schedule a transition again. This time it should take longer
2263
+ // to timeout.
2264
+ await ReactNoop . act ( async ( ) => {
2265
+ Scheduler . unstable_runWithPriority (
2266
+ Scheduler . unstable_UserBlockingPriority ,
2267
+ transition ,
2268
+ ) ;
2269
+
2270
+ expect ( Scheduler ) . toFlushAndYield ( [
2271
+ 'Step: 1' ,
2272
+ '(pending...)' ,
2273
+ 'Suspend! [Step: 2]' ,
2274
+ 'Loading...' ,
2275
+ // No log effect, because startTransition did not change
2276
+ ] ) ;
2277
+
2278
+ // Advance time. This should *not* be sufficient to timeout.
2279
+ await advanceTimers ( 1000 ) ;
2280
+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
2281
+ // Still showing pending state, no placeholder.
2282
+ expect ( root ) . toMatchRenderedOutput (
2283
+ < >
2284
+ < span prop = "Step: 1" />
2285
+ < span prop = "(pending...)" />
2286
+ </ > ,
2287
+ ) ;
2288
+ } ) ;
2289
+ } ,
2290
+ ) ;
2114
2291
} ) ;
2115
2292
describe ( 'useDeferredValue' , ( ) => {
2116
2293
it . experimental ( 'defers text value until specified timeout' , async ( ) => {
0 commit comments