Skip to content

Commit 35280c5

Browse files
committed
[Failing tests] startTransition should not change
Like `dispatch` or `setState`, we should return the same `startTransition` function on every render.
1 parent 0f3838a commit 35280c5

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js

+177
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)