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..49fdf62dc6c5 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-pathname-no-samples/[slug]/page.tsx @@ -0,0 +1,9 @@ +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 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/invalid-use-selected-layout-segment-no-samples/[slug]/active-tab.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/active-tab.tsx new file mode 100644 index 000000000000..942eb48c416d --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/active-tab.tsx @@ -0,0 +1,8 @@ +'use client' + +import { useSelectedLayoutSegment } from 'next/navigation' + +export function ActiveTab() { + const segment = useSelectedLayoutSegment() + return {segment} +} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/layout.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/layout.tsx new file mode 100644 index 000000000000..8f704aba0004 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/layout.tsx @@ -0,0 +1,10 @@ +import { ActiveTab } from './active-tab' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ + {children} +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/page.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/page.tsx new file mode 100644 index 000000000000..1e5a065b23c7 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segment-no-samples/[slug]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Leaf page
+} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/breadcrumb-trail.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/breadcrumb-trail.tsx new file mode 100644 index 000000000000..97a6f5aa98a4 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/breadcrumb-trail.tsx @@ -0,0 +1,8 @@ +'use client' + +import { useSelectedLayoutSegments } from 'next/navigation' + +export function BreadcrumbTrail() { + const segments = useSelectedLayoutSegments() + return {segments.join('/')} +} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/layout.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/layout.tsx new file mode 100644 index 000000000000..9d12f129b129 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/layout.tsx @@ -0,0 +1,10 @@ +import { BreadcrumbTrail } from './breadcrumb-trail' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( +
+ + {children} +
+ ) +} diff --git a/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/page.tsx b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/page.tsx new file mode 100644 index 000000000000..1e5a065b23c7 --- /dev/null +++ b/test/e2e/app-dir/instant-validation/app/default/invalid-use-selected-layout-segments-no-samples/[slug]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Leaf page
+} 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..f13c926ffd58 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,15 @@ 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..942ed14fec8e 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, @@ -1893,6 +1894,48 @@ describe('instant validation', () => { expectNoBuildValidationErrors(result) } }) + + // usePathname is misclassified as "uncached data" today and the dev + // overlay attributes the error to the parent's JSX line. This test + // asserts the correct stack attribution. When the URL-hook factory + // lands, `it.failing` will itself fail — flip 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') + } + ) + + it('invalid - useSelectedLayoutSegment() in a layout on a route with a fallback param', async () => { + if (!isNextDev || isClientNav) return + const browser = await navigateTo( + '/default/invalid-use-selected-layout-segment-no-samples/123' + ) + await openRedbox(browser) + const source = await getRedboxSource(browser) + expect(source).toContain('active-tab.tsx') + expect(source).toContain('ActiveTab') + }) + + it('invalid - useSelectedLayoutSegments() in a layout on a route with a fallback param', async () => { + if (!isNextDev || isClientNav) return + const browser = await navigateTo( + '/default/invalid-use-selected-layout-segments-no-samples/123' + ) + await openRedbox(browser) + const source = await getRedboxSource(browser) + expect(source).toContain('breadcrumb-trail.tsx') + expect(source).toContain('BreadcrumbTrail') + }) + /* eslint-enable jest/no-standalone-expect */ }) describe('client errors', () => {