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..395f81e9d65e --- /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,26 @@ +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() 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-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..337a29553479 --- /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,26 @@ +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() 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/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..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 @@ -816,6 +816,69 @@ 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]' + ) + // 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. + 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]' + ) + // 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)' + ) + 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." + `) + 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', () => {