next.js/test/e2e/next-analyze/next-analyze.test.ts
next-analyze.test.ts139 lines3.8 KB
import { nextTestSetup } from 'e2e-utils'
import { runNextCommand, shouldUseTurbopack } from 'next-test-utils'
import path from 'node:path'
import { ChildProcess, spawn } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'

describe('next experimental-analyze', () => {
  if (!shouldUseTurbopack()) {
    // Test suites require at least one test
    it('skips in non-Turbopack tests', () => {})
    return
  }

  const { next, skipped } = nextTestSetup({
    files: __dirname,
    skipStart: true,
    skipDeployment: true,
  })

  if (skipped) {
    // Test suites require at least one test
    it('is skipped', () => {})
    return
  }

  it('runs successfully without errors', async () => {
    const nextDir = path.dirname(require.resolve('next/package'))
    const nextBin = path.join(nextDir, 'dist/bin/next')

    const serveProcess = spawn(
      'node',
      [nextBin, 'experimental-analyze', '--port', '0'],
      {
        cwd: next.testDir,
        stdio: ['ignore', 'pipe', 'pipe'],
      }
    )

    try {
      const url = await waitForServer(serveProcess)
      const response = await fetch(url)
      expect(response.status).toBe(200)
      expect(await response.text()).toContain(
        '<title>Next.js Bundle Analyzer</title>'
      )
    } finally {
      serveProcess.kill()
    }
  })
  ;['-o', '--output'].forEach((flag) => {
    describe(`with ${flag} flag`, () => {
      it('writes output to .next/diagnostics/analyze path', async () => {
        const defaultOutputPath = path.join(
          next.testDir,
          '.next/diagnostics/analyze'
        )

        const { code, stderr, stdout } = await runNextCommand(
          ['experimental-analyze', flag],
          {
            cwd: next.testDir,
            stderr: true,
            stdout: true,
          }
        )

        expect(code).toBe(0)
        expect(stderr).not.toContain('Error')
        expect(stdout).toContain('.next/diagnostics/analyze')

        expect(existsSync(defaultOutputPath)).toBe(true)
        for (const file of [
          'index.html',
          'data/routes.json',
          'data/modules.data',
          'data/analyze.data',
        ]) {
          expect(existsSync(path.join(defaultOutputPath, file))).toBe(true)
        }

        const routesJson = readFileSync(
          path.join(defaultOutputPath, 'data', 'routes.json'),
          'utf-8'
        )
        const routes = JSON.parse(routesJson)
        expect(routes).toEqual(['/', '/_not-found'])
      })
    })
  })
})

function waitForServer(process: ChildProcess, timeoutMs: number = 30000) {
  const serverUrlPromise = new Promise<string>((resolve, reject) => {
    const timeout = setTimeout(() => {
      process.stdout.off('data', onStdout)
      process.off('error', onError)
      process.off('exit', onExit)
      reject(new Error('Server did not start within timeout'))
    }, timeoutMs)

    function onStdout(data: Buffer) {
      const urlMatch = data.toString().match(/http:\/\/[^\s]+/)
      if (urlMatch) {
        clearTimeout(timeout)
        process.stdout.off('data', onStdout)
        process.off('error', onError)
        process.off('exit', onExit)
        resolve(urlMatch[0])
      }
    }

    function onError(error: Error) {
      clearTimeout(timeout)
      process.stdout.off('data', onStdout)
      process.off('error', onError)
      process.off('exit', onExit)
      reject(error)
    }

    function onExit(code: number) {
      clearTimeout(timeout)
      process.stdout.off('data', onStdout)
      process.off('error', onError)
      process.off('exit', onExit)
      reject(
        new Error(
          `Server process exited with code ${code} before URL was emitted`
        )
      )
    }

    process.stdout.on('data', onStdout)
    process.on('error', onError)
    process.on('exit', onExit)
  })

  return serverUrlPromise
}
Quest for Codev2.0.0
/
SIGN IN