initial plugin tests

This commit is contained in:
Evan You
2018-01-05 13:13:39 -05:00
parent e0755f4ce9
commit 0151a8182d
15 changed files with 248 additions and 35 deletions

View File

@@ -16,7 +16,7 @@ cd packages/@vue/cli
yarn link
# create test projects in /packages/test
export VUE_CLI_TEST=true # necessary for manual tests to work
export VUE_CLI_DEBUG=true # necessary for manual tests to work
cd -
cd packages/test
vue create test-app

View File

@@ -0,0 +1,5 @@
{
"rules": {
"vue-libs/no-async-functions": 0
}
}

View File

@@ -0,0 +1,83 @@
const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin')
it('base', async () => {
const { pkg } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {}
})
expect(pkg.scripts.lint).toBeTruthy()
expect(pkg.eslintConfig).toEqual({
extends: ['plugin:vue/essential', 'eslint:recommended']
})
expect(pkg.devDependencies).toHaveProperty('eslint-plugin-vue')
})
it('airbnb', async () => {
const { pkg } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {
config: 'airbnb'
}
})
expect(pkg.scripts.lint).toBeTruthy()
expect(pkg.eslintConfig).toEqual({
extends: ['plugin:vue/essential', '@vue/airbnb']
})
expect(pkg.devDependencies).toHaveProperty('eslint-plugin-vue')
expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-airbnb')
})
it('standard', async () => {
const { pkg } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {
config: 'standard'
}
})
expect(pkg.scripts.lint).toBeTruthy()
expect(pkg.eslintConfig).toEqual({
extends: ['plugin:vue/essential', '@vue/standard']
})
expect(pkg.devDependencies).toHaveProperty('eslint-plugin-vue')
expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-standard')
})
it('lint on save', async () => {
const { pkg } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {
lintOn: 'save'
}
})
expect(pkg.vue).toEqual({
lintOnSave: true
})
})
it('lint on commit', async () => {
const { pkg } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {
lintOn: 'commit'
}
})
expect(pkg.scripts.precommit).toBe('lint-staged')
expect(pkg.devDependencies).toHaveProperty('lint-staged')
expect(pkg['lint-staged']).toEqual({
'*.js': ['vue-cli-service lint', 'git add'],
'*.vue': ['vue-cli-service lint', 'git add']
})
})
it('prettier', async () => {
// TODO
})

View File

@@ -0,0 +1,25 @@
const create = require('@vue/cli-test-utils/createTestProjectWithOptions')
it('should work', async () => {
const { read, write, exec } = await create('eslint', {
plugins: {
'@vue/cli-plugin-babel': {},
'@vue/cli-plugin-eslint': {
config: 'airbnb',
lintOn: 'commit'
}
}
})
// should've applied airbnb autofix
const main = await read('src/main.js')
expect(main).toContain(';')
// remove semicolons
await write('src/main.js', main.replace(/;/g, ''))
// lint
await exec('node_modules/.bin/vue-cli-service lint')
expect(await read('src/main.js')).toContain(';')
// TODO lint-on-commit
// TODO lint-on-save
})

View File

@@ -0,0 +1,5 @@
{
"rules": {
"vue-libs/no-async-functions": 0
}
}

View File

@@ -1,3 +1,5 @@
// using this requires mocking fs & inquirer
const Creator = require('@vue/cli/lib/Creator')
const { expectPrompts } = require('inquirer') // from mock

View File

@@ -1,3 +0,0 @@
module.exports = function createTestProject (plugins) {
}

View File

@@ -0,0 +1,63 @@
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const childProcess = require('child_process')
const cliBinPath = require.resolve('@vue/cli/bin/vue')
const { spawn } = childProcess
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const _exec = promisify(childProcess.exec)
const mkdirp = promisify(require('mkdirp'))
module.exports = function createTestProjectWithOptions (name, config, cwd) {
cwd = cwd || path.resolve(__dirname, '../../test')
config = Object.assign({
packageManager: 'yarn',
useTaobaoRegistry: false,
plugins: {}
}, config)
const read = file => {
return readFile(path.resolve(cwd, name, file), 'utf-8')
}
const write = (file, content) => {
const targetPath = path.resolve(cwd, name, file)
const dir = path.dirname(targetPath)
return mkdirp(dir).then(() => writeFile(targetPath, content))
}
const exec = command => {
return _exec(command, { cwd: path.resolve(cwd, name) })
}
return new Promise((resolve, reject) => {
const child = spawn(cliBinPath, [
'create',
name,
'--force',
'--config',
JSON.stringify(config)
], {
cwd,
env: process.env,
stdio: 'inherit'
})
child.on('exit', code => {
if (code !== 0) {
reject(`cli creation failed with code ${code}`)
} else {
resolve({
read,
write,
exec
})
}
})
child.on('error', reject)
})
}

View File

@@ -0,0 +1,11 @@
const Generator = require('@vue/cli/lib/Generator')
module.exports = async function generateWithPlugin (plugin, pkg) {
process.env.VUE_CLI_SKIP_WRITE = true
const generator = new Generator('/', pkg || {}, [].concat(plugin))
await generator.generate()
return {
pkg: generator.pkg,
files: generator.files
}
}

View File

@@ -2,7 +2,6 @@
"name": "@vue/cli-test-utils",
"version": "0.1.0",
"description": "test utilities for vue-cli packages",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git"

View File

@@ -23,10 +23,12 @@ program
program
.command('create <app-name>')
.description('create a new project powered by vue-cli-service')
.option('-s, --saved', 'Skip prompts and use saved options')
.option('-d, --default', 'Skip prompts and use default options')
.option('-s, --saved', 'Skip prompts and use saved config')
.option('-d, --default', 'Skip prompts and use default config')
.option('-c, --config <json>', 'Skip prompts and use inline JSON string as config')
.option('-r, --registry <url>', 'Use specified NPM registry when installing dependencies')
.option('-p, --package-manager <command>', 'Use specified NPM client when installing dependencies')
.option('-m, --package-manager <command>', 'Use specified NPM client when installing dependencies')
.option('-f, --force', 'Overwrite target directory if it exists')
.action(require('../lib/create'))
program

View File

@@ -20,6 +20,7 @@ const {
const {
log,
error,
hasGit,
hasYarn,
logWithSpinner,
@@ -45,6 +46,7 @@ module.exports = class Creator {
}
async create (cliOptions = {}) {
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
const { name, context, createCompleteCbs } = this
let options
@@ -52,6 +54,13 @@ module.exports = class Creator {
options = loadOptions()
} else if (cliOptions.default) {
options = defaults
} else if (cliOptions.config) {
try {
options = JSON.parse(cliOptions.config)
} catch (e) {
error(`CLI inline config is not valid JSON: ${cliOptions.config}`)
process.exit(1)
}
} else {
options = await this.promptAndResolveOptions()
}
@@ -87,7 +96,7 @@ module.exports = class Creator {
// install plugins
logWithSpinner('⚙', `Installing CLI plugins. This might take a while...`)
const deps = Object.keys(options.plugins)
if (process.env.VUE_CLI_TEST) {
if (isTestOrDebug) {
// in development, avoid installation process
setupDevProject(context, deps)
} else {
@@ -108,7 +117,7 @@ module.exports = class Creator {
// install additional deps (injected by generators)
logWithSpinner('📦', `Installing additional dependencies...`)
if (!process.env.VUE_CLI_TEST) {
if (!isTestOrDebug) {
await installDeps(context, packageManager, null, cliOptions.registry)
}
@@ -186,7 +195,7 @@ module.exports = class Creator {
message: `Please pick a project creation mode:`,
choices: [
{
name: `Zero-configuration with defaults (${defualtFeatures})`,
name: `Zero-config with defaults (${defualtFeatures})`,
value: 'default'
},
{
@@ -199,7 +208,7 @@ module.exports = class Creator {
if (savedOptions.plugins) {
const savedFeatures = formatFeatures(savedOptions.plugins)
modePrompt.choices.unshift({
name: `Use previously saved preferences (${savedFeatures})`,
name: `Use previously saved config (${savedFeatures})`,
value: 'saved'
})
}

View File

@@ -10,29 +10,33 @@ const { error, stopSpinner } = require('@vue/cli-shared-utils')
async function create (projectName, options) {
const targetDir = path.resolve(process.cwd(), projectName)
if (fs.existsSync(targetDir)) {
clearConsole()
const { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
choices: [
{ name: 'Overwrite', value: 'overwrite' },
{ name: 'Merge', value: 'merge' },
{ name: 'Cancel', value: false }
]
}
])
if (!action) {
return
} else if (action === 'overwrite') {
if (options.force) {
rimraf.sync(targetDir)
} else {
clearConsole()
const { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
choices: [
{ name: 'Overwrite', value: 'overwrite' },
{ name: 'Merge', value: 'merge' },
{ name: 'Cancel', value: false }
]
}
])
if (!action) {
return
} else if (action === 'overwrite') {
rimraf.sync(targetDir)
}
}
}
const promptModules = fs
.readdirSync(path.resolve(__dirname, './promptModules'))
.filter(file => file.charAt(0) !== '.')
.filter(file => file.charAt(0) !== '.' && file.charAt(0) !== '_')
.map(file => require(`./promptModules/${file}`))
const creator = new Creator(projectName, targetDir, promptModules)

View File

@@ -4,7 +4,10 @@ const { clearConsole } = require('@vue/cli-shared-utils')
let title = chalk.bold.green(`Vue CLI v${version}`)
if (process.env.VUE_CLI_TEST) {
title += ' ' + chalk.magenta.bold('TEST MODE')
title += ' ' + chalk.blue.bold('TEST')
}
if (process.env.VUE_CLI_DEBUG) {
title += ' ' + chalk.magenta.bold('DEBUG')
}
module.exports = () => clearConsole(title)

View File

@@ -1,11 +1,16 @@
const fs = require('fs')
const path = require('path')
const mkdirp = require('mkdirp')
const { promisify } = require('util')
const mkdirp = promisify(require('mkdirp'))
const write = promisify(fs.writeFile)
module.exports = function writeFileTree (dir, files) {
for (const name in files) {
const filePath = path.join(dir, name)
mkdirp.sync(path.dirname(filePath))
fs.writeFileSync(filePath, files[name])
if (process.env.VUE_CLI_SKIP_WRITE) {
return
}
return Promise.all(Object.keys(files).map(async (name) => {
const filePath = path.join(dir, name)
await mkdirp(path.dirname(filePath))
await write(filePath, files[name])
}))
}