mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
ad1d1971af
* test(app-router): add E2E coverage for isPending across RSC redirect router.push() to a page that calls redirect() in its server component produced isPending flashing false mid-redirect. This test exposes that by installing a MutationObserver before click, recording every text value of #pending-state, and asserting no idle entry appears between the first pending transition and the destination commit. Ported from Next.js: test/e2e/app-dir/navigation/navigation.test.ts#L482 New fixtures: - /nextjs-compat/router-push-pending-redirect -- server component that calls redirect() - /nextjs-compat/router-push-pending-destination -- final destination with #redirect-destination - Push-redirect button added to existing PendingClient * fix(app-router): keep isPending true across RSC-level redirects When router.push() targeted a page whose server component called redirect(), isPending flashed false mid-redirect. The fetch returned a 307, navigateRsc detected the response URL differing from the requested URL, settled the deferred pendingRouterState to prevent promise orphaning, then recursed with programmaticTransition=false. The settle fired before the recursive call so React resolved the transition and isPending dropped to false during the hop. Root cause: the recursive re-entry model required settling the deferred promise before handing off, because the recursive call had no way to inherit the existing promise. This was explicitly documented as a tradeoff in the bandage comment at the settle site. Convert the recursive redirect follow into an inline while loop. The loop variables (currentHref, currentHistoryMode, currentPrevNextUrl, redirectCount) are updated on each redirect hop and the loop continues without re-entering navigateRsc. A single pendingRouterState is created before the loop and lives until the finally block, so isPending stays true across all redirect hops and drops exactly once when the final destination commits. The scattered settlePendingBrowserRouterState() calls inside the try body (early-return stale-id guards) are removed. The finally owns the single settlement site, and the function is already inside React.startTransition from the programmatic push, so the correct transition priority is maintained throughout. Closes #869.