From bb80fd977fa08990454fdec01e9c4a2c3841c459 Mon Sep 17 00:00:00 2001 From: Justin Helmer Date: Thu, 4 Oct 2018 21:51:10 -0700 Subject: [PATCH] feat: support for babelConfig and tsConfig - babelConfig/tsConfig can take a boolean (config file lookup), object (inline config options), or string (path to config file) - deprecated babelRcFile and tsConfigFile options - Matches ts-jest API for babelConfig/tsConfig, with the exception that the default behavior for babelConfig is switched (enabled) - Not a breaking change --- README.md | 138 +++++++++++++++++++++++++++- lib/load-babel-config.js | 45 +++++++-- lib/load-typescript-config.js | 40 ++++++-- test/load-babel-config.spec.js | 59 +++++++++++- test/load-typescript-config.spec.js | 66 ++++++++++++- 5 files changed, 324 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d8d235ed..8a4b2854 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Jest Vue transformer with source map support +> **NOTE:** This is documentation for `vue-jest@3.x`. [View the vue-jest@2.x documentation](https://github.com/vuejs/vue-jest/tree/e694fc7ce11ae1ac1c778ed7c4402515c5f0d5aa) + ## Usage ```bash @@ -10,7 +12,7 @@ npm install --save-dev vue-jest ## Setup -To define vue-jest as a transformer for your .vue files, you need to map .vue files to the vue-jest module. +To define `vue-jest` as a transformer for your `.vue` files, map them to the `vue-jest` module: ```json { @@ -57,28 +59,154 @@ vue-jest compiles the script and template of SFCs into a JavaScript file that Je - **typescript** (`lang="ts"`, `lang="typescript"`) - **coffeescript** (`lang="coffee"`, `lang="coffeescript"`) -To define a tsconfig file that vue-jest will use when transpiling typescript, you can specify it in the jest globals +### Global Jest options + +You can change the behavior of `vue-jest` by using `jest.globals`. + +> *Tip:* Need programmatic configuration? Use the [--config](https://jestjs.io/docs/en/cli.html#config-path) option in Jest CLI, and export a `.js` file + +#### babelConfig + +Provide `babelConfig` in one of the following formats: + +- `` +- `` +- `` + +##### Boolean + +- `true` - Enable Babel processing. `vue-jest` will try to find Babel configuration using [find-babel-config](https://www.npmjs.com/package/find-babel-config). + +> This is the default behavior if [babelConfig](#babelconfig) is not defined. + +- `false` - Skip Babel processing entirely: + +```json +{ + "jest": { + "globals": { + "vue-jest": { + "babelConfig": false + } + } + } +} +``` + +##### Object + +Provide inline [Babel options](https://babeljs.io/docs/en/options): ```json { "jest": { "globals": { "vue-jest": { - "tsConfigFile": "tsconfig.jest.json" + "babelConfig": { + "presets": [ + [ + "env", + { + "useBuiltIns": "entry", + "shippedProposals": true + } + ] + ], + "plugins": [ + "syntax-dynamic-import" + ], + "env": { + "test": { + "plugins": [ + "dynamic-import-node" + ] + } + } + } } } } } ``` -To define a babelrc file that vue-jest will use when transpiling javascript, you can specify it in the jest globals +##### String + +If a string is provided, it will be an assumed path to a babel configuration file (e.g. `.babelrc`, `.babelrc.js`). +- Config file should export a Babel configuration object. +- Should *not* point to a [project-wide configuration file (babel.config.js)](https://babeljs.io/docs/en/config-files#project-wide-configuration), which exports a function. + +```json +{ + "jest": { + "globals": { + "vue-jest": { + "babelConfig": "path/to/.babelrc.js" + } + } + } +} +``` + +To use the [Config Function API](https://babeljs.io/docs/en/config-files#config-function-api), use inline options instead. i.e.: + +```json +{ + "jest": { + "globals": { + "vue-jest": { + "babelConfig": { + "configFile": "path/to/babel.config.js" + } + } + } + } +} +``` + +#### tsConfig + +Provide `tsConfig` in one of the following formats: + +- `` +- `` +- `` + +##### Boolean + +- `true` - Process TypeScript files using custom configuration. `vue-jest` will try to find TypeScript configuration using [tsconfig.loadSync](https://www.npmjs.com/package/tsconfig#api). + +> This is the default behavior if [tsConfig](#tsConfig) is not defined. + +- `false` - Process TypeScript files using the [default configuration provided by vue-jest](https://github.com/vuejs/vue-jest/blob/master/lib/load-typescript-config.js#L5-L27). + +##### Object + +Provide inline [TypeScript compiler options](https://www.typescriptlang.org/docs/handbook/compiler-options.html): + +```json +{ + "jest": { + "globals": { + "vue-jest": { + "tsConfig": { + "importHelpers": true + } + } + } + } +} +``` + +##### String + +If a string is provided, it will be an assumed path to a TypeScript configuration file: ```json { "jest": { "globals": { "vue-jest": { - "babelRcFile": "jest.babelrc" + "tsConfig": "path/to/tsconfig.json" } } } diff --git a/lib/load-babel-config.js b/lib/load-babel-config.js index 5c666909..d60ebccd 100644 --- a/lib/load-babel-config.js +++ b/lib/load-babel-config.js @@ -1,10 +1,22 @@ const findBabelConfig = require('find-babel-config') const logger = require('./logger') const cache = require('./cache') +const deprecate = require('./deprecate') const path = require('path') const { readFileSync, existsSync } = require('fs') module.exports = function getBabelConfig (vueJestConfig, filePath) { + const find = () => { + const { file, config } = findBabelConfig.sync(filePath || process.cwd()) + + if (!file) { + logger.info('no .babelrc found, skipping babel compilation') + cache.set('babel-config', false) + return + } + + return config + } const cachedConfig = cache.get('babel-config') if (cachedConfig) { return cachedConfig @@ -14,22 +26,37 @@ module.exports = function getBabelConfig (vueJestConfig, filePath) { let babelConfig if (vueJestConfig.babelRcFile) { + deprecate.replace('babelRcFile', 'babelConfig') babelConfig = JSON.parse(readFileSync(vueJestConfig.babelRcFile)) } else if (existsSync('babel.config.js')) { babelConfig = require(path.resolve('babel.config.js')) - } else { - const { file, config } = findBabelConfig.sync(filePath || process.cwd()) - - if (!file) { - logger.info('no .babelrc found, skipping babel compilation') - cache.set('babel-config', false) - return + } else if (vueJestConfig.hasOwnProperty('babelConfig')) { + switch (typeof vueJestConfig.babelConfig) { + case 'string': + // a path to a config file is being passed in; load it + babelConfig = require(vueJestConfig.babelConfig) + break + case 'boolean': + // if babelConfig is true, search for it. If false, will end up + // returning undefined which results in no babel processing + if (vueJestConfig.babelConfig === true) { + babelConfig = find() + } + break + case 'object': + default: + // support for inline babel options + babelConfig = vueJestConfig.babelConfig + break } + } else { + babelConfig = find() + } - babelConfig = config + if (babelConfig) { + cache.set('babel-config', babelConfig) } - cache.set('babel-config', babelConfig) return babelConfig } } diff --git a/lib/load-typescript-config.js b/lib/load-typescript-config.js index aee1b970..eee908d6 100644 --- a/lib/load-typescript-config.js +++ b/lib/load-typescript-config.js @@ -1,5 +1,6 @@ const tsconfig = require('tsconfig') const cache = require('./cache') +const deprecate = require('./deprecate') const logger = require('./logger') const defaultTypescriptConfig = { @@ -27,6 +28,15 @@ const defaultTypescriptConfig = { } module.exports.loadTypescriptConfig = function loadTypescriptConfig (vueJestConfig) { + const find = () => { + const { path, config } = tsconfig.loadSync(process.cwd()) + + if (!path) { + logger.info('no tsconfig.json found, defaulting to default typescript options') + } + + return path ? config : defaultTypescriptConfig + } const cachedConfig = cache.get('typescript-config') if (cachedConfig) { return cachedConfig @@ -34,15 +44,31 @@ module.exports.loadTypescriptConfig = function loadTypescriptConfig (vueJestConf let typescriptConfig if (vueJestConfig.tsConfigFile) { + deprecate.replace('tsConfigFile', 'tsConfig') typescriptConfig = tsconfig.readFileSync(vueJestConfig.tsConfigFile) - } else { - const { path, config } = tsconfig.loadSync(process.cwd()) - - if (!path) { - logger.info('no tsconfig.json found, defaulting to default typescript options') + } else if (vueJestConfig.hasOwnProperty('tsConfig')) { + switch (typeof vueJestConfig.tsConfig) { + case 'string': + // a path to a config file is being passed in; load it + typescriptConfig = require(vueJestConfig.tsConfig) + break + case 'boolean': + // if tsConfig is true, search for it + if (vueJestConfig.tsConfig === true) { + typescriptConfig = find() + } else { + // use default typescript options + typescriptConfig = defaultTypescriptConfig + } + break + case 'object': + default: + // support for inline typescript options + typescriptConfig = vueJestConfig.tsConfig + break } - - typescriptConfig = path ? config : defaultTypescriptConfig + } else { + typescriptConfig = find() } cache.set('typescript-config', typescriptConfig) diff --git a/test/load-babel-config.spec.js b/test/load-babel-config.spec.js index 1b898e70..0ea12de2 100644 --- a/test/load-babel-config.spec.js +++ b/test/load-babel-config.spec.js @@ -1,3 +1,4 @@ +import findBabelConfig from 'find-babel-config' import loadBabelConfig from '../lib/load-babel-config' import { resolve } from 'path' import { @@ -10,6 +11,7 @@ import { } from 'fs' import clearModule from 'clear-module' import cache from '../lib/cache' +import deprecate from '../lib/deprecate' describe('load-babel-config.js', () => { beforeEach(() => { @@ -38,7 +40,10 @@ describe('load-babel-config.js', () => { expect(babelConfigCached).toBe(undefined) }) - it('reads babelrc from jest globals if exists', () => { + it('[DEPRECATED] reads babelrc from jest globals if exists', () => { + const replace = deprecate.replace + deprecate.replace = jest.fn() + const jestGlobalBabelPath = resolve(__dirname, '../jest.babelrc') writeFileSync(jestGlobalBabelPath, JSON.stringify({ plugins: ['foo'] @@ -48,7 +53,9 @@ describe('load-babel-config.js', () => { babelRcFile: 'jest.babelrc' }) expect(babelConfig).toEqual(jestGlobalBabelConfig) + expect(deprecate.replace).toHaveBeenCalledWith('babelRcFile', 'babelConfig') unlinkSync(jestGlobalBabelPath) + deprecate.replace = replace }) it('reads default babel if there is .babelrc', () => { @@ -103,4 +110,54 @@ describe('load-babel-config.js', () => { expect(babelConfig).toEqual(config) unlinkSync(babelConfigPath) }) + + describe('babelConfig option', () => { + it('supports a path to a babel configuration file', () => { + const babelConfigPath = resolve(__dirname, '../some-babel-config.js') + const config = { + plugins: ['foo'] + } + writeFileSync(babelConfigPath, `module.exports = ${JSON.stringify(config)}`) + const babelConfig = loadBabelConfig({ + babelConfig: babelConfigPath + }) + expect(babelConfig).toEqual(config) + }) + + it('supports a boolean indicating whether or not to search for babel config', () => { + const config = { + plugins: ['foo'] + } + findBabelConfig.sync = jest.fn(() => ({ file: true, config })) + const noBabelConfig = loadBabelConfig({ + babelConfig: false + }) + expect(findBabelConfig.sync).not.toHaveBeenCalled() + expect(noBabelConfig).toBeUndefined() + + const babelConfig = loadBabelConfig({ + babelConfig: true + }) + expect(findBabelConfig.sync).toHaveBeenCalled() + expect(babelConfig).toEqual(config) + findBabelConfig.sync.mockRestore() + }) + + it('supports an inline babel configuration object', () => { + const config = { + plugins: ['foo'] + } + const babelConfig = loadBabelConfig({ + babelConfig: config + }) + expect(babelConfig).toEqual(config) + }) + + it('defaults to searching for babel config if option is not provided', () => { + findBabelConfig.sync = jest.fn(() => ({})) + loadBabelConfig({}) + expect(findBabelConfig.sync).toHaveBeenCalled() + findBabelConfig.sync.mockRestore() + }) + }) }) diff --git a/test/load-typescript-config.spec.js b/test/load-typescript-config.spec.js index 0a1b05c9..f27ea62b 100644 --- a/test/load-typescript-config.spec.js +++ b/test/load-typescript-config.spec.js @@ -1,3 +1,5 @@ +import deprecate from '../lib/deprecate' +const tsc = require('tsconfig') import { defaultConfig, loadTypescriptConfig } from '../lib/load-typescript-config' import { resolve } from 'path' import { @@ -36,7 +38,10 @@ describe('load-typescript-config.js', () => { expect(tsconfigCachedConfig).toEqual(defaultConfig) }) - it('returns the tsconfig specified in jest globals', () => { + it('[DEPRECATED] returns the tsconfig specified in jest globals', () => { + const replace = deprecate.replace + deprecate.replace = jest.fn() + const jestGlobalTsConfigPath = resolve(__dirname, '../tsconfig.jest.json') writeFileSync(jestGlobalTsConfigPath, JSON.stringify({ @@ -48,10 +53,11 @@ describe('load-typescript-config.js', () => { const tsconfig = loadTypescriptConfig({ tsConfigFile: jestGlobalTsConfigPath }) - expect(tsconfig).toEqual(jestGlobalTsConfig) + expect(deprecate.replace).toHaveBeenCalledWith('tsConfigFile', 'tsConfig') unlinkSync(jestGlobalTsConfigPath) + deprecate.replace = replace }) it('reads default tsconfig if there is tsconfig.json', () => { @@ -72,4 +78,60 @@ describe('load-typescript-config.js', () => { throw err } }) + + describe('tsConfig option', () => { + it('supports a path to a ts configuration file', () => { + const tsConfigPath = resolve(__dirname, '../some-ts-config.json') + const config = { + importHelpers: true + } + writeFileSync(tsConfigPath, JSON.stringify(config)) + const tsConfig = loadTypescriptConfig({ + tsConfig: tsConfigPath + }) + expect(tsConfig).toEqual(config) + }) + + it('supports a boolean "true" indicating to search for ts config', () => { + const config = { + importHelpers: true + } + tsc.loadSync = jest.fn(() => ({ path: true, config })) + const tsConfig = loadTypescriptConfig({ + tsConfig: true + }) + expect(tsc.loadSync).toHaveBeenCalled() + expect(tsConfig).toEqual(config) + tsc.loadSync.mockRestore() + }) + + it('supports a boolean "false" indicating to use the default ts config options', () => { + const config = { + importHelpers: true + } + tsc.loadSync = jest.fn(() => ({ path: true, config })) + const defaultTsConfig = loadTypescriptConfig({ + tsConfig: false + }) + expect(tsc.loadSync).not.toHaveBeenCalled() + expect(defaultTsConfig).toEqual(defaultConfig) + }) + + it('supports an inline ts configuration object', () => { + const config = { + importHelpers: true + } + const tsConfig = loadTypescriptConfig({ + tsConfig: config + }) + expect(tsConfig).toEqual(config) + }) + + it('defaults to searching for ts config if option is not provided', () => { + tsc.loadSync = jest.fn(() => ({})) + loadTypescriptConfig({}) + expect(tsc.loadSync).toHaveBeenCalled() + tsc.loadSync.mockRestore() + }) + }) })