From 08b7e6ce456b8b6ed36be3379513ac86ab6f3ffc Mon Sep 17 00:00:00 2001 From: Aurora Scharff Date: Wed, 3 Jun 2026 13:49:48 +0200 Subject: [PATCH 1/5] test(instant-validation): add fixtures for usePathname() stack-trace attribution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three failing-on-purpose fixtures + snapshots that capture how the instant validator currently attributes errors thrown by client URL hooks. Today the framework loses the React component frame when usePathname() suspends during validation; the snapshots bake the current buggy shape so any future fix shows up clearly in review. Build-mode (test/e2e/app-dir/instant-validation-build): - pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]: usePathname() called at the top of a Client Component body. Stack frame degrades to '' even though file/line/column are correct. - pathname/invalid-use-pathname-deep-component/[one]/[two]: usePathname() buried under three Client Component wrappers. Stack frame shows the SWC-minified single-letter name instead of 'PathnameReader'. The minified name is non-deterministic across module-graph orderings, so the snapshot normalizes 'at X (' to 'at ('. Dev-mode (test/e2e/app-dir/instant-validation): - default/invalid-use-pathname-no-samples/[slug]: No unstable_instant config, [slug] is a fallback route param. The dev overlay reports the parent page.tsx's '' JSX line with code E1265 'uncached data during prerendering' and label 'Blocking Route' — instead of pointing at the actual usePathname() call inside pathname-label.tsx and using a URL-data category. The dev fixture lives under app/default/ (no root Suspense above body) so the validation hole isn't swallowed before the Insight can fire. Tracks vercel/next.js#94235 and vercel/next.js#94387 docs follow-ups. --- .../[one]/[two]/inner-wrapper.tsx | 11 +++ .../[one]/[two]/middle-wrapper.tsx | 11 +++ .../[one]/[two]/outer-wrapper.tsx | 11 +++ .../[one]/[two]/page.tsx | 29 +++++++ .../[one]/[two]/pathname-reader.tsx | 8 ++ .../[one]/[two]/page.tsx | 28 +++++++ .../[one]/[two]/pathname-reader.tsx | 8 ++ .../instant-validation-build.test.ts | 76 +++++++++++++++++++ .../[slug]/page.tsx | 14 ++++ .../[slug]/pathname-label.tsx | 8 ++ .../instant-validation/app/default/page.tsx | 3 + .../instant-validation.test.ts | 44 +++++++++++ 12 files changed, 251 insertions(+) create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/inner-wrapper.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/middle-wrapper.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/outer-wrapper.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/pathname-reader.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx create mode 100644 test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/pathname-reader.tsx create mode 100644 test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx create mode 100644 test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/inner-wrapper.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/inner-wrapper.tsx new file mode 100644 index 000000000000..e8f065ce839f --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/inner-wrapper.tsx @@ -0,0 +1,11 @@ +'use client' + +import { PathnameReader } from './pathname-reader' + +export function InnerWrapper() { + return ( +

+ Current path: +

+ ) +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/middle-wrapper.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/middle-wrapper.tsx new file mode 100644 index 000000000000..4c3c74fed751 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/middle-wrapper.tsx @@ -0,0 +1,11 @@ +'use client' + +import { InnerWrapper } from './inner-wrapper' + +export function MiddleWrapper() { + return ( +
+ +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/outer-wrapper.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/outer-wrapper.tsx new file mode 100644 index 000000000000..01dac3fbc9bd --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/outer-wrapper.tsx @@ -0,0 +1,11 @@ +'use client' + +import { MiddleWrapper } from './middle-wrapper' + +export function OuterWrapper() { + return ( +
+ +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx new file mode 100644 index 000000000000..72eeec134b3f --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx @@ -0,0 +1,29 @@ +import type { Instant } from 'next' +import { OuterWrapper } from './outer-wrapper' + +export const unstable_instant: Instant = { + level: 'experimental-error', + unstable_samples: [ + { + params: { + one: '123', + // two: + }, + }, + ], +} + +export default function Page() { + return ( +
+

+ usePathname() called from a Client Component nested three wrapper + components deep on a route with dynamic params where not all params are + provided in the sample. The build-time validation error should still + point at the actual call site in the user's source, not at a generic{' '} + {''} frame. +

+ +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/pathname-reader.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/pathname-reader.tsx new file mode 100644 index 000000000000..c96d1a9f2b64 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/pathname-reader.tsx @@ -0,0 +1,8 @@ +'use client' + +import { usePathname } from 'next/navigation' + +export function PathnameReader() { + const pathname = usePathname() + return {pathname} +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx new file mode 100644 index 000000000000..a736d99f2fc4 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx @@ -0,0 +1,28 @@ +import type { Instant } from 'next' +import { PathnameReader } from './pathname-reader' + +export const unstable_instant: Instant = { + level: 'experimental-error', + unstable_samples: [ + { + params: { + one: '123', + // two: + }, + }, + ], +} + +export default function Page() { + return ( +
+

+ usePathname() called directly at the top of a Client Component body (no{' '} + ensureThrows() wrapper) on a route with dynamic params + where not all params are provided in the sample. The build-time + validation error should point at the call site in the user's source. +

+ +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/pathname-reader.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/pathname-reader.tsx new file mode 100644 index 000000000000..c96d1a9f2b64 --- /dev/null +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/pathname-reader.tsx @@ -0,0 +1,8 @@ +'use client' + +import { usePathname } from 'next/navigation' + +export function PathnameReader() { + const pathname = usePathname() + return {pathname} +} diff --git a/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts b/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts index 7ebe14f6e75f..399202acfa71 100644 --- a/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts +++ b/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts @@ -816,6 +816,82 @@ describe('instant-validation-build', () => { expect(result.cliOutput).not.toContain('AssertionError') expect(result.exitCode).toBe(1) }) + + it('error - usePathname() at the top of a Client Component body (no ensureThrows wrapper)', async () => { + const result = await prerender( + '/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]' + ) + // Snapshot the full validation error so any change to the stack frame + // attribution (good or bad) shows up clearly. Today's behavior: the file, + // line, and column are correct, but the function name is reported as + // `` instead of `PathnameReader`. When the framework starts + // recovering the real function name from sourcemaps, update this snapshot. + expect(extractBuildValidationError(result.cliOutput)) + .toMatchInlineSnapshot(` + "Error: Route "/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]" called usePathname() but param "two" is not defined in the \`unstable_samples\` of \`unstable_instant\`. usePathname() requires all route params to be provided. + at (app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/pathname-reader.tsx:6:20) + 4 | + 5 | export function PathnameReader() { + > 6 | const pathname = usePathname() + | ^ + 7 | return {pathname} + 8 | } + 9 | { + digest: 'INSTANT_VALIDATION_ERROR' + } + Build-time instant validation failed for route "/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]". + To get a more detailed stack trace and pinpoint the issue, try one of the following: + - Start the app in development mode by running \`next dev\`, then open "/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]" in your browser to investigate the error. + - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces. + Stopping prerender due to instant validation errors." + `) + expect(result.cliOutput).not.toContain('AssertionError') + expect(result.exitCode).toBe(1) + }) + + it('error - usePathname() called from a Client Component nested three wrappers deep', async () => { + const result = await prerender( + '/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]' + ) + // Even when the offending hook call is buried under multiple Client + // Component wrappers, the build-time validation error should identify + // the actual call site inside `pathname-reader.tsx` (not a wrapper file). + // Today's behavior: file/line/column are correct, but the function name + // in the stack frame is an SWC-minified single letter instead of + // `PathnameReader`. The minified name is not deterministic across + // module-graph orderings, so normalize it to `` before + // snapshotting. + const error = extractBuildValidationError(result.cliOutput).replace( + /at [a-z] \(app\/\(default\)/g, + 'at (app/(default)' + ) + expect(error).toMatchInlineSnapshot(` + "Error: Route "/pathname/invalid-use-pathname-deep-component/[one]/[two]" called usePathname() but param "two" is not defined in the \`unstable_samples\` of \`unstable_instant\`. usePathname() requires all route params to be provided. + at (app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/pathname-reader.tsx:6:20) + 4 | + 5 | export function PathnameReader() { + > 6 | const pathname = usePathname() + | ^ + 7 | return {pathname} + 8 | } + 9 | { + digest: 'INSTANT_VALIDATION_ERROR' + } + Build-time instant validation failed for route "/pathname/invalid-use-pathname-deep-component/[one]/[two]". + To get a more detailed stack trace and pinpoint the issue, try one of the following: + - Start the app in development mode by running \`next dev\`, then open "/pathname/invalid-use-pathname-deep-component/[one]/[two]" in your browser to investigate the error. + - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces. + Stopping prerender due to instant validation errors." + `) + // Wrapper components should not appear in the call chain — `usePathname` + // is called directly inside `PathnameReader`, not inside its wrappers. + const rawError = extractBuildValidationError(result.cliOutput) + expect(rawError).not.toContain('outer-wrapper.tsx') + expect(rawError).not.toContain('middle-wrapper.tsx') + expect(rawError).not.toContain('inner-wrapper.tsx') + expect(result.cliOutput).not.toContain('AssertionError') + expect(result.exitCode).toBe(1) + }) }) describe('root params', () => { diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx new file mode 100644 index 000000000000..b9ea65bf8b73 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx @@ -0,0 +1,14 @@ +import { PathnameLabel } from './pathname-label' + +// No `unstable_instant` config at all — the [slug] param becomes a fallback +// route param during validation, which means usePathname() suspends. This is +// the natural user shape and matches the test-app's repro at +// `88-client-use-pathname/[slug]/page.tsx`. + +export default function Page() { + return ( +
+ +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx new file mode 100644 index 000000000000..8c4c2520c33c --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx @@ -0,0 +1,8 @@ +'use client' + +import { usePathname } from 'next/navigation' + +export function PathnameLabel() { + const pathname = usePathname() + return {pathname} +} diff --git a/test/e2e/app-dir/instant-validation/app/default/page.tsx b/test/e2e/app-dir/instant-validation/app/default/page.tsx index 92e5ef89941d..dfed682450f9 100644 --- a/test/e2e/app-dir/instant-validation/app/default/page.tsx +++ b/test/e2e/app-dir/instant-validation/app/default/page.tsx @@ -14,6 +14,9 @@ export default async function Page() {
  • +
  • + +
  • ) diff --git a/test/e2e/app-dir/instant-validation/instant-validation.test.ts b/test/e2e/app-dir/instant-validation/instant-validation.test.ts index a90a26734f79..a3f16f69d2fa 100644 --- a/test/e2e/app-dir/instant-validation/instant-validation.test.ts +++ b/test/e2e/app-dir/instant-validation/instant-validation.test.ts @@ -1893,6 +1893,50 @@ describe('instant validation', () => { expectNoBuildValidationErrors(result) } }) + + it('invalid - usePathname() in a client component on a route with a fallback param', async () => { + if (!isNextDev) { + // Build-mode coverage lives in + // test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts + // under the `pathname` describe block. This test asserts the dev + // overlay's frame attribution. + return + } + if (isClientNav) { + // The bug we're capturing only manifests on initial load, where the + // dev validation runs server-side and the React stack is what gets + // surfaced. Skip the client-nav case so the inline snapshot below + // doesn't have to capture two different shapes. + return + } + // The fixture lives under `default/` (no root Suspense above body) so + // the validation hole isn't swallowed before the Insight can fire. + // `[slug]` is a fallback route param with no `unstable_samples`, so + // `usePathname()` suspends during dev validation — mirroring the + // test-app's repro at `88-client-use-pathname/[slug]`. + const browser = await navigateTo( + '/default/invalid-use-pathname-no-samples/123' + ) + // Snapshot the dev redbox shape so the bug surface is visible: + // today the overlay reports the parent page.tsx's render JSX as the + // source, not the `usePathname()` call inside pathname-label.tsx, + // and routes the user toward Cache / Stream / Block cards even + // though `usePathname` is URL data, not cacheable data. + await expect(browser).toDisplayCollapsedRedbox(` + { + "code": "E1265", + "description": "Next.js encountered uncached data during prerendering.", + "environmentLabel": "Server", + "label": "Blocking Route", + "source": "app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (11:7) @ Page + > 11 | + | ^", + "stack": [ + "Page app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (11:7)", + ], + } + `) + }) }) describe('client errors', () => { From 73ba110fc952369ec6fc58c48a4cdd512d1e9601 Mon Sep 17 00:00:00 2001 From: Aurora Scharff Date: Wed, 3 Jun 2026 14:13:49 +0200 Subject: [PATCH 2/5] trim fixture descriptions, follow existing test conventions --- .../[one]/[two]/page.tsx | 7 +- .../[one]/[two]/page.tsx | 6 +- .../instant-validation-build.test.ts | 17 +---- .../[slug]/page.tsx | 5 -- .../instant-validation.test.ts | 64 +++++++------------ 5 files changed, 30 insertions(+), 69 deletions(-) diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx index 72eeec134b3f..395f81e9d65e 100644 --- a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]/page.tsx @@ -17,11 +17,8 @@ export default function Page() { return (

    - usePathname() called from a Client Component nested three wrapper - components deep on a route with dynamic params where not all params are - provided in the sample. The build-time validation error should still - point at the actual call site in the user's source, not at a generic{' '} - {''} frame. + usePathname() nested three wrappers deep should still point at the + actual call site.

    diff --git a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx index a736d99f2fc4..337a29553479 100644 --- a/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx +++ b/test/e2e/app-dir/instant-validation-build/app/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]/page.tsx @@ -17,10 +17,8 @@ export default function Page() { return (

    - usePathname() called directly at the top of a Client Component body (no{' '} - ensureThrows() wrapper) on a route with dynamic params - where not all params are provided in the sample. The build-time - validation error should point at the call site in the user's source. + usePathname() without an ensureThrows wrapper should point at the call + site in the user's source.

    diff --git a/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts b/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts index 399202acfa71..0168b2b5e7b5 100644 --- a/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts +++ b/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts @@ -821,11 +821,7 @@ describe('instant-validation-build', () => { const result = await prerender( '/(default)/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]' ) - // Snapshot the full validation error so any change to the stack frame - // attribution (good or bad) shows up clearly. Today's behavior: the file, - // line, and column are correct, but the function name is reported as - // `` instead of `PathnameReader`. When the framework starts - // recovering the real function name from sourcemaps, update this snapshot. + // Today the function name is `` instead of `PathnameReader`. expect(extractBuildValidationError(result.cliOutput)) .toMatchInlineSnapshot(` "Error: Route "/pathname/invalid-use-pathname-missing-params-no-wrapper/[one]/[two]" called usePathname() but param "two" is not defined in the \`unstable_samples\` of \`unstable_instant\`. usePathname() requires all route params to be provided. @@ -853,14 +849,7 @@ describe('instant-validation-build', () => { const result = await prerender( '/(default)/pathname/invalid-use-pathname-deep-component/[one]/[two]' ) - // Even when the offending hook call is buried under multiple Client - // Component wrappers, the build-time validation error should identify - // the actual call site inside `pathname-reader.tsx` (not a wrapper file). - // Today's behavior: file/line/column are correct, but the function name - // in the stack frame is an SWC-minified single letter instead of - // `PathnameReader`. The minified name is not deterministic across - // module-graph orderings, so normalize it to `` before - // snapshotting. + // The minified function name is not deterministic, so normalize it. const error = extractBuildValidationError(result.cliOutput).replace( /at [a-z] \(app\/\(default\)/g, 'at (app/(default)' @@ -883,8 +872,6 @@ describe('instant-validation-build', () => { - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces. Stopping prerender due to instant validation errors." `) - // Wrapper components should not appear in the call chain — `usePathname` - // is called directly inside `PathnameReader`, not inside its wrappers. const rawError = extractBuildValidationError(result.cliOutput) expect(rawError).not.toContain('outer-wrapper.tsx') expect(rawError).not.toContain('middle-wrapper.tsx') diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx index b9ea65bf8b73..49fdf62dc6c5 100644 --- a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx @@ -1,10 +1,5 @@ import { PathnameLabel } from './pathname-label' -// No `unstable_instant` config at all — the [slug] param becomes a fallback -// route param during validation, which means usePathname() suspends. This is -// the natural user shape and matches the test-app's repro at -// `88-client-use-pathname/[slug]/page.tsx`. - export default function Page() { return (
    diff --git a/test/e2e/app-dir/instant-validation/instant-validation.test.ts b/test/e2e/app-dir/instant-validation/instant-validation.test.ts index a3f16f69d2fa..00876f522e23 100644 --- a/test/e2e/app-dir/instant-validation/instant-validation.test.ts +++ b/test/e2e/app-dir/instant-validation/instant-validation.test.ts @@ -1895,47 +1895,31 @@ describe('instant validation', () => { }) it('invalid - usePathname() in a client component on a route with a fallback param', async () => { - if (!isNextDev) { - // Build-mode coverage lives in - // test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts - // under the `pathname` describe block. This test asserts the dev - // overlay's frame attribution. - return - } - if (isClientNav) { - // The bug we're capturing only manifests on initial load, where the - // dev validation runs server-side and the React stack is what gets - // surfaced. Skip the client-nav case so the inline snapshot below - // doesn't have to capture two different shapes. - return + if (isNextDev) { + if (isClientNav) { + // Client-nav fires a different redbox shape; only assert initial load. + return + } + const browser = await navigateTo( + '/default/invalid-use-pathname-no-samples/123' + ) + // TODO(url-hook-factory): once the URL-hook factory lands, the + // source should point at pathname-label.tsx, not page.tsx. + await expect(browser).toDisplayCollapsedRedbox(` + { + "code": "E1265", + "description": "Next.js encountered uncached data during prerendering.", + "environmentLabel": "Server", + "label": "Blocking Route", + "source": "app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (6:7) @ Page + > 6 | + | ^", + "stack": [ + "Page app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (6:7)", + ], + } + `) } - // The fixture lives under `default/` (no root Suspense above body) so - // the validation hole isn't swallowed before the Insight can fire. - // `[slug]` is a fallback route param with no `unstable_samples`, so - // `usePathname()` suspends during dev validation — mirroring the - // test-app's repro at `88-client-use-pathname/[slug]`. - const browser = await navigateTo( - '/default/invalid-use-pathname-no-samples/123' - ) - // Snapshot the dev redbox shape so the bug surface is visible: - // today the overlay reports the parent page.tsx's render JSX as the - // source, not the `usePathname()` call inside pathname-label.tsx, - // and routes the user toward Cache / Stream / Block cards even - // though `usePathname` is URL data, not cacheable data. - await expect(browser).toDisplayCollapsedRedbox(` - { - "code": "E1265", - "description": "Next.js encountered uncached data during prerendering.", - "environmentLabel": "Server", - "label": "Blocking Route", - "source": "app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (11:7) @ Page - > 11 | - | ^", - "stack": [ - "Page app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (11:7)", - ], - } - `) }) }) From d7285c650edd2a21283951055c36362641093b22 Mon Sep 17 00:00:00 2001 From: Aurora Scharff Date: Wed, 3 Jun 2026 14:19:04 +0200 Subject: [PATCH 3/5] use it.failing with correct stack assertion per Sebbie's review --- .../instant-validation.test.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/e2e/app-dir/instant-validation/instant-validation.test.ts b/test/e2e/app-dir/instant-validation/instant-validation.test.ts index 00876f522e23..198b47d0d36a 100644 --- a/test/e2e/app-dir/instant-validation/instant-validation.test.ts +++ b/test/e2e/app-dir/instant-validation/instant-validation.test.ts @@ -1894,33 +1894,36 @@ describe('instant validation', () => { } }) - it('invalid - usePathname() in a client component on a route with a fallback param', async () => { - if (isNextDev) { - if (isClientNav) { - // Client-nav fires a different redbox shape; only assert initial load. + // Today the dev overlay misattributes usePathname() errors to the + // parent's JSX line instead of the actual hook call site. When the + // URL-hook factory lands, this test will start passing and + // `it.failing` will itself fail — flip it back to `it`. + /* eslint-disable jest/no-standalone-expect */ + ;(isNextDev && !isClientNav ? it.failing : it)( + 'invalid - usePathname() in a client component on a route with a fallback param', + async () => { + if (!isNextDev || isClientNav) { return } const browser = await navigateTo( '/default/invalid-use-pathname-no-samples/123' ) - // TODO(url-hook-factory): once the URL-hook factory lands, the - // source should point at pathname-label.tsx, not page.tsx. await expect(browser).toDisplayCollapsedRedbox(` { - "code": "E1265", "description": "Next.js encountered uncached data during prerendering.", "environmentLabel": "Server", "label": "Blocking Route", - "source": "app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (6:7) @ Page - > 6 | - | ^", + "source": "app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx (6:20) @ PathnameLabel + > 6 | const pathname = usePathname() + | ^", "stack": [ - "Page app/default/invalid-use-pathname-no-samples/[slug]/page.tsx (6:7)", + "PathnameLabel app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx (6:20)", ], } `) } - }) + ) + /* eslint-enable jest/no-standalone-expect */ }) describe('client errors', () => { From 3e310c5e01f34614a88cf0c20b6425faac96ab0c Mon Sep 17 00:00:00 2001 From: Aurora Scharff Date: Wed, 3 Jun 2026 15:07:21 +0200 Subject: [PATCH 4/5] fix: use regular assertions instead of inline snapshots in it.failing --- .../instant-validation.test.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/test/e2e/app-dir/instant-validation/instant-validation.test.ts b/test/e2e/app-dir/instant-validation/instant-validation.test.ts index 198b47d0d36a..3375af034260 100644 --- a/test/e2e/app-dir/instant-validation/instant-validation.test.ts +++ b/test/e2e/app-dir/instant-validation/instant-validation.test.ts @@ -6,6 +6,7 @@ import { waitForValidation, } from 'e2e-utils/instant-validation' import { + getRedboxSource, openRedbox, retry, waitForNoErrorToast, @@ -1908,19 +1909,10 @@ describe('instant validation', () => { const browser = await navigateTo( '/default/invalid-use-pathname-no-samples/123' ) - await expect(browser).toDisplayCollapsedRedbox(` - { - "description": "Next.js encountered uncached data during prerendering.", - "environmentLabel": "Server", - "label": "Blocking Route", - "source": "app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx (6:20) @ PathnameLabel - > 6 | const pathname = usePathname() - | ^", - "stack": [ - "PathnameLabel app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx (6:20)", - ], - } - `) + await openRedbox(browser) + const source = await getRedboxSource(browser) + expect(source).toContain('pathname-label.tsx') + expect(source).toContain('PathnameLabel') } ) /* eslint-enable jest/no-standalone-expect */ From 18567a9421fb88127a3dd8dd6a4f844ccea2eeb0 Mon Sep 17 00:00:00 2001 From: Aurora Scharff Date: Wed, 3 Jun 2026 15:25:46 +0200 Subject: [PATCH 5/5] remove dev usePathname test (moved to #94423) --- .../[slug]/page.tsx | 9 -------- .../[slug]/pathname-label.tsx | 8 ------- .../instant-validation/app/default/page.tsx | 3 --- .../instant-validation.test.ts | 23 ------------------- 4 files changed, 43 deletions(-) delete mode 100644 test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx delete mode 100644 test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx deleted file mode 100644 index 49fdf62dc6c5..000000000000 --- a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { PathnameLabel } from './pathname-label' - -export default function Page() { - return ( -
    - -
    - ) -} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx deleted file mode 100644 index 8c4c2520c33c..000000000000 --- a/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/pathname-label.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client' - -import { usePathname } from 'next/navigation' - -export function PathnameLabel() { - const pathname = usePathname() - return {pathname} -} diff --git a/test/e2e/app-dir/instant-validation/app/default/page.tsx b/test/e2e/app-dir/instant-validation/app/default/page.tsx index dfed682450f9..92e5ef89941d 100644 --- a/test/e2e/app-dir/instant-validation/app/default/page.tsx +++ b/test/e2e/app-dir/instant-validation/app/default/page.tsx @@ -14,9 +14,6 @@ export default async function Page() {
  • -
  • - -
  • ) diff --git a/test/e2e/app-dir/instant-validation/instant-validation.test.ts b/test/e2e/app-dir/instant-validation/instant-validation.test.ts index 3375af034260..a90a26734f79 100644 --- a/test/e2e/app-dir/instant-validation/instant-validation.test.ts +++ b/test/e2e/app-dir/instant-validation/instant-validation.test.ts @@ -6,7 +6,6 @@ import { waitForValidation, } from 'e2e-utils/instant-validation' import { - getRedboxSource, openRedbox, retry, waitForNoErrorToast, @@ -1894,28 +1893,6 @@ describe('instant validation', () => { expectNoBuildValidationErrors(result) } }) - - // Today the dev overlay misattributes usePathname() errors to the - // parent's JSX line instead of the actual hook call site. When the - // URL-hook factory lands, this test will start passing and - // `it.failing` will itself fail — flip it back to `it`. - /* eslint-disable jest/no-standalone-expect */ - ;(isNextDev && !isClientNav ? it.failing : it)( - 'invalid - usePathname() in a client component on a route with a fallback param', - async () => { - if (!isNextDev || isClientNav) { - return - } - const browser = await navigateTo( - '/default/invalid-use-pathname-no-samples/123' - ) - await openRedbox(browser) - const source = await getRedboxSource(browser) - expect(source).toContain('pathname-label.tsx') - expect(source).toContain('PathnameLabel') - } - ) - /* eslint-enable jest/no-standalone-expect */ }) describe('client errors', () => {