next.js/test/e2e/app-dir/not-found/basic/index.test.ts
index.test.ts182 lines6.2 KB
import { nextTestSetup } from 'e2e-utils'
import { check } from 'next-test-utils'

describe('app dir - not-found - basic', () => {
  const { next, isNextDev, isNextStart, skipped } = nextTestSetup({
    files: __dirname,
    skipDeployment: true,
  })

  if (skipped) {
    return
  }

  it("should propagate notFound errors past a segment's error boundary", async () => {
    let browser = await next.browser('/error-boundary')
    await browser.elementByCss('button').click()
    expect(await browser.elementByCss('h1').text()).toBe('Root Not Found')

    browser = await next.browser('/error-boundary/nested/nested-2')
    await browser.elementByCss('button').click()
    expect(await browser.elementByCss('h1').text()).toBe(
      'Not Found (error-boundary/nested)'
    )

    browser = await next.browser('/error-boundary/nested/trigger-not-found')
    expect(await browser.elementByCss('h1').text()).toBe(
      'Not Found (error-boundary/nested)'
    )
  })

  it('should return 404 status code for custom not-found page', async () => {
    const res = await next.fetch('/_not-found')
    expect(res.status).toBe(404)
  })

  if (isNextStart) {
    it('should include not found client reference manifest in the file trace', async () => {
      const fileTrace = JSON.parse(
        await next.readFile('.next/server/app/_not-found/page.js.nft.json')
      )

      const isTraced = fileTrace.files.some((filePath) =>
        filePath.includes('page_client-reference-manifest.js')
      )

      expect(isTraced).toBe(true)
    })

    it('should not output /404 in tree view logs', async () => {
      const output = await next.cliOutput
      expect(output).not.toContain('○ /404')
    })

    it('should use root not-found content for 404 html', async () => {
      // static /404 page will use /_not-found content
      const page404 = await next.readFile('.next/server/pages/404.html')
      expect(page404).toContain('Root Not Found')
    })
  }

  const runTests = ({ isEdge }: { isEdge: boolean }) => {
    it('should return 404 status if notFound() is called in shell', async () => {
      const res = await next.fetch('/shell-not-found')

      expect(res.status).toBe(404)
      expect(await res.text()).toInclude('"noindex"')
    })

    it('should use the not-found page for non-matching routes', async () => {
      const browser = await next.browser('/random-content')
      expect(await browser.elementByCss('h1').text()).toContain(
        'Root Not Found'
      )
      // should contain root layout content
      expect(await browser.elementByCss('#layout-nav').text()).toBe('Navbar')
    })

    it('should match dynamic route not-found boundary correctly', async () => {
      // `/dynamic` display works
      const browserDynamic = await next.browser('/dynamic')
      expect(await browserDynamic.elementByCss('main').text()).toBe('dynamic')

      // `/dynamic/404` calling notFound() will match the same level not-found boundary
      const browserDynamic404 = await next.browser('/dynamic/404')
      expect(await browserDynamic404.elementByCss('#not-found').text()).toBe(
        'dynamic/[id] not found'
      )

      const browserDynamicId = await next.browser('/dynamic/123')
      expect(await browserDynamicId.elementByCss('#page').text()).toBe(
        'dynamic [id]'
      )
    })

    it('should escalate notFound to parent layout if no not-found boundary present in current layer', async () => {
      const browserDynamic = await next.browser(
        '/dynamic-layout-without-not-found'
      )
      expect(await browserDynamic.elementByCss('h1').text()).toBe(
        'Dynamic with Layout'
      )

      // no not-found boundary in /dynamic-layout-without-not-found, escalate to parent layout to render root not-found
      const browserDynamicId = await next.browser(
        '/dynamic-layout-without-not-found/404'
      )
      expect(await browserDynamicId.elementByCss('h1').text()).toBe(
        'Root Not Found'
      )

      const browserDynamic404 = await next.browser(
        '/dynamic-layout-without-not-found/123'
      )
      expect(await browserDynamic404.elementByCss('#page').text()).toBe(
        'dynamic-layout-without-not-found [id]'
      )
    })

    if (isNextDev) {
      it('should not reload the page', async () => {
        const browser = await next.browser('/random-content')
        const timestamp = await browser.elementByCss('#timestamp').text()

        await new Promise((resolve) => {
          setTimeout(resolve, 3000)
        })

        await check(async () => {
          const newTimestamp = await browser.elementByCss('#timestamp').text()
          return newTimestamp !== timestamp ? 'failure' : 'success'
        }, 'success')
      })

      // Disabling for Edge because it is too flakey.
      // @TODO investigate a proper for fix for this flake
      if (!isEdge) {
        it('should render the 404 page when the file is removed, and restore the page when re-added', async () => {
          const browser = await next.browser('/')
          await check(() => browser.elementByCss('h1').text(), 'My page')
          await next.renameFile('./app/page.js', './app/foo.js')
          await check(() => browser.elementByCss('h1').text(), 'Root Not Found')
          await next.renameFile('./app/foo.js', './app/page.js')
          await check(() => browser.elementByCss('h1').text(), 'My page')
        })
      }
    }

    if (!isNextDev && !isEdge) {
      it('should create the 404 mapping and copy the file to pages', async () => {
        const html = await next.readFile('.next/server/pages/404.html')
        expect(html).toContain('Root Not Found')
        expect(
          await next.readFile('.next/server/pages-manifest.json')
        ).toContain('"pages/404.html"')
      })
    }
  }

  describe('with default runtime', () => {
    runTests({ isEdge: false })
  })

  describe('with runtime = edge', () => {
    let originalLayout = ''

    beforeAll(async () => {
      await next.stop()
      originalLayout = await next.readFile('app/layout.js')
      await next.patchFile(
        'app/layout.js',
        `export const runtime = 'edge'\n${originalLayout}`
      )
      await next.start()
    })
    afterAll(async () => {
      await next.patchFile('app/layout.js', originalLayout)
    })

    runTests({ isEdge: true })
  })
})
Quest for Codev2.0.0
/
SIGN IN