From 0669b68c91649ee9f41b8df0d6faf7bb8ec5bd9f Mon Sep 17 00:00:00 2001 From: Pim Date: Thu, 25 Oct 2018 09:43:42 +0200 Subject: [PATCH] refactor(cli): better consistency and easier unit testing (#4160) --- packages/cli/bin/nuxt.js | 43 +---- packages/cli/package.json | 3 +- packages/cli/src/command.js | 143 +++++++++++++++ packages/cli/src/commands/build.js | 107 ++++------- packages/cli/src/commands/dev.js | 68 ++----- packages/cli/src/commands/generate.js | 66 ++----- packages/cli/src/commands/index.js | 4 + packages/cli/src/commands/start.js | 58 ++---- packages/cli/src/imports.js | 3 + packages/cli/src/index.js | 13 +- packages/cli/src/options.js | 101 +++++++++++ packages/cli/src/run.js | 33 ++++ packages/cli/src/setup.js | 32 ++++ packages/cli/src/{common => }/utils.js | 44 +++-- .../cli/test/fixtures/nuxt.async-config.js | 9 + .../cli/test/fixtures/nuxt.async-error.js | 1 + packages/cli/test/fixtures/nuxt.config.js | 10 ++ packages/cli/test/unit/build.test.js | 62 +++++++ packages/cli/test/unit/cli.test.js | 95 ++++++++++ packages/cli/test/unit/command.test.js | 169 ++++++++++++++++++ packages/cli/test/unit/dev.test.js | 114 ++++++++++++ packages/cli/test/unit/generate.test.js | 64 +++++++ packages/cli/test/unit/start.test.js | 69 +++++++ packages/cli/test/unit/utils.test.js | 111 ++++++++++++ packages/cli/test/utils/index.js | 8 + packages/cli/test/utils/mocking.js | 96 ++++++++++ yarn.lock | 9 + 27 files changed, 1244 insertions(+), 291 deletions(-) create mode 100644 packages/cli/src/command.js create mode 100644 packages/cli/src/commands/index.js create mode 100644 packages/cli/src/imports.js create mode 100644 packages/cli/src/options.js create mode 100644 packages/cli/src/run.js create mode 100644 packages/cli/src/setup.js rename packages/cli/src/{common => }/utils.js (71%) create mode 100644 packages/cli/test/fixtures/nuxt.async-config.js create mode 100644 packages/cli/test/fixtures/nuxt.async-error.js create mode 100644 packages/cli/test/fixtures/nuxt.config.js create mode 100644 packages/cli/test/unit/build.test.js create mode 100644 packages/cli/test/unit/cli.test.js create mode 100644 packages/cli/test/unit/command.test.js create mode 100644 packages/cli/test/unit/dev.test.js create mode 100644 packages/cli/test/unit/generate.test.js create mode 100644 packages/cli/test/unit/start.test.js create mode 100644 packages/cli/test/unit/utils.test.js create mode 100644 packages/cli/test/utils/index.js create mode 100644 packages/cli/test/utils/mocking.js diff --git a/packages/cli/bin/nuxt.js b/packages/cli/bin/nuxt.js index 45127b7c4f..9876a2055f 100755 --- a/packages/cli/bin/nuxt.js +++ b/packages/cli/bin/nuxt.js @@ -1,44 +1,3 @@ #!/usr/bin/env node -const consola = require('consola') -const cli = require('../dist/cli.js') - -// Global error handler -process.on('unhandledRejection', (err) => { - consola.error(err) -}) - -// Exit process on fatal errors -consola.add({ - log(logObj) { - if (logObj.type === 'fatal') { - process.stderr.write('Nuxt Fatal Error :(\n') - process.exit(1) - } - } -}) - -const defaultCommand = 'dev' -const commands = new Set([ - defaultCommand, - 'build', - 'start', - 'generate' -]) - -let cmd = process.argv[2] - -if (commands.has(cmd)) { - process.argv.splice(2, 1) -} else { - cmd = defaultCommand -} - -// Apply default NODE_ENV if not provided -if (!process.env.NODE_ENV) { - process.env.NODE_ENV = cmd === 'dev' ? 'development' : 'production' -} - -cli[cmd]().then(m => m.default()).catch((error) => { - consola.fatal(error) -}) +require('../dist/cli.js').run() diff --git a/packages/cli/package.json b/packages/cli/package.json index 611a18a1c2..83b0dcf27b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -14,7 +14,8 @@ "dependencies": { "consola": "^1.4.4", "esm": "^3.0.84", - "minimist": "^1.2.0" + "minimist": "^1.2.0", + "wrap-ansi": "^4.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/cli/src/command.js b/packages/cli/src/command.js new file mode 100644 index 0000000000..b38e8cfee1 --- /dev/null +++ b/packages/cli/src/command.js @@ -0,0 +1,143 @@ +import parseArgs from 'minimist' +import wrapAnsi from 'wrap-ansi' +import { name, version } from '../package.json' +import { loadNuxtConfig, indent, indentLines, foldLines } from './utils' +import { options as Options, defaultOptions as DefaultOptions } from './options' +import * as imports from './imports' + +const startSpaces = 6 +const optionSpaces = 2 +const maxCharsPerLine = 80 + +export default class NuxtCommand { + constructor({ description, usage, options } = {}) { + this.description = description || '' + this.usage = usage || '' + this.options = Array.from(new Set((options || []).concat(DefaultOptions))) + } + + _getMinimistOptions() { + const minimistOptions = { + alias: {}, + boolean: [], + string: [], + default: {} + } + + for (const name of this.options) { + const option = Options[name] + + if (option.alias) { + minimistOptions.alias[option.alias] = name + } + if (option.type) { + minimistOptions[option.type].push(option.alias || name) + } + if (option.default) { + minimistOptions.default[option.alias || name] = option.default + } + } + + return minimistOptions + } + + getArgv(args) { + const minimistOptions = this._getMinimistOptions() + const argv = parseArgs(args || process.argv.slice(2), minimistOptions) + + if (argv.version) { + this.showVersion() + } else if (argv.help) { + this.showHelp() + } + + return argv + } + + async getNuxtConfig(argv, extraOptions) { + const config = await loadNuxtConfig(argv) + const options = Object.assign(config, extraOptions || {}) + + for (const name of this.options) { + if (Options[name].handle) { + Options[name].handle(options, argv) + } + } + + return options + } + + importCore() { + return imports.core() + } + + importBuilder() { + return imports.builder() + } + + importGenerator() { + return imports.generator() + } + + async getNuxt(options) { + const { Nuxt } = await this.importCore() + return new Nuxt(options) + } + + async getBuilder(nuxt) { + const { Builder } = await this.importBuilder() + return new Builder(nuxt) + } + + async getGenerator(nuxt) { + const { Generator } = await this.importGenerator() + const { Builder } = await this.importBuilder() + return new Generator(nuxt, new Builder(nuxt)) + } + + _getHelp() { + const options = [] + + let maxOptionLength = 0 + // For consistency Options determines order + for (const name in Options) { + const option = Options[name] + if (this.options.includes(name)) { + let optionHelp = '--' + optionHelp += option.type === 'boolean' && option.default ? 'no-' : '' + optionHelp += name + if (option.alias) { + optionHelp += `, -${option.alias}` + } + + maxOptionLength = Math.max(maxOptionLength, optionHelp.length) + options.push([ optionHelp, option.description ]) + } + } + + const optionStr = options.map(([option, description]) => { + const line = option + + indent(maxOptionLength + optionSpaces - option.length) + + wrapAnsi(description, maxCharsPerLine - startSpaces - maxOptionLength - optionSpaces) + return indentLines(line, startSpaces + maxOptionLength + optionSpaces, startSpaces) + }).join('\n') + + const description = foldLines(this.description, maxCharsPerLine, startSpaces) + + return ` + Description\n${description} + Usage + $ nuxt ${this.usage} + Options\n${optionStr}\n\n` + } + + showVersion() { + process.stdout.write(`${name} v${version}\n`) + process.exit(0) + } + + showHelp() { + process.stdout.write(this._getHelp()) + process.exit(0) + } +} diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 955362fcc3..2868b81ab8 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -1,91 +1,46 @@ -import parseArgs from 'minimist' import consola from 'consola' - -import { loadNuxtConfig } from '../common/utils' +import NuxtCommand from '../command' export default async function build() { - const { Nuxt } = await import('@nuxt/core') - const { Builder } = await import('@nuxt/builder') - const { Generator } = await import('@nuxt/generator') - - const argv = parseArgs(process.argv.slice(2), { - alias: { - h: 'help', - c: 'config-file', - a: 'analyze', - s: 'spa', - u: 'universal', - q: 'quiet' - }, - boolean: ['h', 'a', 's', 'u', 'q'], - string: ['c'], - default: { - c: 'nuxt.config.js' - } + const nuxtCmd = new NuxtCommand({ + description: 'Compiles the application for production deployment', + usage: 'build ', + options: [ 'analyze', 'quiet' ] }) - if (argv.help) { - process.stderr.write(` - Description - Compiles the application for production deployment - Usage - $ nuxt build - Options - --analyze, -a Launch webpack-bundle-analyzer to optimize your bundles. - --spa, -s Launch in SPA mode - --universal, -u Launch in Universal mode (default) - --no-generate Don't generate static version for SPA mode (useful for nuxt start) - --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) - --quiet, -q Disable output except for errors - --help, -h Displays this message - `) - process.exit(0) - } + const argv = nuxtCmd.getArgv() - const options = await loadNuxtConfig(argv) - - // Create production build when calling `nuxt build` - options.dev = false - - // Analyze option - options.build = options.build || {} - if (argv.analyze && typeof options.build.analyze !== 'object') { - options.build.analyze = true - } - - // Silence output when using --quiet - if (argv.quiet) { - options.build.quiet = !!argv.quiet - } - - const nuxt = new Nuxt(options) - const builder = new Builder(nuxt) + // Create production build when calling `nuxt build` (dev: false) + const nuxt = await nuxtCmd.getNuxt( + await nuxtCmd.getNuxtConfig(argv, { dev: false }) + ) // Setup hooks nuxt.hook('error', err => consola.fatal(err)) - // Close function - const close = () => { - // In analyze mode wait for plugin - // emitting assets and opening browser - if (options.build.analyze === true || typeof options.build.analyze === 'object') { - return - } - - process.exit(0) - } - - if (options.mode !== 'spa' || argv.generate === false) { + let builderOrGenerator + if (nuxt.options.mode !== 'spa' || argv.generate === false) { // Build only - return builder - .build() - .then(close) - .catch(err => consola.fatal(err)) + builderOrGenerator = (await nuxtCmd.getBuilder(nuxt)).build() } else { // Build + Generate for static deployment - return new Generator(nuxt, builder) - .generate({ build: true }) - .then(close) - .catch(err => consola.fatal(err)) + builderOrGenerator = (await nuxtCmd.getGenerator(nuxt)).generate({ + build: true + }) } + + return builderOrGenerator + .then(() => { + // In analyze mode wait for plugin + // emitting assets and opening browser + if ( + nuxt.options.build.analyze === true || + typeof nuxt.options.build.analyze === 'object' + ) { + return + } + + process.exit(0) + }) + .catch(err => consola.fatal(err)) } diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index 1595b7873e..5779cfdbff 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -1,71 +1,29 @@ -import parseArgs from 'minimist' import consola from 'consola' -import { loadNuxtConfig, runAsyncScript } from '../common/utils' +import NuxtCommand from '../command' export default async function dev() { - const { Nuxt } = await import('@nuxt/core') - const { Builder } = await import('@nuxt/builder') - - const argv = parseArgs(process.argv.slice(2), { - alias: { - h: 'help', - H: 'hostname', - p: 'port', - c: 'config-file', - s: 'spa', - u: 'universal', - v: 'version' - }, - boolean: ['h', 's', 'u', 'v'], - string: ['H', 'c'], - default: { - c: 'nuxt.config.js' - } + const nuxtCmd = new NuxtCommand({ + description: 'Start the application in development mode (e.g. hot-code reloading, error reporting)', + usage: 'dev -p -H ', + options: [ 'hostname', 'port' ] }) - if (argv.version) { - process.stderr.write('TODO' + '\n') - process.exit(0) - } - - if (argv.hostname === '') { - consola.fatal('Provided hostname argument has no value') - } - - if (argv.help) { - process.stderr.write(` - Description - Starts the application in development mode (hot-code reloading, error - reporting, etc) - Usage - $ nuxt dev -p -H - Options - --port, -p A port number on which to start the application - --hostname, -H Hostname on which to start the application - --spa Launch in SPA mode - --universal Launch in Universal mode (default) - --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) - --help, -h Displays this message - `) - process.exit(0) - } - - const config = async () => { - // Force development mode for add hot reloading and watching changes - return Object.assign(await loadNuxtConfig(argv), { dev: true }) - } + const argv = nuxtCmd.getArgv() const errorHandler = (err, instance) => { instance && instance.builder.watchServer() consola.error(err) } + const { Nuxt } = await nuxtCmd.importCore() + const { Builder } = await nuxtCmd.importBuilder() + // Start dev async function startDev(oldInstance) { let nuxt, builder try { - nuxt = new Nuxt(await config()) + nuxt = new Nuxt(await nuxtCmd.getNuxtConfig(argv, { dev: true })) builder = new Builder(nuxt) nuxt.hook('watch:fileChanged', async (builder, fname) => { consola.debug(`[${fname}] changed, Rebuilding the app...`) @@ -81,10 +39,10 @@ export default async function dev() { .then(() => oldInstance && oldInstance.builder.unwatch()) // Start build .then(() => builder.build()) - // Close old nuxt no mater if build successfully + // Close old nuxt no matter if build successfully .catch((err) => { oldInstance && oldInstance.nuxt.close() - // Jump to eventHandler + // Jump to errorHandler throw err }) .then(() => oldInstance && oldInstance.nuxt.close()) @@ -98,5 +56,5 @@ export default async function dev() { ) } - await runAsyncScript(startDev) + await startDev() } diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js index 64ab9e934d..a2d79697ed 100644 --- a/packages/cli/src/commands/generate.js +++ b/packages/cli/src/commands/generate.js @@ -1,61 +1,25 @@ -import parseArgs from 'minimist' import consola from 'consola' - -import { loadNuxtConfig } from '../common/utils' +import NuxtCommand from '../command' export default async function generate() { - const { Nuxt } = await import('@nuxt/core') - const { Builder } = await import('@nuxt/builder') - const { Generator } = await import('@nuxt/generator') - - const argv = parseArgs(process.argv.slice(2), { - alias: { - h: 'help', - c: 'config-file', - s: 'spa', - u: 'universal' - }, - boolean: ['h', 's', 'u', 'build'], - string: ['c'], - default: { - c: 'nuxt.config.js', - build: true - } + const nuxtCmd = new NuxtCommand({ + description: 'Generate a static web application (server-rendered)', + usage: 'generate ', + options: [ 'build' ] }) - if (argv.help) { - process.stderr.write(` - Description - Generate a static web application (server-rendered) - Usage - $ nuxt generate - Options - --spa Launch in SPA mode - --universal Launch in Universal mode (default) - --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) - --help, -h Displays this message - --no-build Just run generate for faster builds when just dynamic routes changed. Nuxt build is needed before this command. - `) - process.exit(0) - } + const argv = nuxtCmd.getArgv() - const options = await loadNuxtConfig(argv) + const generator = await nuxtCmd.getGenerator( + await nuxtCmd.getNuxt( + await nuxtCmd.getNuxtConfig(argv, { dev: false }) + ) + ) - options.dev = false // Force production mode (no webpack middleware called) - - const nuxt = new Nuxt(options) - const builder = new Builder(nuxt) - const generator = new Generator(nuxt, builder) - - const generateOptions = { + return generator.generate({ init: true, build: argv.build - } - - return generator - .generate(generateOptions) - .then(() => { - process.exit(0) - }) - .catch(err => consola.fatal(err)) + }).then(() => { + process.exit(0) + }).catch(err => consola.fatal(err)) } diff --git a/packages/cli/src/commands/index.js b/packages/cli/src/commands/index.js new file mode 100644 index 0000000000..d92eb03998 --- /dev/null +++ b/packages/cli/src/commands/index.js @@ -0,0 +1,4 @@ +export const start = () => import('./start') +export const dev = () => import('./dev') +export const build = () => import('./build') +export const generate = () => import('./generate') diff --git a/packages/cli/src/commands/start.js b/packages/cli/src/commands/start.js index 9d36532976..2498a06a87 100644 --- a/packages/cli/src/commands/start.js +++ b/packages/cli/src/commands/start.js @@ -1,59 +1,21 @@ import fs from 'fs' import path from 'path' -import parseArgs from 'minimist' import consola from 'consola' - -import { loadNuxtConfig } from '../common/utils' +import NuxtCommand from '../command' export default async function start() { - const { Nuxt } = await import('@nuxt/core') - - const argv = parseArgs(process.argv.slice(2), { - alias: { - h: 'help', - H: 'hostname', - p: 'port', - n: 'unix-socket', - c: 'config-file', - s: 'spa', - u: 'universal' - }, - boolean: ['h', 's', 'u'], - string: ['H', 'c', 'n'], - default: { - c: 'nuxt.config.js' - } + const nuxtCmd = new NuxtCommand({ + description: 'Start the application in production mode (the application should be compiled with `nuxt build` first)', + usage: 'start -p -H ', + options: [ 'hostname', 'port', 'unix-socket' ] }) - if (argv.hostname === '') { - consola.fatal('Provided hostname argument has no value') - } + const argv = nuxtCmd.getArgv() - if (argv.help) { - process.stderr.write(` - Description - Starts the application in production mode. - The application should be compiled with \`nuxt build\` first. - Usage - $ nuxt start -p -H - Options - --port, -p A port number on which to start the application - --hostname, -H Hostname on which to start the application - --unix-socket, -n Path to a UNIX socket - --spa Launch in SPA mode - --universal Launch in Universal mode (default) - --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) - --help, -h Displays this message - `) - process.exit(0) - } - - const options = await loadNuxtConfig(argv) - - // Force production mode (no webpack middleware called) - options.dev = false - - const nuxt = new Nuxt(options) + // Create production build when calling `nuxt build` + const nuxt = await nuxtCmd.getNuxt( + await nuxtCmd.getNuxtConfig(argv, { dev: false }) + ) // Setup hooks nuxt.hook('error', err => consola.fatal(err)) diff --git a/packages/cli/src/imports.js b/packages/cli/src/imports.js new file mode 100644 index 0000000000..ada97e5c5a --- /dev/null +++ b/packages/cli/src/imports.js @@ -0,0 +1,3 @@ +export const builder = () => import('@nuxt/builder') +export const generator = () => import('@nuxt/generator') +export const core = () => import('@nuxt/core') diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js index 6382cbe31a..5c39b23a97 100644 --- a/packages/cli/src/index.js +++ b/packages/cli/src/index.js @@ -1,5 +1,10 @@ -export const start = () => import('./commands/start') -export const dev = () => import('./commands/dev') +import * as _commands from './commands' +import * as _imports from './imports' -export const build = () => import('./commands/build') -export const generate = () => import('./commands/generate') +export const commands = _commands +export const imports = _imports + +export { default as setup } from './setup' +export { default as run } from './run' + +export { loadNuxtConfig } from './utils' diff --git a/packages/cli/src/options.js b/packages/cli/src/options.js new file mode 100644 index 0000000000..2afd911650 --- /dev/null +++ b/packages/cli/src/options.js @@ -0,0 +1,101 @@ +import consola from 'consola' + +export const defaultOptions = [ + 'spa', + 'universal', + 'config-file', + 'version', + 'help' +] + +export const options = { + port: { + alias: 'p', + type: 'string', + description: 'Port number on which to start the application', + handle(options, argv) { + if (argv.port) { + options.server.port = +argv.port + } + } + }, + hostname: { + alias: 'H', + type: 'string', + description: 'Hostname on which to start the application', + handle(options, argv) { + if (argv.hostname === '') { + consola.fatal('Provided hostname argument has no value') + } + } + }, + 'unix-socket': { + alias: 'n', + type: 'string', + description: 'Path to a UNIX socket' + }, + analyze: { + alias: 'a', + type: 'boolean', + description: 'Launch webpack-bundle-analyzer to optimize your bundles', + handle(options, argv) { + // Analyze option + options.build = options.build || {} + if (argv.analyze && typeof options.build.analyze !== 'object') { + options.build.analyze = true + } + } + }, + build: { + type: 'boolean', + default: true, + description: 'Only generate pages for dynamic routes. Nuxt has to be built once before using this option' + }, + generate: { + type: 'boolean', + default: true, + description: 'Don\'t generate static version for SPA mode (useful for nuxt start)' + }, + spa: { + alias: 's', + type: 'boolean', + description: 'Launch in SPA mode' + }, + universal: { + alias: 'u', + type: 'boolean', + description: 'Launch in Universal mode (default)' + }, + 'config-file': { + alias: 'c', + type: 'string', + default: 'nuxt.config.js', + description: 'Path to Nuxt.js config file (default: nuxt.config.js)' + }, + quiet: { + alias: 'q', + type: 'boolean', + description: 'Disable output except for errors', + handle(options, argv) { + // Silence output when using --quiet + options.build = options.build || {} + if (argv.quiet) { + options.build.quiet = !!argv.quiet + } + } + }, + verbose: { + alias: 'v', + type: 'boolean', + description: 'Show debug information' + }, + version: { + type: 'boolean', + description: 'Display the Nuxt version' + }, + help: { + alias: 'h', + type: 'boolean', + description: 'Display this message' + } +} diff --git a/packages/cli/src/run.js b/packages/cli/src/run.js new file mode 100644 index 0000000000..97c322b53b --- /dev/null +++ b/packages/cli/src/run.js @@ -0,0 +1,33 @@ +import consola from 'consola' +import * as commands from './commands' +import setup from './setup' + +export default function run() { + const defaultCommand = 'dev' + + const cmds = new Set([ + defaultCommand, + 'build', + 'start', + 'generate' + ]) + + let cmd = process.argv[2] + + if (cmds.has(cmd)) { + process.argv.splice(2, 1) + } else { + cmd = defaultCommand + } + + // Setup runtime + setup({ + dev: cmd === 'dev' + }) + + return commands[cmd]() // eslint-disable-line import/namespace + .then(m => m.default()) + .catch((error) => { + consola.fatal(error) + }) +} diff --git a/packages/cli/src/setup.js b/packages/cli/src/setup.js new file mode 100644 index 0000000000..802e7fb168 --- /dev/null +++ b/packages/cli/src/setup.js @@ -0,0 +1,32 @@ +import consola from 'consola' + +let _setup = false + +export default function setup({ dev }) { + // Apply default NODE_ENV if not provided + if (!process.env.NODE_ENV) { + process.env.NODE_ENV = dev ? 'development' : 'production' + } + + if (_setup) { + return + } + _setup = true + + // Global error handler + /* istanbul ignore next */ + process.on('unhandledRejection', (err) => { + consola.error(err) + }) + + // Exit process on fatal errors + /* istanbul ignore next */ + consola.add({ + log(logObj) { + if (logObj.type === 'fatal') { + process.stderr.write('Nuxt Fatal Error :(\n') + process.exit(1) + } + } + }) +} diff --git a/packages/cli/src/common/utils.js b/packages/cli/src/utils.js similarity index 71% rename from packages/cli/src/common/utils.js rename to packages/cli/src/utils.js index d989be0999..47dcc27bee 100644 --- a/packages/cli/src/common/utils.js +++ b/packages/cli/src/utils.js @@ -2,6 +2,7 @@ import path from 'path' import { existsSync } from 'fs' import consola from 'consola' import esm from 'esm' +import wrapAnsi from 'wrap-ansi' const _require = esm(module, { cache: false, @@ -33,15 +34,6 @@ const getLatestHost = (argv) => { return { port, host, socket } } -export async function runAsyncScript(fn) { - try { - await fn() - } catch (err) { - consola.error(err) - consola.fatal(`Failed to run async Nuxt script!`) - } -} - export async function loadNuxtConfig(argv) { const rootDir = getRootDir(argv) const nuxtConfigFile = getNuxtConfigFile(argv) @@ -50,16 +42,17 @@ export async function loadNuxtConfig(argv) { if (existsSync(nuxtConfigFile)) { delete require.cache[nuxtConfigFile] - options = _require(nuxtConfigFile) - if (!options) { - options = {} - } + options = _require(nuxtConfigFile) || {} if (options.default) { options = options.default } + if (typeof options === 'function') { try { options = await options() + if (options.default) { + options = options.default + } } catch (error) { consola.error(error) consola.fatal('Error while fetching async configuration') @@ -68,7 +61,6 @@ export async function loadNuxtConfig(argv) { } else if (argv['config-file'] !== 'nuxt.config.js') { consola.fatal('Could not load config file: ' + argv['config-file']) } - if (typeof options.rootDir !== 'string') { options.rootDir = rootDir } @@ -81,9 +73,33 @@ export async function loadNuxtConfig(argv) { if (!options.server) { options.server = {} } + const { port, host, socket } = getLatestHost(argv) options.server.port = port || options.server.port || 3000 options.server.host = host || options.server.host || 'localhost' options.server.socket = socket || options.server.socket + return options } + +export function indent(count, chr = ' ') { + return chr.repeat(count) +} + +export function indentLines(string, spaces, firstLineSpaces) { + const lines = Array.isArray(string) ? string : string.split('\n') + let s = '' + if (lines.length) { + const i0 = indent(firstLineSpaces === undefined ? spaces : firstLineSpaces) + s = i0 + lines.shift() + } + if (lines.length) { + const i = indent(spaces) + s += '\n' + lines.map(l => i + l.trim()).join('\n') + } + return s +} + +export function foldLines(string, maxCharsPerLine, spaces, firstLineSpaces) { + return indentLines(wrapAnsi(string, maxCharsPerLine), spaces, firstLineSpaces) +} diff --git a/packages/cli/test/fixtures/nuxt.async-config.js b/packages/cli/test/fixtures/nuxt.async-config.js new file mode 100644 index 0000000000..4fa271b380 --- /dev/null +++ b/packages/cli/test/fixtures/nuxt.async-config.js @@ -0,0 +1,9 @@ +import { resolve } from 'path' + +export default () => { + // delete cache is needed because otherwise Jest will return the same + // object reference as the previous test and then mode will not be + // set correctly. jest.resetModules doesnt work for some reason + delete require.cache[resolve(__dirname, 'nuxt.config.js')] + return import('./nuxt.config.js') +} diff --git a/packages/cli/test/fixtures/nuxt.async-error.js b/packages/cli/test/fixtures/nuxt.async-error.js new file mode 100644 index 0000000000..fb5f8ca711 --- /dev/null +++ b/packages/cli/test/fixtures/nuxt.async-error.js @@ -0,0 +1 @@ +export default () => Promise.reject(new Error('Async Config Error')) diff --git a/packages/cli/test/fixtures/nuxt.config.js b/packages/cli/test/fixtures/nuxt.config.js new file mode 100644 index 0000000000..7594287a99 --- /dev/null +++ b/packages/cli/test/fixtures/nuxt.config.js @@ -0,0 +1,10 @@ +export default { + testOption: true, + rootDir: '/some/path', + mode: 'supercharged', + server: { + host: 'nuxt-host', + port: 3001, + socket: '/var/run/nuxt.sock' + } +} diff --git a/packages/cli/test/unit/build.test.js b/packages/cli/test/unit/build.test.js new file mode 100644 index 0000000000..5c501804ec --- /dev/null +++ b/packages/cli/test/unit/build.test.js @@ -0,0 +1,62 @@ +import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator } from '../utils' + +describe('build', () => { + let build + + beforeAll(async () => { + build = await import('../../src/commands/build') + build = build.default + + jest.spyOn(process, 'exit').mockImplementation(code => code) + }) + + afterAll(() => { + process.exit.mockRestore() + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + test('is function', () => { + expect(typeof build).toBe('function') + }) + + test('builds on universal mode', async () => { + mockGetNuxt({ + mode: 'universal', + build: { + analyze: true + } + }) + const builder = mockGetBuilder(Promise.resolve()) + + await build() + + expect(builder).toHaveBeenCalled() + }) + + test('generates on spa mode', async () => { + mockGetNuxt({ + mode: 'spa', + build: { + analyze: false + } + }) + const generate = mockGetGenerator(Promise.resolve()) + + await build() + + expect(generate).toHaveBeenCalled() + expect(process.exit).toHaveBeenCalled() + }) + + test('catches error', async () => { + mockGetNuxt({ mode: 'universal' }) + mockGetBuilder(Promise.reject(new Error('Builder Error'))) + + await build() + + expect(consola.fatal).toHaveBeenCalledWith(new Error('Builder Error')) + }) +}) diff --git a/packages/cli/test/unit/cli.test.js b/packages/cli/test/unit/cli.test.js new file mode 100644 index 0000000000..8150180fa4 --- /dev/null +++ b/packages/cli/test/unit/cli.test.js @@ -0,0 +1,95 @@ +import { readdir } from 'fs' +import { resolve } from 'path' +import { promisify } from 'util' +import { consola } from '../utils' +import { run } from '../../src' +import * as commands from '../../src/commands' + +const readDir = promisify(readdir) + +consola.add = jest.fn() + +const mockCommand = (cmd, p) => { + commands[cmd] = jest.fn().mockImplementationOnce(() => { // eslint-disable-line import/namespace + return Promise.resolve({ + default: () => { + return p + } + }) + }) +} + +describe('cli', () => { + afterEach(() => { + jest.resetAllMocks() + }) + + test('exports for all commands defined', async () => { + const cmds = await readDir(resolve(__dirname, '..', '..', 'src', 'commands')) + + for (let cmd of cmds) { + if (cmd === 'index.js') { + continue + } + cmd = cmd.substring(0, cmd.length - 3) + + expect(commands[cmd]).toBeDefined() // eslint-disable-line import/namespace + expect(typeof commands[cmd]).toBe('function') // eslint-disable-line import/namespace + } + }) + + test('calls expected method', async () => { + const argv = process.argv + process.argv = ['', '', 'dev'] + mockCommand('dev', Promise.resolve()) + + await run() + + expect(commands.dev).toHaveBeenCalled() + process.argv = argv + }) + + test('unknown calls default method', async () => { + const argv = process.argv + process.argv = ['', '', 'test'] + mockCommand('dev', Promise.resolve()) + + await run() + + expect(commands.dev).toHaveBeenCalled() + process.argv = argv + }) + + test('sets NODE_ENV=development for dev', async () => { + const nodeEnv = process.env.NODE_ENV + process.env.NODE_ENV = '' + mockCommand('dev', Promise.resolve()) + + await run() + + expect(process.env.NODE_ENV).toBe('development') + process.env.NODE_ENV = nodeEnv + }) + + test('sets ODE_ENV=production for build', async () => { + const argv = process.argv + const nodeEnv = process.env.NODE_ENV + process.argv = ['', '', 'build'] + process.env.NODE_ENV = '' + mockCommand('build', Promise.resolve()) + + await run() + + expect(process.env.NODE_ENV).toBe('production') + process.argv = argv + process.env.NODE_ENV = nodeEnv + }) + + test('catches fatal error', async () => { + mockCommand('dev', Promise.reject(new Error('Command Error'))) + + await run() + + expect(consola.fatal).toHaveBeenCalledWith(new Error('Command Error')) + }) +}) diff --git a/packages/cli/test/unit/command.test.js b/packages/cli/test/unit/command.test.js new file mode 100644 index 0000000000..d8ff3e579f --- /dev/null +++ b/packages/cli/test/unit/command.test.js @@ -0,0 +1,169 @@ +import { consola } from '../utils' +import Command from '../../src/command' +import { options as Options } from '../../src/options' + +jest.mock('@nuxt/core') +jest.mock('@nuxt/builder') +jest.mock('@nuxt/generator') + +describe('cli/command', () => { + beforeEach(() => { + jest.restoreAllMocks() + }) + + test('adds default options', () => { + const cmd = new Command() + + expect(cmd.options.length).not.toBe(0) + }) + + test('builds minimist options', () => { + const cmd = new Command({ + options: Object.keys(Options) + }) + + const minimistOptions = cmd._getMinimistOptions() + + expect(minimistOptions.string.length).toBe(4) + expect(minimistOptions.boolean.length).toBe(9) + expect(minimistOptions.alias.c).toBe('config-file') + expect(minimistOptions.default.c).toBe(Options['config-file'].default) + }) + + test('parses args', () => { + const cmd = new Command({ + options: Object.keys(Options) + }) + + let args = ['-c', 'test-file', '-s', '-p', '3001'] + let argv = cmd.getArgv(args) + + expect(argv['config-file']).toBe(args[1]) + expect(argv.spa).toBe(true) + expect(argv.universal).toBe(false) + expect(argv.build).toBe(true) + expect(argv.port).toBe('3001') + + args = ['--no-build'] + argv = cmd.getArgv(args) + + expect(argv.build).toBe(false) + }) + + test('prints version automatically', () => { + const cmd = new Command() + cmd.showVersion = jest.fn() + + const args = ['--version'] + cmd.getArgv(args) + + expect(cmd.showVersion).toHaveBeenCalledTimes(1) + }) + + test('prints help automatically', () => { + const cmd = new Command() + cmd.showHelp = jest.fn() + + const args = ['-h'] + cmd.getArgv(args) + + expect(cmd.showHelp).toHaveBeenCalledTimes(1) + }) + + test('returns nuxt config', async () => { + const cmd = new Command({ + options: Object.keys(Options) + }) + + const args = ['-c', 'test-file', '-a', '-p', '3001', '-q', '-H'] + const argv = cmd.getArgv(args) + argv._ = ['.'] + + const options = await cmd.getNuxtConfig(argv, { testOption: true }) + + expect(options.testOption).toBe(true) + expect(options.server.port).toBe(3001) + expect(options.build.quiet).toBe(true) + expect(options.build.analyze).toBe(true) + expect(consola.fatal).toHaveBeenCalledWith('Provided hostname argument has no value') // hostname check + }) + + test('returns Nuxt instance', async () => { + const cmd = new Command() + const nuxt = await cmd.getNuxt() + + expect(nuxt.constructor.name).toBe('Nuxt') + expect(typeof nuxt.ready).toBe('function') + }) + + test('returns Builder instance', async () => { + const cmd = new Command() + const builder = await cmd.getBuilder() + + expect(builder.constructor.name).toBe('Builder') + expect(typeof builder.build).toBe('function') + }) + + test('returns Generator instance', async () => { + const cmd = new Command() + const generator = await cmd.getGenerator() + + expect(generator.constructor.name).toBe('Generator') + expect(typeof generator.generate).toBe('function') + }) + + test('builds help text', () => { + const cmd = new Command({ + description: 'a very long description that is longer than 80 chars and ' + + 'should wrap to the next line while keeping indentation', + usage: 'this is how you do it', + options: ['build'] + }) + + const expectedText = ` + Description + a very long description that is longer than 80 chars and should wrap to the next + line while keeping indentation + Usage + $ nuxt this is how you do it + Options + --no-build Only generate pages for dynamic routes. Nuxt has to be + built once before using this option + --spa, -s Launch in SPA mode + --universal, -u Launch in Universal mode (default) + --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) + --version Display the Nuxt version + --help, -h Display this message + +` + expect(cmd._getHelp()).toBe(expectedText) + }) + + test('show version prints to stdout and exits', () => { + jest.spyOn(process.stdout, 'write').mockImplementation(() => {}) + jest.spyOn(process, 'exit').mockImplementationOnce(code => code) + + const cmd = new Command() + cmd.showVersion() + + expect(process.stdout.write).toHaveBeenCalled() + expect(process.exit).toHaveBeenCalled() + + process.stdout.write.mockRestore() + process.exit.mockRestore() + }) + + test('show help prints to stdout and exits', () => { + jest.spyOn(process.stdout, 'write').mockImplementation(() => {}) + jest.spyOn(process, 'exit').mockImplementationOnce(code => code) + + const cmd = new Command() + cmd.showHelp() + + expect(process.stdout.write).toHaveBeenCalled() + expect(process.exit).toHaveBeenCalled() + + process.stdout.write.mockRestore() + process.exit.mockRestore() + }) +}) diff --git a/packages/cli/test/unit/dev.test.js b/packages/cli/test/unit/dev.test.js new file mode 100644 index 0000000000..a0592d5a34 --- /dev/null +++ b/packages/cli/test/unit/dev.test.js @@ -0,0 +1,114 @@ +import { consola } from '../utils' +import { mockNuxt, mockBuilder, mockGetNuxtConfig } from '../utils/mocking' + +describe('dev', () => { + let dev + + beforeAll(async () => { + dev = await import('../../src/commands/dev') + dev = dev.default + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + test('is function', () => { + expect(typeof dev).toBe('function') + }) + + test('reloads on fileChanged hook', async () => { + const Nuxt = mockNuxt() + const Builder = mockBuilder() + + await dev() + + expect(consola.error).not.toHaveBeenCalled() + + expect(Builder.prototype.build).toHaveBeenCalled() + expect(Nuxt.prototype.listen).toHaveBeenCalled() + expect(Nuxt.prototype.showReady).toHaveBeenCalled() + expect(Builder.prototype.watchServer).toHaveBeenCalled() + + jest.resetAllMocks() + + const builder = new Builder() + builder.nuxt = new Nuxt() + await Nuxt.fileChangedHook(builder) + expect(consola.debug).toHaveBeenCalled() + + expect(Nuxt.prototype.clearHook).toHaveBeenCalled() + expect(Builder.prototype.unwatch).toHaveBeenCalled() + expect(Builder.prototype.build).toHaveBeenCalled() + expect(Nuxt.prototype.close).toHaveBeenCalled() + expect(Nuxt.prototype.listen).toHaveBeenCalled() + expect(Nuxt.prototype.showReady).not.toHaveBeenCalled() + expect(Builder.prototype.watchServer).toHaveBeenCalled() + + expect(consola.error).not.toHaveBeenCalled() + }) + + test('catches build error', async () => { + const Nuxt = mockNuxt() + const Builder = mockBuilder() + + await dev() + jest.resetAllMocks() + + // Test error on second build so we cover oldInstance stuff + const builder = new Builder() + builder.nuxt = new Nuxt() + Builder.prototype.build = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('Build Error'))) + await Nuxt.fileChangedHook(builder) + + expect(Nuxt.prototype.close).toHaveBeenCalled() + expect(consola.error).toHaveBeenCalledWith(new Error('Build Error')) + }) + + test('catches watchServer error', async () => { + const Nuxt = mockNuxt() + const Builder = mockBuilder() + + await dev() + jest.resetAllMocks() + + const builder = new Builder() + builder.nuxt = new Nuxt() + Builder.prototype.watchServer = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('watchServer Error'))) + await Nuxt.fileChangedHook(builder) + + expect(consola.error).toHaveBeenCalledWith(new Error('watchServer Error')) + expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(2) + }) + + test('catches error on hook error', async () => { + const Nuxt = mockNuxt() + const Builder = mockBuilder() + + await dev() + jest.resetAllMocks() + + mockGetNuxtConfig().mockImplementationOnce(() => { + throw new Error('Config Error') + }) + const builder = new Builder() + builder.nuxt = new Nuxt() + await Nuxt.fileChangedHook(builder) + + expect(consola.error).toHaveBeenCalledWith(new Error('Config Error')) + expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(1) + }) + + test('catches error on startDev', async () => { + mockNuxt({ + listen: jest.fn().mockImplementation(() => { + throw new Error('Listen Error') + }) + }) + mockBuilder() + + await dev() + + expect(consola.error).toHaveBeenCalledWith(new Error('Listen Error')) + }) +}) diff --git a/packages/cli/test/unit/generate.test.js b/packages/cli/test/unit/generate.test.js new file mode 100644 index 0000000000..42b8f0d141 --- /dev/null +++ b/packages/cli/test/unit/generate.test.js @@ -0,0 +1,64 @@ +import { consola, mockGetNuxt, mockGetGenerator } from '../utils' +import Command from '../../src/command' + +describe('generate', () => { + let generate + + beforeAll(async () => { + generate = await import('../../src/commands/generate') + generate = generate.default + + jest.spyOn(process, 'exit').mockImplementation(code => code) + }) + + afterAll(() => { + process.exit.mockRestore() + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + test('is function', () => { + expect(typeof generate).toBe('function') + }) + + test('builds by default', async () => { + mockGetNuxt() + const generator = mockGetGenerator(Promise.resolve()) + + await generate() + + expect(generator).toHaveBeenCalled() + expect(generator.mock.calls[0][0].build).toBe(true) + }) + + test('doesnt build with no-build', async () => { + mockGetNuxt() + const getArgv = Command.prototype.getArgv + Command.prototype.getArgv = jest.fn().mockImplementationOnce(() => { + return { + '_': ['.'], + rootDir: '.', + 'config-file': 'nuxt.config.js', + build: false + } + }) + const generator = mockGetGenerator(Promise.resolve()) + + await generate() + + expect(generator).toHaveBeenCalled() + expect(generator.mock.calls[0][0].build).toBe(false) + Command.prototype.getArgv = getArgv + }) + + test('catches error', async () => { + mockGetNuxt() + mockGetGenerator(Promise.reject(new Error('Generator Error'))) + + await generate() + + expect(consola.fatal).toHaveBeenCalledWith(new Error('Generator Error')) + }) +}) diff --git a/packages/cli/test/unit/start.test.js b/packages/cli/test/unit/start.test.js new file mode 100644 index 0000000000..876b50eb6b --- /dev/null +++ b/packages/cli/test/unit/start.test.js @@ -0,0 +1,69 @@ +import fs from 'fs' +import { consola, mockGetNuxtStart, mockGetNuxtConfig } from '../utils' + +describe('start', () => { + let start + + beforeAll(async () => { + start = await import('../../src/commands/start') + start = start.default + }) + + afterEach(() => { + if (fs.existsSync.mockRestore) { + fs.existsSync.mockRestore() + } + + jest.resetAllMocks() + }) + + test('is function', () => { + expect(typeof start).toBe('function') + }) + + test('starts listening and calls showReady', async () => { + const { listen, showReady } = mockGetNuxtStart() + await start() + + expect(listen).toHaveBeenCalled() + expect(showReady).toHaveBeenCalled() + }) + + test('no error if dist dir exists', async () => { + mockGetNuxtStart() + mockGetNuxtConfig() + jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => true) + + await start() + + expect(consola.fatal).not.toHaveBeenCalled() + }) + + test('fatal error if dist dir doesnt exist', async () => { + mockGetNuxtStart() + jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => false) + + await start() + + expect(consola.fatal).toHaveBeenCalledWith('No build files found, please run `nuxt build` before launching `nuxt start`') + }) + + test('no error on ssr and server bundle exists', async () => { + mockGetNuxtStart(true) + mockGetNuxtConfig() + jest.spyOn(fs, 'existsSync').mockImplementation(() => true) + + await start() + + expect(consola.fatal).not.toHaveBeenCalled() + }) + + test('fatal error on ssr and server bundle doesnt exist', async () => { + mockGetNuxtStart(true) + jest.spyOn(fs, 'existsSync').mockImplementation(() => false) + + await start() + + expect(consola.fatal).toHaveBeenCalledWith('No SSR build! Please start with `nuxt start --spa` or build using `nuxt build --universal`') + }) +}) diff --git a/packages/cli/test/unit/utils.test.js b/packages/cli/test/unit/utils.test.js new file mode 100644 index 0000000000..f111aeb932 --- /dev/null +++ b/packages/cli/test/unit/utils.test.js @@ -0,0 +1,111 @@ +import { consola } from '../utils' +import * as utils from '../../src/utils' + +describe('cli/utils', () => { + afterEach(() => { + jest.resetAllMocks() + }) + + test('loadNuxtConfig: defaults', async () => { + const argv = { + _: ['.'], + 'config-file': 'nuxt.config.js', + universal: true + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.rootDir).toBe(process.cwd()) + expect(options.mode).toBe('universal') + expect(options.server.host).toBe('localhost') + expect(options.server.port).toBe(3000) + expect(options.server.socket).not.toBeDefined() + }) + + test('loadNuxtConfig: config-file', async () => { + const argv = { + _: [__dirname], + 'config-file': '../fixtures/nuxt.config.js', + spa: true + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.testOption).toBe(true) + expect(options.rootDir).toBe('/some/path') + expect(options.mode).toBe('spa') + expect(options.server.host).toBe('nuxt-host') + expect(options.server.port).toBe(3001) + expect(options.server.socket).toBe('/var/run/nuxt.sock') + }) + + test('loadNuxtConfig: not-existing config-file', async () => { + const argv = { + _: [__dirname], + 'config-file': '../fixtures/nuxt.doesnt-exist.js' + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.testOption).not.toBeDefined() + + expect(consola.fatal).toHaveBeenCalledTimes(1) + expect(consola.fatal).toHaveBeenCalledWith(expect.stringMatching(/Could not load config file/)) + }) + + test('loadNuxtConfig: async config-file', async () => { + const argv = { + _: [__dirname], + 'config-file': '../fixtures/nuxt.async-config.js', + hostname: 'async-host', + port: 3002, + 'unix-socket': '/var/run/async.sock' + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.testOption).toBe(true) + expect(options.mode).toBe('supercharged') + expect(options.server.host).toBe('async-host') + expect(options.server.port).toBe(3002) + expect(options.server.socket).toBe('/var/run/async.sock') + }) + + test('loadNuxtConfig: async config-file with error', async () => { + const argv = { + _: [__dirname], + 'config-file': '../fixtures/nuxt.async-error.js' + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.testOption).not.toBeDefined() + + expect(consola.error).toHaveBeenCalledTimes(1) + expect(consola.error).toHaveBeenCalledWith(new Error('Async Config Error')) + expect(consola.fatal).toHaveBeenCalledWith('Error while fetching async configuration') + }) + + test('loadNuxtConfig: server env', async () => { + const env = process.env + + process.env.HOST = 'env-host' + process.env.PORT = 3003 + process.env.UNIX_SOCKET = '/var/run/env.sock' + + const argv = { + _: [__dirname], + 'config-file': '../fixtures/nuxt.config.js' + } + + const options = await utils.loadNuxtConfig(argv) + expect(options.server.host).toBe('env-host') + expect(options.server.port).toBe('3003') + expect(options.server.socket).toBe('/var/run/env.sock') + + process.env = env + }) + + test('indent', () => { + expect(utils.indent(4)).toBe(' ') + }) + + test('indent custom char', () => { + expect(utils.indent(4, '-')).toBe('----') + }) +}) diff --git a/packages/cli/test/utils/index.js b/packages/cli/test/utils/index.js new file mode 100644 index 0000000000..8c36b338fe --- /dev/null +++ b/packages/cli/test/utils/index.js @@ -0,0 +1,8 @@ +import consola from 'consola' +export * from './mocking' + +jest.mock('consola') + +export { + consola +} diff --git a/packages/cli/test/utils/mocking.js b/packages/cli/test/utils/mocking.js new file mode 100644 index 0000000000..7427a8b8be --- /dev/null +++ b/packages/cli/test/utils/mocking.js @@ -0,0 +1,96 @@ +import Command from '../../src/command' + +export const mockGetNuxt = (options, implementation) => { + Command.prototype.getNuxt = jest.fn().mockImplementationOnce(() => { + return Object.assign({ + hook: jest.fn(), + options + }, implementation || {}) + }) +} + +export const mockGetBuilder = (ret) => { + const build = jest.fn().mockImplementationOnce(() => { + return ret + }) + + Command.prototype.getBuilder = jest.fn().mockImplementationOnce(() => { + return { build } + }) + + return build +} + +export const mockGetGenerator = (ret) => { + const generate = jest.fn() + if (ret) { + generate.mockImplementationOnce(() => { + return ret + }) + } + + Command.prototype.getGenerator = jest.fn().mockImplementationOnce(() => { + return { generate } + }) + + return generate +} + +export const mockGetNuxtStart = (ssr) => { + const listen = jest.fn().mockImplementationOnce(() => { + return Promise.resolve() + }) + const showReady = jest.fn() + + mockGetNuxt({ + rootDir: '.', + render: { + ssr + } + }, { + listen, + showReady + }) + + return { listen, showReady } +} + +export const mockGetNuxtConfig = () => { + const spy = jest.fn() + Command.prototype.getNuxtConfig = spy + return spy +} + +export const mockNuxt = (implementation) => { + const Nuxt = function () {} + Object.assign(Nuxt.prototype, { + hook(type, fn) { + if (type === 'watch:fileChanged') { + Nuxt.fileChangedHook = fn + } + }, + clearHook: jest.fn(), + close: jest.fn(), + listen: jest.fn().mockImplementationOnce(() => Promise.resolve()), + showReady: jest.fn().mockImplementationOnce(() => Promise.resolve()) + }, implementation || {}) + + Command.prototype.importCore = jest.fn().mockImplementationOnce(() => { + return { Nuxt } + }) + return Nuxt +} + +export const mockBuilder = (implementation) => { + const Builder = function () {} + Object.assign(Builder.prototype, { + build: jest.fn().mockImplementationOnce(() => Promise.resolve()), + unwatch: jest.fn().mockImplementationOnce(() => Promise.resolve()), + watchServer: jest.fn().mockImplementationOnce(() => Promise.resolve()) + }, implementation || {}) + + Command.prototype.importBuilder = jest.fn().mockImplementationOnce(() => { + return { Builder } + }) + return Builder +} diff --git a/yarn.lock b/yarn.lock index 6a3473c652..c4650f501d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11088,6 +11088,15 @@ wrap-ansi@^3.0.1: string-width "^2.1.1" strip-ansi "^4.0.0" +wrap-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131" + integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg== + dependencies: + ansi-styles "^3.2.0" + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"