next.js/test/e2e/module-layer/module-layer.test.ts
module-layer.test.ts147 lines4.8 KB
import { nextTestSetup } from 'e2e-utils'
import {
  waitForRedbox,
  getRedboxSource,
  retry,
  waitForNoRedbox,
} from 'next-test-utils'

describe('module layer', () => {
  const { next, isNextStart, isNextDev, isNextDeploy } = nextTestSetup({
    files: __dirname,
  })

  function runTests() {
    const routes = [
      // app client components pages
      '/app/client',
      '/app/client-edge',
      // app sever components pages
      '/app/server',
      '/app/server-edge',
      // app routes
      '/app/route',
      '/app/route-edge',
      // pages/api
      '/api/default',
      '/api/default-edge',
      '/api/server-only',
      '/api/server-only-edge',
      '/api/mixed',
    ]

    for (const route of routes) {
      it(`should render routes marked with restriction marks without errors ${route}`, async () => {
        const { status } = await next.fetch(route)
        expect([route, status]).toEqual([route, 200])
      })
    }

    it('should render installed react-server condition for middleware', async () => {
      const json = await next.fetch('/middleware').then((res) => res.json())
      expect(json.React).toContain('version') // basic react-server export
      expect(json.React).not.toContain('useEffect') // no client api export
    })

    // This is for backward compatibility, don't change react usage in existing pages/api
    it('should contain client react exports for pages api', async () => {
      async function verifyReactExports(route, isEdge) {
        const json = await next.fetch(route).then((res) => res.json())
        // contain all react-server and default condition exports
        expect(json.React).toContain('version')
        expect(json.React).toContain('useEffect')

        // contain react-dom-server default condition exports
        expect(json.ReactDomServer).toContain('version')
        expect(json.ReactDomServer).toContain('renderToString')
        expect(json.ReactDomServer).toContain('renderToStaticMarkup')
        if (isEdge) {
          expect(json.ReactDomServer).toContain('renderToReadableStream')
        } else if (isNextDeploy) {
          // Deploy mode can expose either server stream API depending on runtime
          // and bundling path, so accept both for non-edge pages/api coverage.
          expect(
            json.ReactDomServer.some(
              (name) =>
                name === 'renderToPipeableStream' ||
                name === 'renderToReadableStream'
            )
          ).toBe(true)
        } else {
          expect(json.ReactDomServer).toContain('renderToPipeableStream')
        }
      }

      await verifyReactExports('/api/default', false)
      await verifyReactExports('/api/default-edge', true)
      await verifyReactExports('/api/server-only', false)
      await verifyReactExports('/api/server-only-edge', true)
    })

    if (isNextStart) {
      it('should log the build info properly', async () => {
        const cliOutput = next.cliOutput
        expect(cliOutput).toContain('Middleware')

        const functionsManifest = JSON.parse(
          await next.readFile('.next/server/functions-config-manifest.json')
        )
        expect(functionsManifest.functions).toContainKeys([
          '/app/route-edge',
          '/api/default-edge',
          '/api/server-only-edge',
          '/app/client-edge',
          '/app/server-edge',
        ])
        const pagesManifest = JSON.parse(
          await next.readFile('.next/server/pages-manifest.json')
        )
        const middlewareManifest = JSON.parse(
          await next.readFile('.next/server/middleware-manifest.json')
        )
        expect(middlewareManifest.middleware).toBeTruthy()
        expect(pagesManifest).toContainKeys([
          '/api/default-edge',
          '/pages-ssr',
          '/api/default',
          '/api/server-only',
        ])
      })
    }
  }

  if (isNextDev) {
    describe('client packages in middleware', () => {
      it('should error when import server packages in middleware', async () => {
        const browser = await next.browser('/')

        await next.patchFile(
          'middleware.js',
          (c) =>
            c
              .replace("import 'server-only'", "// import 'server-only'")
              .replace(
                "// import './lib/mixed-lib'",
                "import './lib/mixed-lib'"
              ),
          async () => {
            await retry(async () => {
              await waitForRedbox(browser)
              const source = await getRedboxSource(browser)
              expect(source).toContain(
                `You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client"`
              )
            })
          }
        )

        await waitForNoRedbox(browser)
      })
    })
  }

  describe('with server-only in server targets', () => {
    runTests()
  })
})
Quest for Codev2.0.0
/
SIGN IN