@@ -2111,6 +2111,183 @@ describe('ReactHooksWithNoopRenderer', () => {
21112111 ] ) ;
21122112 } ,
21132113 ) ;
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+ ) ;
21142291 } ) ;
21152292 describe ( 'useDeferredValue' , ( ) => {
21162293 it . experimental ( 'defers text value until specified timeout' , async ( ) => {
0 commit comments