Testing Plugin Invocation Workflow in Vue CLI
This test suite validates the plugin invocation functionality in Vue CLI, focusing on ESLint and TypeScript integration. It covers various configuration scenarios and plugin installation workflows through comprehensive unit tests.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
vuejs/vue-cli
packages/@vue/cli/__tests__/invoke.spec.js
jest.setTimeout(20000)
jest.mock('inquirer')
const invoke = require('../lib/invoke')
const { expectPrompts } = require('inquirer')
const create = require('@vue/cli-test-utils/createTestProject')
const parseJS = file => {
const res = {}
;(new Function('module', file))(res) // eslint-disable-line no-new-func
return res.exports
}
const baseESLintConfig = Object.assign({}, require('@vue/cli-plugin-eslint/eslintOptions').config({
hasPlugin: (name) => { if (name === 'babel') { return true } }
}), {
rules: {
'no-console': 'off',
'no-debugger': 'off'
}
})
async function createAndInstall (name) {
const project = await create(name, {
plugins: {
'@vue/cli-plugin-babel': {}
}
})
// mock install
const pkg = JSON.parse(await project.read('package.json'))
pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
await project.write('package.json', JSON.stringify(pkg, null, 2))
return project
}
async function assertUpdates (project) {
const updatedPkg = JSON.parse(await project.read('package.json'))
expect(updatedPkg.scripts.lint).toBe('vue-cli-service lint')
expect(updatedPkg.devDependencies).toHaveProperty('lint-staged')
expect(updatedPkg.gitHooks).toEqual({
'pre-commit': 'lint-staged'
})
const eslintrc = parseJS(await project.read('.eslintrc.js'))
expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
extends: ['plugin:vue/essential', '@vue/airbnb']
}))
const lintedMain = await project.read('src/main.js')
expect(lintedMain).toMatch(';') // should've been linted in post-generate hook
}
test('invoke with inline options', async () => {
const project = await createAndInstall(`invoke-inline`)
await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb --lintOn save,commit`)
await assertUpdates(project)
})
test('invoke with prompts', async () => {
const project = await createAndInstall(`invoke-prompts`)
expectPrompts([
{
message: `Pick an ESLint config`,
choices: [`Error prevention only`, `Airbnb`, `Standard`, `Prettier`],
choose: 1
},
{
message: `Pick additional lint features`,
choices: [`on save`, 'on commit'],
check: [0, 1]
}
])
// need to be in the same process to have inquirer mocked
// so calling directly
const cwd = process.cwd()
// By default, @babel/eslint-parser will load babel.config.js in `path.resolve('.')`(equals `process.cwd()`) through @babel/core.
// chdir, and let @babel/eslint-parser find the babel.config.js in our test project
process.chdir(project.dir)
await invoke(`eslint`, {}, project.dir)
await assertUpdates(project)
// restore
process.chdir(cwd)
})
test('invoke with ts', async () => {
const project = await create(`invoke-existing`, {
useConfigFiles: true,
plugins: {
'@vue/cli-plugin-babel': {},
'@vue/cli-plugin-eslint': { config: 'base' }
}
})
// mock install
const pkg = JSON.parse(await project.read('package.json'))
pkg.devDependencies['@vue/cli-plugin-typescript'] = '*'
await project.write('package.json', JSON.stringify(pkg, null, 2))
// mock existing vue.config.js
await project.write('vue.config.js', `module.exports = { lintOnSave: 'default' }`)
const eslintrc = parseJS(await project.read('.eslintrc.js'))
expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
extends: ['plugin:vue/essential', 'eslint:recommended']
}))
await project.run(`${require.resolve('../bin/vue')} invoke typescript --classComponent --useTsWithBabel`)
const updatedESLintrc = parseJS(await project.read('.eslintrc.js'))
expect(updatedESLintrc).toEqual(Object.assign({}, baseESLintConfig, {
extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/typescript'],
parserOptions: {
parser: '@typescript-eslint/parser'
}
}))
})
test('invoke with existing files (yaml)', async () => {
const project = await create(`invoke-existing-yaml`, {
useConfigFiles: true,
plugins: {
'@vue/cli-plugin-babel': {},
'@vue/cli-plugin-eslint': {}
}
})
const eslintrc = parseJS(await project.read('.eslintrc.js'))
expect(eslintrc).toEqual(Object.assign({}, baseESLintConfig, {
extends: ['plugin:vue/essential', 'eslint:recommended']
}))
await project.rm(`.eslintrc.js`)
await project.write(`.eslintrc.yml`, `
root: true
extends:
- plugin:vue/essential
- eslint:recommended
`.trim())
await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb`)
const updated = await project.read('.eslintrc.yml')
expect(updated).toMatch(`
extends:
- plugin:vue/essential
- eslint:recommended
- '@vue/airbnb'
`.trim())
})
test('invoking a plugin that renames files', async () => {
const project = await create(`invoke-rename`, { plugins: {} })
const pkg = JSON.parse(await project.read('package.json'))
pkg.devDependencies['@vue/cli-plugin-typescript'] = '*'
await project.write('package.json', JSON.stringify(pkg, null, 2))
await project.run(`${require.resolve('../bin/vue')} invoke typescript -d`)
expect(project.has('src/main.js')).toBe(false)
})
test('should prompt if invoking in a git repository with uncommitted changes', async () => {
delete process.env.VUE_CLI_SKIP_DIRTY_GIT_PROMPT
const project = await create('invoke-dirty', {
plugins: {
'@vue/cli-plugin-babel': {}
}
})
await project.write('some-random-file', '')
expectPrompts([
{
message: `Still proceed?`,
confirm: true
}
])
await invoke(`babel`, {}, project.dir)
})
test.todo('invoke: should respect plugin resolveFrom')