Files
next.js/test/integration/create-next-app/prompts.test.ts
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

358 lines
9.3 KiB
TypeScript

import { check } from 'next-test-utils'
import { join } from 'path'
import { createNextApp, projectFilesShouldExist, useTempDir } from './utils'
describe('create-next-app prompts', () => {
let nextTgzFilename: string
beforeAll(() => {
if (!process.env.NEXT_TEST_PKG_PATHS) {
throw new Error('This test needs to be run with `node run-tests.js`.')
}
const pkgPaths = new Map<string, string>(
JSON.parse(process.env.NEXT_TEST_PKG_PATHS)
)
nextTgzFilename = pkgPaths.get('next')
})
it('should prompt user for choice if directory name is absent', async () => {
await useTempDir(async (cwd) => {
const projectName = 'no-dir-name'
const childProcess = createNextApp(
[
'--ts',
'--app',
'--eslint',
'--no-src-dir',
'--no-tailwind',
'--no-import-alias',
'--no-react-compiler',
'--no-agents-md',
],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>((resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: ['package.json'],
})
resolve()
})
// enter project name
childProcess.stdin.write(`${projectName}\n`)
})
const pkg = require(join(cwd, projectName, 'package.json'))
expect(pkg.name).toBe(projectName)
})
})
it('should prompt user for choice if --js or --ts flag is absent', async () => {
await useTempDir(async (cwd) => {
const projectName = 'ts-js'
const childProcess = createNextApp(
[
projectName,
'--app',
'--eslint',
'--no-tailwind',
'--no-src-dir',
'--no-import-alias',
'--no-react-compiler',
'--no-agents-md',
],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>((resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: ['tsconfig.json'],
})
resolve()
})
// select default choice: typescript
childProcess.stdin.write('\n')
})
})
})
it('should prompt user for choice if --tailwind is absent', async () => {
await useTempDir(async (cwd) => {
const projectName = 'tw'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--app',
'--eslint',
'--no-src-dir',
'--no-import-alias',
'--no-react-compiler',
'--no-agents-md',
],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>((resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: ['postcss.config.mjs'],
})
resolve()
})
// select default choice: tailwind
childProcess.stdin.write('\n')
})
})
})
it('should prompt user for choice if --import-alias is absent', async () => {
await useTempDir(async (cwd) => {
const projectName = 'import-alias'
const childProcess = createNextApp(
[
projectName,
'--ts',
'--app',
'--eslint',
'--no-tailwind',
'--no-src-dir',
'--no-react-compiler',
'--no-agents-md',
],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>(async (resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
resolve()
})
let output = ''
childProcess.stdout.on('data', (data) => {
output += data
process.stdout.write(data)
})
// cursor forward, choose 'Yes' for custom import alias
childProcess.stdin.write('\u001b[C\n')
// used check here since it needs to wait for the prompt
await check(() => output, /What import alias would you like configured/)
childProcess.stdin.write('@/something/*\n')
})
const tsConfig = require(join(cwd, projectName, 'tsconfig.json'))
expect(tsConfig.compilerOptions.paths).toMatchInlineSnapshot(`
{
"@/something/*": [
"./*",
],
}
`)
})
})
it('should not prompt user for choice and use defaults if --yes is defined', async () => {
await useTempDir(async (cwd) => {
const projectName = 'yes-we-can'
const childProcess = createNextApp(
[projectName, '--yes'],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>((resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'app',
'package.json',
'postcss.config.mjs',
'tsconfig.json',
'AGENTS.md',
'CLAUDE.md',
],
})
resolve()
})
})
const pkg = require(join(cwd, projectName, 'package.json'))
expect(pkg.name).toBe(projectName)
const tsConfig = require(join(cwd, projectName, 'tsconfig.json'))
expect(tsConfig.compilerOptions.paths).toMatchInlineSnapshot(`
{
"@/*": [
"./*",
],
}
`)
})
})
it('should use recommended defaults when user selects that option', async () => {
await useTempDir(async (cwd) => {
const projectName = 'recommended-defaults'
const childProcess = createNextApp(
[projectName],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>((resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'app',
'package.json',
'postcss.config.mjs', // tailwind
'tsconfig.json', // typescript
'AGENTS.md', // agent files
'CLAUDE.md',
],
})
resolve()
})
// Select "Yes, use recommended defaults" (default option, just press enter)
childProcess.stdin.write('\n')
})
const pkg = require(join(cwd, projectName, 'package.json'))
expect(pkg.name).toBe(projectName)
})
})
it('should show reuse previous settings option when preferences exist', async () => {
const Conf = require('next/dist/compiled/conf')
await useTempDir(async (cwd) => {
// Manually set preferences to simulate a previous run
const conf = new Conf({ projectName: 'create-next-app' })
conf.set('preferences', {
typescript: false,
eslint: true,
linter: 'eslint',
tailwind: false,
app: false,
srcDir: false,
importAlias: '@/*',
customizeImportAlias: false,
reactCompiler: false,
})
const projectName = 'reuse-prefs-project'
const childProcess = createNextApp(
[projectName],
{
cwd,
},
nextTgzFilename,
false // Don't clear preferences
)
await new Promise<void>(async (resolve) => {
let output = ''
childProcess.stdout.on('data', (data) => {
output += data
process.stdout.write(data)
})
// Select "reuse previous settings" (cursor down once, then enter)
childProcess.stdin.write('\u001b[B\n')
// Wait for the prompt to appear with "reuse previous settings"
await check(() => output, /No, reuse previous settings/)
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
projectFilesShouldExist({
cwd,
projectName,
files: [
'pages', // pages router (not app)
'package.json',
'jsconfig.json', // javascript
],
})
resolve()
})
})
const pkg = require(join(cwd, projectName, 'package.json'))
expect(pkg.name).toBe(projectName)
})
})
it('should prompt user to confirm reset preferences', async () => {
await useTempDir(async (cwd) => {
const childProcess = createNextApp(
['--reset'],
{
cwd,
},
nextTgzFilename
)
await new Promise<void>(async (resolve) => {
childProcess.on('exit', async (exitCode) => {
expect(exitCode).toBe(0)
resolve()
})
let output = ''
childProcess.stdout.on('data', (data) => {
output += data
process.stdout.write(data)
})
await check(
() => output,
/Would you like to reset the saved preferences/
)
// cursor forward, choose 'Yes' for reset preferences
childProcess.stdin.write('\u001b[C\n')
await check(
() => output,
/The preferences have been reset successfully/
)
})
})
})
})