next.js/test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts
instant-validation-build.test.ts956 lines47.9 KB
import { nextTestSetup } from 'e2e-utils'
import {
  expectNoBuildValidationErrors,
  extractBuildValidationError,
  parseValidationMessages,
} from 'e2e-utils/instant-validation'

describe('instant-validation-build', () => {
  const { next, skipped, isNextStart, isTurbopack } = nextTestSetup({
    files: __dirname,
    skipStart: true,
    skipDeployment: true,
  })

  if (skipped) {
    return
  }
  if (!isNextStart) {
    it.skip('Build-time only test', () => {})
    return
  }
  if (!isTurbopack) {
    // TODO(instant-validation-build): snapshot tests for webpack
    it.skip('TODO: snapshot tests for webpack', () => {})
    return
  }

  const prerender = async (pathname: string) => {
    const args = [
      '--experimental-build-mode',
      'generate',
      '--debug-build-paths',
      `app${pathname}/page.tsx`,
    ]
    return await next.build({
      args,
      env: {
        NEXT_TEST_LOG_VALIDATION: '1',
      },
    })
  }

  beforeAll(async () => {
    await next.build({ args: ['--experimental-build-mode', 'compile'] })
  })

  describe('basic dynamic hole detection', () => {
    // We have extensive tests for this in the instant-validation test suite.
    // This is just a basic test that we can validate a runtime prefetch, which static shell validation can't do.
    describe('valid - suspense around runtime', () => {
      it('should succeed build when cookies are inside Suspense', async () => {
        const result = await prerender(
          '/(default)/valid-suspense-around-runtime'
        )
        expectNoBuildValidationErrors(result)
      })
    })

    describe('invalid - missing suspense around runtime', () => {
      it('should fail build when cookies are outside Suspense', async () => {
        const result = await prerender(
          '/(default)/invalid-missing-suspense-around-runtime'
        )
        expect(extractBuildValidationError(result.cliOutput))
          .toMatchInlineSnapshot(`
         "Error: Route "/invalid-missing-suspense-around-runtime": Next.js encountered uncached data during the initial render.

         \`fetch(...)\` or \`connection()\` accessed outside of \`<Suspense>\` blocks navigation, leading to a slower user experience.

         Ways to fix this:
           - Cache the data access with \`"use cache"\`
           - Move the data access into a child component within a <Suspense> boundary
           - Set \`export const instant = false\` to allow a blocking route

         Learn more: https://nextjs.org/docs/messages/blocking-route
             at main (<anonymous>)
             at body (<anonymous>)
             at html (<anonymous>)
         Build-time instant validation failed for route "/invalid-missing-suspense-around-runtime".
         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 "/invalid-missing-suspense-around-runtime" 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.exitCode).toBe(1)
      })
    })
  })

  describe('server errors', () => {
    it('valid - ignores server errors that do not surface in SSR', async () => {
      const result = await prerender(
        '/(default)/server-errors/error-passed-to-client-and-ignored'
      )
      expect(extractBuildValidationError(result.cliOutput)).toBe('')
      expectNoBuildValidationErrors(result)
    })

    it('error - server error that blocks page validation with no suspense boundary', async () => {
      const result = await prerender('/(default)/server-errors/page-throws')
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/server-errors/page-throws": Could not validate \`unstable_instant\` because an error prevented the target segment from rendering.
           at main (<anonymous>)
           at body (<anonymous>)
           at html (<anonymous>) {
         [cause]: Error: Kaboom
             at a (app/(default)/server-errors/page-throws/page.tsx:22:9)
           20 | async function Throws(): Promise<never> {
           21 |   await cookies()
         > 22 |   throw new Error('Kaboom')
              |         ^
           23 | }
           24 | {
           digest: '<error-digest>'
         }
       }
       Build-time instant validation failed for route "/server-errors/page-throws".
       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 "/server-errors/page-throws" 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.exitCode).toBe(1)
    })

    it('error - server error that blocks page validation with a suspense boundary in a parent segment', async () => {
      const result = await prerender(
        '/(default)/server-errors/page-throws-with-suspense'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/server-errors/page-throws-with-suspense": Could not validate \`unstable_instant\` because an error prevented the target segment from rendering.
           at main (<anonymous>)
           at a (<anonymous>)
           at body (<anonymous>)
           at html (<anonymous>) {
         [cause]: Error: Kaboom
             at b (app/(default)/server-errors/page-throws-with-suspense/page.tsx:22:9)
           20 | async function Throws(): Promise<never> {
           21 |   await cookies()
         > 22 |   throw new Error('Kaboom')
              |         ^
           23 | }
           24 | {
           digest: '<error-digest>'
         }
       }
       Build-time instant validation failed for route "/server-errors/page-throws-with-suspense".
       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 "/server-errors/page-throws-with-suspense" 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.exitCode).toBe(1)
    })
  })

  describe('client errors', () => {
    it('error - server error that blocks page validation with no suspense boundary', async () => {
      const result = await prerender('/(default)/client-errors/page-throws')
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/client-errors/page-throws": Could not validate \`unstable_instant\` because an error prevented the target segment from rendering.
           at <unknown> (app/(default)/client-errors/page-throws/client.tsx:6:3)
           at main (<anonymous>)
           at body (<anonymous>)
           at html (<anonymous>)
         4 |
         5 | export function ThrowsInClient(): Promise<never> {
       > 6 |   useSearchParams()
           |   ^
         7 |   throw new Error('Kaboom')
         8 | }
         9 | {
         [cause]: Error: Kaboom
             at <unknown> (app/(default)/client-errors/page-throws/client.tsx:7:9)
           5 | export function ThrowsInClient(): Promise<never> {
           6 |   useSearchParams()
         > 7 |   throw new Error('Kaboom')
             |         ^
           8 | }
           9 |
       }
       Build-time instant validation failed for route "/client-errors/page-throws".
       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 "/client-errors/page-throws" 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.exitCode).toBe(1)
    })

    it('error - client error that blocks page validation with a suspense boundary in a parent segment', async () => {
      const result = await prerender(
        '/(default)/client-errors/page-throws-with-suspense'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/client-errors/page-throws-with-suspense": Could not validate \`unstable_instant\` because an error prevented the target segment from rendering.
           at <unknown> (app/(default)/client-errors/page-throws-with-suspense/client.tsx:6:3)
           at main (<anonymous>)
           at a (<anonymous>)
           at body (<anonymous>)
           at html (<anonymous>)
         4 |
         5 | export function ThrowsInClient(): Promise<never> {
       > 6 |   useSearchParams()
           |   ^
         7 |   throw new Error('Kaboom')
         8 | }
         9 | {
         [cause]: Error: Kaboom
             at <unknown> (app/(default)/client-errors/page-throws-with-suspense/client.tsx:7:9)
           5 | export function ThrowsInClient(): Promise<never> {
           6 |   useSearchParams()
         > 7 |   throw new Error('Kaboom')
             |         ^
           8 | }
           9 |
       }
       Build-time instant validation failed for route "/client-errors/page-throws-with-suspense".
       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 "/client-errors/page-throws-with-suspense" 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.exitCode).toBe(1)
    })
  })

  describe('searchParams', () => {
    it('search params are correctly read from samples', async () => {
      const result = await prerender(
        '/(default)/search-params/valid-search-params-in-samples'
      )
      expect(result.cliOutput).not.toContain('AssertionError')
      expectNoBuildValidationErrors(result)
    })

    it('error - accessing search param not present in samples', async () => {
      const result = await prerender(
        '/(default)/search-params/invalid-undeclared-search-param'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/search-params/invalid-undeclared-search-param" accessed searchParam "undeclared" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`searchParams\` object, or \`{ "undeclared": null }\` if it should be absent.
           at <unknown> (app/(default)/search-params/invalid-undeclared-search-param/page.tsx:29:14)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/search-params/invalid-undeclared-search-param/page.tsx:28:3)
         27 |   const sp = await searchParams
         28 |   ensureThrows(
       > 29 |     () => sp.undeclared,
            |              ^
         30 |     \`Expected accessing an undeclared search param to throw\`
         31 |   )
         32 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/search-params/invalid-undeclared-search-param".
       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 "/search-params/invalid-undeclared-search-param" 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('useSearchParams() receives search params from samples', async () => {
      const result = await prerender(
        '/(default)/search-params/valid-use-search-params'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('error - accessing search param not present in samples and catching the error', async () => {
      const result = await prerender(
        '/(default)/search-params/invalid-undeclared-search-param-caught'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/search-params/invalid-undeclared-search-param-caught" accessed searchParam "undeclared" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`searchParams\` object, or \`{ "undeclared": null }\` if it should be absent.
           at <unknown> (app/(default)/search-params/invalid-undeclared-search-param-caught/page.tsx:33:16)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/search-params/invalid-undeclared-search-param-caught/page.tsx:32:5)
         31 |   try {
         32 |     ensureThrows(
       > 33 |       () => sp.undeclared,
            |                ^
         34 |       \`Expected accessing an undeclared search param to throw\`
         35 |     )
         36 |   } catch (err) { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/search-params/invalid-undeclared-search-param-caught".
       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 "/search-params/invalid-undeclared-search-param-caught" 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 - accessing undeclared search param via useSearchParams()', async () => {
      const result = await prerender(
        '/(default)/search-params/invalid-undeclared-use-search-params'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/search-params/invalid-undeclared-use-search-params" accessed searchParam "undeclared" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`searchParams\` object, or \`{ "undeclared": null }\` if it should be absent.
           at <unknown> (app/(default)/search-params/invalid-undeclared-use-search-params/search-params-reader.tsx:9:14)
           at <unknown> (ensure-error.ts:11:5)
           at <unknown> (app/(default)/search-params/invalid-undeclared-use-search-params/search-params-reader.tsx:8:3)
          7 |   const sp = useSearchParams()
          8 |   ensureThrows(
       >  9 |     () => sp.get('undeclared'),
            |              ^
         10 |     \`Expected accessing an undeclared search param to throw\`
         11 |   )
         12 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/search-params/invalid-undeclared-use-search-params".
       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 "/search-params/invalid-undeclared-use-search-params" 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 - accessing undeclared search param via useSearchParams() and catching the error', async () => {
      const result = await prerender(
        '/(default)/search-params/invalid-undeclared-use-search-params-caught'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/search-params/invalid-undeclared-use-search-params-caught" accessed searchParam "undeclared" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`searchParams\` object, or \`{ "undeclared": null }\` if it should be absent.
           at <unknown> (app/(default)/search-params/invalid-undeclared-use-search-params-caught/search-params-reader.tsx:10:16)
           at <unknown> (ensure-error.ts:11:5)
           at <unknown> (app/(default)/search-params/invalid-undeclared-use-search-params-caught/search-params-reader.tsx:9:5)
          8 |   try {
          9 |     ensureThrows(
       > 10 |       () => sp.get('undeclared'),
            |                ^
         11 |       \`Expected accessing an undeclared search param to throw\`
         12 |     )
         13 |   } catch { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/search-params/invalid-undeclared-use-search-params-caught".
       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 "/search-params/invalid-undeclared-use-search-params-caught" 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('valid - awaited search params passed to a cache', async () => {
      const result = await prerender(
        '/(default)/search-params/valid-search-params-passed-to-cache'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - awaited search params passed to a client component', async () => {
      const result = await prerender(
        '/(default)/search-params/valid-search-params-passed-to-client'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('headers', () => {
    it('headers are correctly read from samples', async () => {
      const result = await prerender(
        '/(default)/headers/valid-headers-in-samples'
      )
      expect(result.cliOutput).not.toContain('AssertionError')
      expectNoBuildValidationErrors(result)
    })

    it('error - .get() of header not present in samples', async () => {
      const result = await prerender(
        '/(default)/headers/invalid-undeclared-header-get'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/headers/invalid-undeclared-header-get" accessed header "undeclaredheader" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`headers\` array, or \`["undeclaredheader", null]\` if it should be absent.
           at <unknown> (app/(default)/headers/invalid-undeclared-header-get/page.tsx:25:24)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/headers/invalid-undeclared-header-get/page.tsx:24:3)
         23 |   const headersStore = await headers()
         24 |   ensureThrows(
       > 25 |     () => headersStore.get('undeclaredHeader'),
            |                        ^
         26 |     \`Expected get() to throw for undeclared headers\`
         27 |   )
         28 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/headers/invalid-undeclared-header-get".
       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 "/headers/invalid-undeclared-header-get" 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 - .get() of header not present in samples and catching the error', async () => {
      const result = await prerender(
        '/(default)/headers/invalid-undeclared-header-get-caught'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/headers/invalid-undeclared-header-get-caught" accessed header "undeclaredheader" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`headers\` array, or \`["undeclaredheader", null]\` if it should be absent.
           at <unknown> (app/(default)/headers/invalid-undeclared-header-get-caught/page.tsx:28:25)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/headers/invalid-undeclared-header-get-caught/page.tsx:27:5)
         26 |   try {
         27 |     ensureThrows(
       > 28 |       () => headerStore.get('undeclaredHeader'),
            |                         ^
         29 |       \`Expected get() to throw for undeclared headers\`
         30 |     )
         31 |   } catch (err) { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/headers/invalid-undeclared-header-get-caught".
       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 "/headers/invalid-undeclared-header-get-caught" 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.exitCode).toBe(1)
    })

    it('error - .has() of header not present in samples', async () => {
      const result = await prerender(
        '/(default)/headers/invalid-undeclared-header-has'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/headers/invalid-undeclared-header-has" accessed header "undeclaredheader" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`headers\` array, or \`["undeclaredheader", null]\` if it should be absent.
           at <unknown> (app/(default)/headers/invalid-undeclared-header-has/page.tsx:25:23)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/headers/invalid-undeclared-header-has/page.tsx:24:3)
         23 |   const headerStore = await headers()
         24 |   ensureThrows(
       > 25 |     () => headerStore.has('undeclaredHeader'),
            |                       ^
         26 |     \`Expected has() to throw for undeclared headers\`
         27 |   )
         28 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/headers/invalid-undeclared-header-has".
       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 "/headers/invalid-undeclared-header-has" 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('valid - header value passed to a cache', async () => {
      const result = await prerender(
        '/(default)/headers/valid-headers-passed-to-cache'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - header value passed to a client component', async () => {
      const result = await prerender(
        '/(default)/headers/valid-headers-passed-to-client'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('cookies', () => {
    it('cookies are correctly read from samples', async () => {
      const result = await prerender(
        '/(default)/cookies/valid-cookies-in-samples'
      )
      expect(result.cliOutput).not.toContain('AssertionError')
      expectNoBuildValidationErrors(result)
    })

    it('error - .get() of cookie not present in samples', async () => {
      const result = await prerender(
        '/(default)/cookies/invalid-undeclared-cookie-get'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/cookies/invalid-undeclared-cookie-get" accessed cookie "undeclaredCookie" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`cookies\` array, or \`{ name: "undeclaredCookie", value: null }\` if it should be absent.
           at <unknown> (app/(default)/cookies/invalid-undeclared-cookie-get/page.tsx:26:23)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/cookies/invalid-undeclared-cookie-get/page.tsx:25:3)
         24 |
         25 |   ensureThrows(
       > 26 |     () => cookieStore.get('undeclaredCookie'),
            |                       ^
         27 |     \`Expected get() to throw for undeclared cookies\`
         28 |   )
         29 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/cookies/invalid-undeclared-cookie-get".
       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 "/cookies/invalid-undeclared-cookie-get" 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 - .get() of cookie not present in samples and catching the error', async () => {
      const result = await prerender(
        '/(default)/cookies/invalid-undeclared-cookie-get-caught'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/cookies/invalid-undeclared-cookie-get-caught" accessed cookie "undeclaredCookie" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`cookies\` array, or \`{ name: "undeclaredCookie", value: null }\` if it should be absent.
           at <unknown> (app/(default)/cookies/invalid-undeclared-cookie-get-caught/page.tsx:28:25)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/cookies/invalid-undeclared-cookie-get-caught/page.tsx:27:5)
         26 |   try {
         27 |     ensureThrows(
       > 28 |       () => cookieStore.get('undeclaredCookie'),
            |                         ^
         29 |       \`Expected get() to throw for undeclared cookies\`
         30 |     )
         31 |   } catch (err) { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/cookies/invalid-undeclared-cookie-get-caught".
       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 "/cookies/invalid-undeclared-cookie-get-caught" 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 - .has() of cookie not present in samples', async () => {
      const result = await prerender(
        '/(default)/cookies/invalid-undeclared-cookie-has'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/cookies/invalid-undeclared-cookie-has" accessed cookie "undeclaredCookie" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`cookies\` array, or \`{ name: "undeclaredCookie", value: null }\` if it should be absent.
           at <unknown> (app/(default)/cookies/invalid-undeclared-cookie-has/page.tsx:25:23)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/cookies/invalid-undeclared-cookie-has/page.tsx:24:3)
         23 |   const cookieStore = await cookies()
         24 |   ensureThrows(
       > 25 |     () => cookieStore.has('undeclaredCookie'),
            |                       ^
         26 |     \`Expected has() to throw for undeclared cookies\`
         27 |   )
         28 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/cookies/invalid-undeclared-cookie-has".
       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 "/cookies/invalid-undeclared-cookie-has" 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('valid - cookies passed to a cache', async () => {
      const result = await prerender(
        '/(default)/cookies/valid-cookies-passed-to-cache'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('params', () => {
    it('valid - params are correctly read from samples', async () => {
      const result = await prerender(
        '/(default)/params/valid-params-in-samples/[one]/[two]'
      )
      expect(result.cliOutput).not.toContain('AssertionError')
      expectNoBuildValidationErrors(result)
    })

    it('error - reading a param not present in samples', async () => {
      const result = await prerender(
        '/(default)/params/invalid-param-not-provided/[one]/[two]'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/params/invalid-param-not-provided/[one]/[two]" accessed param "two" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/(default)/params/invalid-param-not-provided/[one]/[two]/page.tsx:47:24)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/params/invalid-param-not-provided/[one]/[two]/page.tsx:47:3)
         45 |
         46 |   // We're not allowed to access params not in the samples.
       > 47 |   ensureThrows(() => p.two)
            |                        ^
         48 |
         49 |   // TODO: test \`in\` and iteration
         50 |   // assert.deepStrictEqual( {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/params/invalid-param-not-provided/[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 "/params/invalid-param-not-provided/[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 - reading a param not present in samples and catching the error', async () => {
      const result = await prerender(
        '/(default)/params/invalid-param-not-provided-caught/[one]/[two]'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/params/invalid-param-not-provided-caught/[one]/[two]" accessed param "two" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/(default)/params/invalid-param-not-provided-caught/[one]/[two]/page.tsx:45:26)
           at <unknown> (ensure-error.ts:11:5)
           at a (app/(default)/params/invalid-param-not-provided-caught/[one]/[two]/page.tsx:45:5)
         43 |   try {
         44 |     // We're not allowed to access params not in the samples.
       > 45 |     ensureThrows(() => p.two, \`Expected accessing an undeclared param to throw\`)
            |                          ^
         46 |   } catch (err) {
         47 |     // We swallow the error. It should still be reported and fail the validation.
         48 |   } {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/params/invalid-param-not-provided-caught/[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 "/params/invalid-param-not-provided-caught/[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('useParams() receives params from samples', async () => {
      const result = await prerender(
        '/(default)/params/valid-use-params/[one]/[two]'
      )
      expect(result.cliOutput).not.toContain('AssertionError')
      expectNoBuildValidationErrors(result)
    })

    it('error - accessing a param not present in samples via useParams()', async () => {
      const result = await prerender(
        '/(default)/params/invalid-undeclared-use-params/[one]/[two]'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/params/invalid-undeclared-use-params/[one]/[two]" accessed param "two" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/(default)/params/invalid-undeclared-use-params/[one]/[two]/params-reader.tsx:10:18)
           at <unknown> (ensure-error.ts:11:5)
           at <unknown> (app/(default)/params/invalid-undeclared-use-params/[one]/[two]/params-reader.tsx:9:3)
          8 |   // We're not allowed to access params not in the samples.
          9 |   ensureThrows(
       > 10 |     () => params.two,
            |                  ^
         11 |     \`Expected accessing an undeclared param to throw\`
         12 |   )
         13 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/params/invalid-undeclared-use-params/[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 "/params/invalid-undeclared-use-params/[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 - accessing a param not present in samples via useParams() and catching the error', async () => {
      const result = await prerender(
        '/(default)/params/invalid-undeclared-use-params-caught/[one]/[two]'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/params/invalid-undeclared-use-params-caught/[one]/[two]" accessed param "two" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/(default)/params/invalid-undeclared-use-params-caught/[one]/[two]/params-reader.tsx:11:20)
           at <unknown> (ensure-error.ts:11:5)
           at <unknown> (app/(default)/params/invalid-undeclared-use-params-caught/[one]/[two]/params-reader.tsx:10:5)
          9 |     // We're not allowed to access params not in the samples.
         10 |     ensureThrows(
       > 11 |       () => params.two,
            |                    ^
         12 |       \`Expected accessing an undeclared param to throw\`
         13 |     )
         14 |   } catch { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/params/invalid-undeclared-use-params-caught/[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 "/params/invalid-undeclared-use-params-caught/[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('valid - awaited params passed to a cache', async () => {
      const result = await prerender(
        '/(default)/params/valid-params-passed-to-cache/[slug]'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - awaited params passed to a client component', async () => {
      const result = await prerender(
        '/(default)/params/valid-params-passed-to-client/[slug]'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('pathname', () => {
    it('valid - usePathname() on a route without params', async () => {
      const result = await prerender(
        '/(default)/pathname/valid-use-pathname-no-params'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - usePathname() on a route with params (all provided in samples)', async () => {
      const result = await prerender(
        '/(default)/pathname/valid-use-pathname-with-params/[one]/[two]'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - usePathname() on a route inside a route group does not include the group segment', async () => {
      const result = await prerender(
        '/(default)/pathname/valid-use-pathname-route-group/(route-group)'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - usePathname() on a catch-all route', async () => {
      const result = await prerender(
        '/(default)/pathname/valid-use-pathname-catch-all/[...catchAll]'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('valid - usePathname() on an optional catch-all route', async () => {
      const result = await prerender(
        '/(default)/pathname/valid-use-pathname-optional-catch-all/[[...optionalCatchAll]]'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('error - usePathname() on a route with params but not all provided in samples', async () => {
      const result = await prerender(
        '/(default)/pathname/invalid-use-pathname-missing-params/[one]/[two]'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/pathname/invalid-use-pathname-missing-params/[one]/[two]" called usePathname() but param "two" is not defined in the \`samples\` of \`unstable_instant\`. usePathname() requires all route params to be provided.
           at <unknown> (app/(default)/pathname/invalid-use-pathname-missing-params/[one]/[two]/pathname-reader.tsx:9:11)
           at <unknown> (ensure-error.ts:11:5)
           at <unknown> (app/(default)/pathname/invalid-use-pathname-missing-params/[one]/[two]/pathname-reader.tsx:7:3)
          7 |   ensureThrows(
          8 |     // eslint-disable-next-line react-hooks/rules-of-hooks
       >  9 |     () => usePathname(),
            |           ^
         10 |     \`Expected usePathname() to throw when not all params are provided in samples\`
         11 |   )
         12 |   return null {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/pathname/invalid-use-pathname-missing-params/[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/[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)
    })
  })

  describe('root params', () => {
    it.each(['static', 'runtime'])(
      'valid - %s - root params are correctly read from samples',
      async (variant) => {
        const result = await prerender(
          `/root-params/[lang]/valid-root-param-in-samples/${variant}`
        )
        expectNoBuildValidationErrors(result)
        // The page asserts on the values
        expect(result.cliOutput).not.toContain('AssertionError')
      }
    )

    it('error - reading a root param not present in samples', async () => {
      const result = await prerender(
        '/root-params/[lang]/invalid-root-param-not-provided'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/root-params/[lang]/invalid-root-param-not-provided" accessed root param "lang" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/root-params/[lang]/invalid-root-param-not-provided/page.tsx:17:11)
           at a (ensure-error.ts:48:11)
           at b (app/root-params/[lang]/invalid-root-param-not-provided/page.tsx:16:9)
         15 |
         16 |   await ensureRejects(
       > 17 |     () => lang(),
            |           ^
         18 |     \`Expected lang() to error if sample is not provided\`
         19 |   )
         20 |   return ( {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/root-params/[lang]/invalid-root-param-not-provided".
       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 "/root-params/[lang]/invalid-root-param-not-provided" 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 - reading a root param not present in samples and catching the error', async () => {
      const result = await prerender(
        '/root-params/[lang]/invalid-root-param-not-provided-caught'
      )
      expect(extractBuildValidationError(result.cliOutput))
        .toMatchInlineSnapshot(`
       "Error: Route "/root-params/[lang]/invalid-root-param-not-provided-caught" accessed root param "lang" which is not defined in the \`samples\` of \`unstable_instant\`. Add it to the sample's \`params\` object.
           at <unknown> (app/root-params/[lang]/invalid-root-param-not-provided-caught/page.tsx:18:13)
           at a (ensure-error.ts:48:11)
           at b (app/root-params/[lang]/invalid-root-param-not-provided-caught/page.tsx:17:11)
         16 |   try {
         17 |     await ensureRejects(
       > 18 |       () => lang(),
            |             ^
         19 |       \`Expected lang() to error if sample is not provided\`
         20 |     )
         21 |   } catch { {
         digest: 'INSTANT_VALIDATION_ERROR'
       }
       Build-time instant validation failed for route "/root-params/[lang]/invalid-root-param-not-provided-caught".
       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 "/root-params/[lang]/invalid-root-param-not-provided-caught" 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)
    })
  })

  describe('samples precedence', () => {
    it('page samples override layout samples', async () => {
      const result = await prerender(
        '/(default)/samples-precedence/[slug]/page-overrides'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })

    it('page inherits samples from layout when it has none', async () => {
      const result = await prerender(
        '/(default)/samples-precedence/[slug]/page-inherits'
      )
      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('generateStaticParams', () => {
    it('valid - page with generateStaticParams and samples only runs validation once', async () => {
      const result = await prerender('/(default)/gsp/[slug]')

      // If validation ran once, we expect one "validation_start"/"validation_end" pair
      const validationMessages = parseValidationMessages(result.cliOutput)
      expect(validationMessages).toEqual([
        expect.objectContaining({ type: 'validation_start' }),
        expect.objectContaining({ type: 'validation_end' }),
      ])

      expectNoBuildValidationErrors(result)
      expect(result.cliOutput).not.toContain('AssertionError')
    })
  })

  describe('caches', () => {
    it('valid - static prefetch - awaiting a cache in the static stage does not require a suspense boundary', async () => {
      const result = await prerender(
        '/(default)/valid-await-cache-without-suspense/static'
      )
      expectNoBuildValidationErrors(result)
    })

    it('valid - runtime prefetch - awaiting a cache in the runtime stage does not require a suspense boundary', async () => {
      const result = await prerender(
        '/(default)/valid-await-cache-without-suspense/runtime'
      )
      expectNoBuildValidationErrors(result)
    })

    it('valid - runtime prefetch - awaiting a mix of caches in the static and runtime stages does not require a suspense boundary', async () => {
      const result = await prerender(
        '/(default)/valid-await-cache-without-suspense/mixed'
      )
      expectNoBuildValidationErrors(result)
    })

    it('valid - runtime prefetch - awaiting a private cache in the runtime stage does not require a suspense boundary', async () => {
      const result = await prerender(
        '/(default)/valid-await-cache-without-suspense/private'
      )
      expectNoBuildValidationErrors(result)
    })
  })
})
Quest for Codev2.0.0
/
SIGN IN