From 0151a8182df40621bb53961dd077431f59714810 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 5 Jan 2018 13:13:39 -0500 Subject: [PATCH] initial plugin tests --- README.md | 2 +- .../cli-plugin-eslint/__tests__/.eslintrc | 5 ++ .../__tests__/generator.spec.js | 83 +++++++++++++++++++ .../__tests__/plugin.spec.js | 25 ++++++ packages/@vue/cli-service-global/.eslintrc | 5 ++ .../@vue/cli-test-utils/assertPromptModule.js | 2 + .../@vue/cli-test-utils/createTestProject.js | 3 - .../createTestProjectWithOptions.js | 63 ++++++++++++++ .../@vue/cli-test-utils/generateWithPlugin.js | 11 +++ packages/@vue/cli-test-utils/package.json | 1 - packages/@vue/cli/bin/vue | 8 +- packages/@vue/cli/lib/Creator.js | 17 +++- packages/@vue/cli/lib/create.js | 38 +++++---- packages/@vue/cli/lib/util/clearConsole.js | 5 +- packages/@vue/cli/lib/util/writeFileTree.js | 15 ++-- 15 files changed, 248 insertions(+), 35 deletions(-) create mode 100644 packages/@vue/cli-plugin-eslint/__tests__/.eslintrc create mode 100644 packages/@vue/cli-plugin-eslint/__tests__/generator.spec.js create mode 100644 packages/@vue/cli-plugin-eslint/__tests__/plugin.spec.js create mode 100644 packages/@vue/cli-service-global/.eslintrc delete mode 100644 packages/@vue/cli-test-utils/createTestProject.js create mode 100644 packages/@vue/cli-test-utils/createTestProjectWithOptions.js create mode 100644 packages/@vue/cli-test-utils/generateWithPlugin.js diff --git a/README.md b/README.md index c73369d5b..644a91d67 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/packages/@vue/cli-plugin-eslint/__tests__/.eslintrc b/packages/@vue/cli-plugin-eslint/__tests__/.eslintrc new file mode 100644 index 000000000..92b6bda70 --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/__tests__/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "vue-libs/no-async-functions": 0 + } +} diff --git a/packages/@vue/cli-plugin-eslint/__tests__/generator.spec.js b/packages/@vue/cli-plugin-eslint/__tests__/generator.spec.js new file mode 100644 index 000000000..38134596c --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/__tests__/generator.spec.js @@ -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 +}) diff --git a/packages/@vue/cli-plugin-eslint/__tests__/plugin.spec.js b/packages/@vue/cli-plugin-eslint/__tests__/plugin.spec.js new file mode 100644 index 000000000..41edd8a51 --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/__tests__/plugin.spec.js @@ -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 +}) diff --git a/packages/@vue/cli-service-global/.eslintrc b/packages/@vue/cli-service-global/.eslintrc new file mode 100644 index 000000000..92b6bda70 --- /dev/null +++ b/packages/@vue/cli-service-global/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "vue-libs/no-async-functions": 0 + } +} diff --git a/packages/@vue/cli-test-utils/assertPromptModule.js b/packages/@vue/cli-test-utils/assertPromptModule.js index b4df66434..f281f456e 100644 --- a/packages/@vue/cli-test-utils/assertPromptModule.js +++ b/packages/@vue/cli-test-utils/assertPromptModule.js @@ -1,3 +1,5 @@ +// using this requires mocking fs & inquirer + const Creator = require('@vue/cli/lib/Creator') const { expectPrompts } = require('inquirer') // from mock diff --git a/packages/@vue/cli-test-utils/createTestProject.js b/packages/@vue/cli-test-utils/createTestProject.js deleted file mode 100644 index 6b3bf6baa..000000000 --- a/packages/@vue/cli-test-utils/createTestProject.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function createTestProject (plugins) { - -} diff --git a/packages/@vue/cli-test-utils/createTestProjectWithOptions.js b/packages/@vue/cli-test-utils/createTestProjectWithOptions.js new file mode 100644 index 000000000..257427425 --- /dev/null +++ b/packages/@vue/cli-test-utils/createTestProjectWithOptions.js @@ -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) + }) +} diff --git a/packages/@vue/cli-test-utils/generateWithPlugin.js b/packages/@vue/cli-test-utils/generateWithPlugin.js new file mode 100644 index 000000000..3f10fb0d0 --- /dev/null +++ b/packages/@vue/cli-test-utils/generateWithPlugin.js @@ -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 + } +} diff --git a/packages/@vue/cli-test-utils/package.json b/packages/@vue/cli-test-utils/package.json index 97930fe19..b7806d39d 100644 --- a/packages/@vue/cli-test-utils/package.json +++ b/packages/@vue/cli-test-utils/package.json @@ -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" diff --git a/packages/@vue/cli/bin/vue b/packages/@vue/cli/bin/vue index fa6e92a7e..424965c89 100755 --- a/packages/@vue/cli/bin/vue +++ b/packages/@vue/cli/bin/vue @@ -23,10 +23,12 @@ program program .command('create ') .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 ', 'Skip prompts and use inline JSON string as config') .option('-r, --registry ', 'Use specified NPM registry when installing dependencies') - .option('-p, --package-manager ', 'Use specified NPM client when installing dependencies') + .option('-m, --package-manager ', 'Use specified NPM client when installing dependencies') + .option('-f, --force', 'Overwrite target directory if it exists') .action(require('../lib/create')) program diff --git a/packages/@vue/cli/lib/Creator.js b/packages/@vue/cli/lib/Creator.js index d325a29fe..ce068c962 100644 --- a/packages/@vue/cli/lib/Creator.js +++ b/packages/@vue/cli/lib/Creator.js @@ -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' }) } diff --git a/packages/@vue/cli/lib/create.js b/packages/@vue/cli/lib/create.js index 3c709719c..ad46b4d29 100644 --- a/packages/@vue/cli/lib/create.js +++ b/packages/@vue/cli/lib/create.js @@ -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) diff --git a/packages/@vue/cli/lib/util/clearConsole.js b/packages/@vue/cli/lib/util/clearConsole.js index ac9efce8b..44f41fae6 100644 --- a/packages/@vue/cli/lib/util/clearConsole.js +++ b/packages/@vue/cli/lib/util/clearConsole.js @@ -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) diff --git a/packages/@vue/cli/lib/util/writeFileTree.js b/packages/@vue/cli/lib/util/writeFileTree.js index 815c73bb9..4fea95011 100644 --- a/packages/@vue/cli/lib/util/writeFileTree.js +++ b/packages/@vue/cli/lib/util/writeFileTree.js @@ -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]) + })) }