diff --git a/docs/recipes/babelrc.md b/docs/recipes/babelrc.md new file mode 100644 index 000000000..4f9cf2e18 --- /dev/null +++ b/docs/recipes/babelrc.md @@ -0,0 +1,86 @@ +# Configuring Babel + +There are multiple options for configuring how AVA transpiles your tests using Babel. + + - [Specify a complete config in `package.json`](#specify-a-complete-config-in-packagejson) + - [Extend existing `.babelrc` without modification](#extend-existing-babelrc-without-modification) + - [Extend existing `.babelrc` with additional plugins or presets](#extend-existing-babelrc-with-additional-plugins-or-presets) + - [Extend an alternate config file (i.e. not `.babelrc`)](#extend-alternate-config-file) + - [Notes](#notes) + +## Specify a complete config in `package.json` + +The `babelrc` option defaults to `false`, meaning `.babelrc` files are not considered when transpiling tests. This means you must specify your complete config in `package.json`. + +```json +{ + "ava": { + "babel": { + "plugins": ["rewire"], + "presets": ["es2015"] + } + } +} +``` + +## Extend existing `.babelrc` without modification + +Use the `"inherit"` shortcut if you want your tests transpiled the same as your sources. This will use your `.babelrc` directly (with a few additional [internal plugins](#notes)). + +`package.json`: + +```json +{ + "ava": { + "babel": "inherit" + } +} +``` + +## Extend existing `.babelrc` with additional plugins or presets + +Set `babelrc` to `true`. This will use your `.babelrc` and extend it with any additional plugins specified. + +`package.json`: + +```json +{ + "ava": { + "babel": { + "babelrc": true, + "plugins": ["custom-plugin-name"], + "presets": ["custom-preset"] + } + } +} +``` + +## Extend alternate config file. + + +If, for some reason, you do not want to extend `.babelrc`, set the `extends` option to the alternate config you want to use during testing. + +`package.json`: + +```json +{ + "ava": { + "babel": { + "extends": "./babel-test-config.json", + "plugins": ["custom-plugin-name"], + "presets": ["custom-preset"] + } + } +} +``` + +The above uses `babel-test-config.json` as the base config, and extends it with the custom plugins and presets specified. + +## Notes + +AVA *always* adds a few custom Babel plugins when transpiling your plugins. They serve a variety of functions: + + * Enable `power-assert` support. + * Rewrite require paths internal AVA dependencies like `babel-runtime` (important if you are still using `npm@2`). + * Generate test metadata to determine which files should be run first (*future*). + * Static analysis of dependencies for precompilation (*future*). diff --git a/readme.md b/readme.md index 84f520e45..80b5072ef 100644 --- a/readme.md +++ b/readme.md @@ -581,6 +581,8 @@ You can also use the special `"inherit"` keyword. This makes AVA defer to the Ba } ``` +See AVA's [`.babelrc` recipe](docs/recipes/babelrc.md) for further examples and a more detailed explanation of configuration options. + Note that AVA will *always* apply the [`espower`](https://github.com/power-assert-js/babel-plugin-espower) and [`transform-runtime`](https://babeljs.io/docs/plugins/transform-runtime/) plugins. ### TypeScript support @@ -916,6 +918,7 @@ It's the [Andromeda galaxy](https://simple.wikipedia.org/wiki/Andromeda_galaxy). - [When to use `t.plan()`](docs/recipes/when-to-use-plan.md) - [Browser testing](docs/recipes/browser-testing.md) - [TypeScript](docs/recipes/typescript.md) +- [Configuring Babel](docs/recipes/babelrc.md) ## Support diff --git a/test/api.js b/test/api.js index 9f5cb3ef8..ae3d2ebd3 100644 --- a/test/api.js +++ b/test/api.js @@ -5,7 +5,7 @@ var figures = require('figures'); var rimraf = require('rimraf'); var test = require('tap').test; var Api = require('../api'); -var testDoublerPlugin = require('./fixture/babel-plugin-test-doubler'); +var testCapitalizerPlugin = require('./fixture/babel-plugin-test-capitalizer'); test('must be called with new', function (t) { t.throws(function () { @@ -731,29 +731,116 @@ test('verify test count', function (t) { }); test('Custom Babel Plugin Support', function (t) { - t.plan(1); + t.plan(2); var api = new Api({ babelConfig: { presets: ['es2015', 'stage-2'], - plugins: [testDoublerPlugin] - } + plugins: [testCapitalizerPlugin] + }, + cacheEnabled: false }); - api.run([path.join(__dirname, 'fixture/es2015.js')]) + api.on('test', function (data) { + t.is(data.title, 'FOO'); + }); + + api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) .then( function () { - t.is(api.passCount, 2); + t.is(api.passCount, 1); }, t.threw ); }); test('Default babel config doesn\'t use .babelrc', function (t) { - t.plan(1); + t.plan(2); var api = new Api(); + api.on('test', function (data) { + t.is(data.title, 'foo'); + }); + + return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) + .then(function () { + t.is(api.passCount, 1); + }); +}); + +test('babelConfig:"inherit" uses .babelrc', function (t) { + t.plan(3); + + var api = new Api({ + babelConfig: 'inherit', + cacheEnabled: false + }); + + api.on('test', function (data) { + t.ok((data.title === 'foo') || (data.title === 'repeated test: foo')); + }); + + return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) + .then(function () { + t.is(api.passCount, 2); + }); +}); + +test('babelConfig:{babelrc:true} uses .babelrc', function (t) { + t.plan(3); + + var api = new Api({ + babelConfig: {babelrc: true}, + cacheEnabled: false + }); + + api.on('test', function (data) { + t.ok((data.title === 'foo') || (data.title === 'repeated test: foo')); + }); + + return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) + .then(function () { + t.is(api.passCount, 2); + }); +}); + +test('babelConfig:{babelrc:true, plugins:[...]} merges plugins with .babelrc', function (t) { + t.plan(3); + + var api = new Api({ + babelConfig: { + plugins: [testCapitalizerPlugin], + babelrc: true + }, + cacheEnabled: false + }); + + api.on('test', function (data) { + t.ok((data.title === 'FOO') || /^repeated test:/.test(data.title)); + }); + + return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) + .then(function () { + t.is(api.passCount, 2); + }); +}); + +test('babelConfig:{extends:path, plugins:[...]} merges plugins with .babelrc', function (t) { + t.plan(2); + + var api = new Api({ + babelConfig: { + plugins: [testCapitalizerPlugin], + extends: path.join(__dirname, 'fixture/babelrc/.alt-babelrc') + }, + cacheEnabled: false + }); + + api.on('test', function (data) { + t.ok((data.title === 'BAR')); + }); + return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) .then(function () { t.is(api.passCount, 1); diff --git a/test/fixture/babel-plugin-foo-to-bar.js b/test/fixture/babel-plugin-foo-to-bar.js new file mode 100644 index 000000000..26d515633 --- /dev/null +++ b/test/fixture/babel-plugin-foo-to-bar.js @@ -0,0 +1,19 @@ +module.exports = function (babel) { + var t = babel.types; + + return { + visitor: { + CallExpression: function (path) { + // skip require calls + var firstArg = path.get('arguments')[0]; + if (!isRequire(path) && firstArg && firstArg.isStringLiteral() && /foo/i.test(firstArg.node.value)) { + firstArg.replaceWith(t.stringLiteral(firstArg.node.value.replace('foo', 'bar').replace('FOO', 'BAR'))); + } + } + } + }; +}; + +function isRequire(path) { + return path.isCallExpression() && path.get('callee').isIdentifier() && (path.get('callee').node.name === 'require'); +} diff --git a/test/fixture/babel-plugin-test-capitalizer.js b/test/fixture/babel-plugin-test-capitalizer.js new file mode 100644 index 000000000..8d05b5f48 --- /dev/null +++ b/test/fixture/babel-plugin-test-capitalizer.js @@ -0,0 +1,19 @@ +module.exports = function (babel) { + var t = babel.types; + + return { + visitor: { + CallExpression: function (path) { + // skip require calls + var firstArg = path.get('arguments')[0]; + if (!isRequire(path) && firstArg && firstArg.isStringLiteral() && !/repeated test/.test(firstArg.node.value)) { + firstArg.replaceWith(t.stringLiteral(firstArg.node.value.toUpperCase())); + } + } + } + }; +}; + +function isRequire(path) { + return path.isCallExpression() && path.get('callee').isIdentifier() && (path.get('callee').node.name === 'require'); +} diff --git a/test/fixture/babelrc/.alt-babelrc b/test/fixture/babelrc/.alt-babelrc new file mode 100644 index 000000000..3b58de6e4 --- /dev/null +++ b/test/fixture/babelrc/.alt-babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "stage-2"], + "plugins": ["../babel-plugin-foo-to-bar"] +} diff --git a/test/fixture/babelrc/.babelrc b/test/fixture/babelrc/.babelrc index c7aec8ef4..02ef3bce8 100644 --- a/test/fixture/babelrc/.babelrc +++ b/test/fixture/babelrc/.babelrc @@ -1,3 +1,4 @@ { - "plugins": ["this-plugin-does-not-exist"] + "presets": ["es2015", "stage-2"], + "plugins": ["../babel-plugin-test-doubler"] } diff --git a/test/fixture/babelrc/test.js b/test/fixture/babelrc/test.js index a5315da17..c472597f0 100644 --- a/test/fixture/babelrc/test.js +++ b/test/fixture/babelrc/test.js @@ -1,3 +1,3 @@ import test from '../../../' -test(t => t.pass()); +test('foo', t => t.pass());