next.js/test/integration/create-next-app/lib/utils.ts
utils.ts192 lines4.4 KB
/**
 * @fileoverview
 *
 * This file contains utilities for  `create-next-app` testing.
 */

import { execSync, spawn, SpawnOptions } from 'child_process'
import { existsSync } from 'fs'
import { join, resolve } from 'path'
import glob from 'glob'
import Conf from 'next/dist/compiled/conf'

import {
  getProjectSetting,
  mapSrcFiles,
  projectSpecification,
} from './specification'
import {
  CustomTemplateOptions,
  DefaultTemplateOptions,
  ProjectDeps,
  ProjectFiles,
} from './types'

const cli = require.resolve('create-next-app/dist/index.js')

/**
 * Run the built version of `create-next-app` with the given arguments.
 */
export const createNextApp = (
  args: string[],
  options?: SpawnOptions,
  testVersion?: string,
  clearPreferences: boolean = true
) => {
  const conf = new Conf({ projectName: 'create-next-app' })
  if (clearPreferences) {
    conf.clear()
  }

  console.log(`[TEST] $ ${cli} ${args.join(' ')}`, { options })

  const cloneEnv = { ...process.env }
  // unset CI env as this skips the auto-install behavior
  // being tested
  delete cloneEnv.CI
  delete cloneEnv.CIRCLECI
  delete cloneEnv.GITHUB_ACTIONS
  delete cloneEnv.CONTINUOUS_INTEGRATION
  delete cloneEnv.RUN_ID
  delete cloneEnv.BUILD_NUMBER

  cloneEnv.NEXT_PRIVATE_TEST_VERSION = testVersion || 'canary'

  return spawn('node', [cli].concat(args), {
    ...options,
    env: {
      ...cloneEnv,
      ...options.env,
    },
  })
}

export const projectShouldHaveNoGitChanges = ({
  cwd,
  projectName,
}: DefaultTemplateOptions) => {
  const projectDirname = join(cwd, projectName)

  try {
    execSync('git diff --quiet', { cwd: projectDirname })
  } catch {
    execSync('git status', { cwd: projectDirname, stdio: 'inherit' })
    execSync('git --no-pager diff', { cwd: projectDirname, stdio: 'inherit' })

    throw new Error('Found unexpected git changes.')
  }
}

export const projectFilesShouldExist = ({
  cwd,
  projectName,
  files,
}: ProjectFiles) => {
  const projectRoot = resolve(cwd, projectName)
  for (const file of files) {
    try {
      expect(existsSync(resolve(projectRoot, file))).toBe(true)
    } catch (err) {
      require('console').error(
        `missing expected file ${file}`,
        glob.sync('**/*', { cwd, ignore: '**/node_modules/**' }),
        files
      )
      throw err
    }
  }
}

export const projectFilesShouldNotExist = ({
  cwd,
  projectName,
  files,
}: ProjectFiles) => {
  const projectRoot = resolve(cwd, projectName)
  for (const file of files) {
    try {
      expect(existsSync(resolve(projectRoot, file))).toBe(false)
    } catch (err) {
      require('console').error(
        `unexpected file present ${file}`,
        glob.sync('**/*', { cwd, ignore: '**/node_modules/**' }),
        files
      )
      throw err
    }
  }
}

export const projectDepsShouldBe = ({
  cwd,
  projectName,
  type,
  deps,
}: ProjectDeps) => {
  const projectRoot = resolve(cwd, projectName)
  const pkgJson = require(resolve(projectRoot, 'package.json'))
  expect(Object.keys(pkgJson[type] || {}).sort()).toEqual(deps.sort())
}

export const shouldBeTemplateProject = ({
  cwd,
  projectName,
  template,
  mode,
  srcDir,
}: CustomTemplateOptions) => {
  projectFilesShouldExist({
    cwd,
    projectName,
    files: getProjectSetting({ template, mode, setting: 'files', srcDir }),
  })

  // Tailwind templates share the same files (tailwind.config.mjs, postcss.config.mjs)
  if (
    !['app-tw', 'app-tw-empty', 'default-tw', 'default-tw-empty'].includes(
      template
    )
  ) {
    projectFilesShouldNotExist({
      cwd,
      projectName,
      files: mapSrcFiles(
        projectSpecification[template][mode === 'js' ? 'ts' : 'js'].files,
        srcDir
      ),
    })
  }

  projectDepsShouldBe({
    type: 'dependencies',
    cwd,
    projectName,
    deps: getProjectSetting({ template, mode, setting: 'deps' }),
  })

  projectDepsShouldBe({
    type: 'devDependencies',
    cwd,
    projectName,
    deps: getProjectSetting({ template, mode, setting: 'devDeps' }),
  })
}

export const shouldBeJavascriptProject = ({
  cwd,
  projectName,
  template,
  srcDir,
}: Omit<CustomTemplateOptions, 'mode'>) => {
  shouldBeTemplateProject({ cwd, projectName, template, mode: 'js', srcDir })
}

export const shouldBeTypescriptProject = ({
  cwd,
  projectName,
  template,
  srcDir,
}: Omit<CustomTemplateOptions, 'mode'>) => {
  shouldBeTemplateProject({ cwd, projectName, template, mode: 'ts', srcDir })
}
Quest for Codev2.0.0
/
SIGN IN