From fd38d5bf1520b58f879eb4afeba1cb8efb428f05 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Sat, 21 Feb 2026 10:29:09 -0800 Subject: [PATCH] segment cache: add test for passing unawaited promise to context provider (#89339) Adds a failing test proving that if the RSC response from a prefetch contains an unresolved promise, navigating to the page will cause the Suspense boundary wrapping the use of that promise to remain in fallback state indefinitely. This appears to have regressed in e9a03ac12cfe3c3fb6b16db42ade06e7382b9018, though the theory is that this is just uncovering a React bug rather than causing the issue. Closes #89170 --- .../prefetch-partial-rsc/app/layout.tsx | 9 ++++++ .../prefetch-partial-rsc/app/learn/layout.tsx | 9 ++++++ .../prefetch-partial-rsc/app/learn/page.tsx | 12 ++++++++ .../prefetch-partial-rsc/app/lib/get-user.ts | 12 ++++++++ .../prefetch-partial-rsc/app/page.tsx | 12 ++++++++ .../app/user-card-shell.tsx | 12 ++++++++ .../prefetch-partial-rsc/app/user-card.tsx | 9 ++++++ .../app/user-provider.tsx | 29 +++++++++++++++++++ .../prefetch-partial-rsc/next.config.js | 8 +++++ .../prefetch-partial-rsc.test.ts | 17 +++++++++++ 10 files changed, 129 insertions(+) create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/layout.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/layout.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/page.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/lib/get-user.ts create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/page.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card-shell.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-provider.tsx create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/next.config.js create mode 100644 test/e2e/app-dir/segment-cache/prefetch-partial-rsc/prefetch-partial-rsc.test.ts diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/layout.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/layout.tsx new file mode 100644 index 0000000000000..716a8db36f52c --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' + +export default function Root({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/layout.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/layout.tsx new file mode 100644 index 0000000000000..5c70dee3e39ac --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' +import { getUser } from '../lib/get-user' +import { UserProvider } from '../user-provider' + +export default function LearnLayout({ children }: { children: ReactNode }) { + const userPromise = getUser() + + return {children} +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/page.tsx new file mode 100644 index 0000000000000..87fc4ddc71430 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/learn/page.tsx @@ -0,0 +1,12 @@ +import { UserCardShell } from '../user-card-shell' + +export default async function LearnPage() { + 'use cache' + + return ( +
+

Learn

+ +
+ ) +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/lib/get-user.ts b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/lib/get-user.ts new file mode 100644 index 0000000000000..2dfb4d090a371 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/lib/get-user.ts @@ -0,0 +1,12 @@ +import { cookies } from 'next/headers' + +export async function getUser(): Promise<{ name: string }> { + const cookieStore = await cookies() + const userCookie = cookieStore.get('user') + + if (!userCookie) { + return { name: 'Guest' } + } + + return { name: userCookie.value } +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/page.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/page.tsx new file mode 100644 index 0000000000000..166b2d085c666 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/page.tsx @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+

User promise demo

+ + Go to learn + +
+ ) +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card-shell.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card-shell.tsx new file mode 100644 index 0000000000000..68b2ae89fdc2f --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card-shell.tsx @@ -0,0 +1,12 @@ +'use client' + +import { Suspense } from 'react' +import { UserCard } from './user-card' + +export function UserCardShell() { + return ( + loading

}> + +
+ ) +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card.tsx new file mode 100644 index 0000000000000..e88bdd59ad4e5 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-card.tsx @@ -0,0 +1,9 @@ +'use client' + +import { useUser } from './user-provider' + +export function UserCard() { + const user = useUser() + + return

user: {user?.name ?? 'unknown'}

+} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-provider.tsx b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-provider.tsx new file mode 100644 index 0000000000000..35c06dd843666 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/app/user-provider.tsx @@ -0,0 +1,29 @@ +'use client' + +import { createContext, ReactNode, use } from 'react' + +type User = { name: string } | undefined + +const UserContext = createContext | null>(null) +UserContext.displayName = 'UserContext' + +export function UserProvider({ + children, + userPromise, +}: { + children: ReactNode + userPromise: Promise +}) { + return ( + {children} + ) +} + +export function useUser(): User { + const userPromise = use(UserContext) + if (!userPromise) { + throw new Error('useUser must be used within a UserProvider') + } + + return use(userPromise) +} diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/next.config.js b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/next.config.js new file mode 100644 index 0000000000000..e64bae22d6580 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/next.config.js @@ -0,0 +1,8 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + cacheComponents: true, +} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/prefetch-partial-rsc.test.ts b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/prefetch-partial-rsc.test.ts new file mode 100644 index 0000000000000..70dfe8ac8c903 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/prefetch-partial-rsc/prefetch-partial-rsc.test.ts @@ -0,0 +1,17 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +describe('prefetch-partial-rsc', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('resolves after a client-side navigation', async () => { + const browser = await next.browser('/') + await browser.elementByCss('#learn-link').click() + await retry(async () => { + const text = await browser.elementByCss('#user').text() + expect(text).toBe('user: Guest') + }) + }) +})