From 3533aba3f7960fc19070c726124a1e1993a1f790 Mon Sep 17 00:00:00 2001 From: Jaden Giordano Date: Wed, 30 May 2018 06:34:19 -0700 Subject: [PATCH] Make test & helper file extensions configurable Allow configuration of test & helper file extensions. Files will be recognized when searching for tests. Users can choose whether the files should participate in the Babel precompilation. --- api.js | 61 ++++++++++----- docs/recipes/babel.md | 19 +++++ docs/recipes/es-modules.md | 41 +++++++++- lib/ava-files.js | 45 ++++++----- lib/babel-pipeline.js | 17 +++- lib/cli.js | 9 +++ lib/extensions.js | 40 ++++++++++ package.json | 2 + readme.md | 5 +- test/api.js | 33 ++++++++ test/ava-files.js | 78 +++++++++++++++++++ test/cli.js | 61 ++++++++++++++- .../custom-extension/test/do-not-compile.js | 0 .../ava-files/custom-extension/test/foo.jsx | 0 .../custom-extension/test/helpers/a.jsx | 0 .../custom-extension/test/helpers/b.js | 0 .../custom-extension/test/sub/_helper.jsx | 0 .../custom-extension/test/sub/bar.jsx | 0 test/fixture/extensions/package.json | 4 + test/fixture/extensions/test.foo.bar | 11 +++ .../array-test-options/package.json | 7 ++ .../invalid-babel-config/bad-key/package.json | 7 ++ .../{ => bad-shortcut}/package.json | 0 .../package.json | 7 ++ .../false-test-options/package.json | 7 ++ .../non-string-value-extensions/package.json | 7 ++ .../null-extensions/package.json | 7 ++ .../null-test-options/package.json | 7 ++ .../obj-extensions/package.json | 7 ++ .../string-extensions/package.json | 7 ++ .../babel-duplicates/package.json | 7 ++ .../shared-duplicates/package.json | 8 ++ .../top-level-duplicates/package.json | 6 ++ .../invalid-extensions/top-level/package.json | 5 ++ .../custom-extension/package.json | 5 ++ .../custom-extension/power-assert.foo | 8 ++ test/helper/report.js | 5 ++ 37 files changed, 485 insertions(+), 48 deletions(-) create mode 100644 lib/extensions.js create mode 100644 test/fixture/ava-files/custom-extension/test/do-not-compile.js create mode 100644 test/fixture/ava-files/custom-extension/test/foo.jsx create mode 100644 test/fixture/ava-files/custom-extension/test/helpers/a.jsx create mode 100644 test/fixture/ava-files/custom-extension/test/helpers/b.js create mode 100644 test/fixture/ava-files/custom-extension/test/sub/_helper.jsx create mode 100644 test/fixture/ava-files/custom-extension/test/sub/bar.jsx create mode 100644 test/fixture/extensions/package.json create mode 100644 test/fixture/extensions/test.foo.bar create mode 100644 test/fixture/invalid-babel-config/array-test-options/package.json create mode 100644 test/fixture/invalid-babel-config/bad-key/package.json rename test/fixture/invalid-babel-config/{ => bad-shortcut}/package.json (100%) create mode 100644 test/fixture/invalid-babel-config/empty-string-value-extensions/package.json create mode 100644 test/fixture/invalid-babel-config/false-test-options/package.json create mode 100644 test/fixture/invalid-babel-config/non-string-value-extensions/package.json create mode 100644 test/fixture/invalid-babel-config/null-extensions/package.json create mode 100644 test/fixture/invalid-babel-config/null-test-options/package.json create mode 100644 test/fixture/invalid-babel-config/obj-extensions/package.json create mode 100644 test/fixture/invalid-babel-config/string-extensions/package.json create mode 100644 test/fixture/invalid-extensions/babel-duplicates/package.json create mode 100644 test/fixture/invalid-extensions/shared-duplicates/package.json create mode 100644 test/fixture/invalid-extensions/top-level-duplicates/package.json create mode 100644 test/fixture/invalid-extensions/top-level/package.json create mode 100644 test/fixture/just-enhancement-compilation/custom-extension/package.json create mode 100644 test/fixture/just-enhancement-compilation/custom-extension/power-assert.foo diff --git a/api.js b/api.js index e1d67eb75..6cb688777 100644 --- a/api.js +++ b/api.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const commonPathPrefix = require('common-path-prefix'); +const escapeStringRegexp = require('escape-string-regexp'); const uniqueTempDir = require('unique-temp-dir'); const isCi = require('is-ci'); const resolveCwd = require('resolve-cwd'); @@ -38,6 +39,8 @@ class Api extends Emittery { this.options = Object.assign({match: []}, options); this.options.require = resolveModules(this.options.require); + this._allExtensions = this.options.extensions.all; + this._regexpFullExtensions = new RegExp(`\\.(${this.options.extensions.full.map(ext => escapeStringRegexp(ext)).join('|')})$`); this._precompiler = null; } @@ -78,7 +81,7 @@ class Api extends Emittery { } // Find all test files. - return new AvaFiles({cwd: apiOptions.resolveTestsFrom, files}).findTestFiles() + return new AvaFiles({cwd: apiOptions.resolveTestsFrom, files, extensions: this._allExtensions}).findTestFiles() .then(files => { runStatus = new RunStatus(files.length); @@ -122,7 +125,7 @@ class Api extends Emittery { return emittedRun .then(() => this._setupPrecompiler()) .then(precompilation => { - if (!precompilation.precompileFile) { + if (!precompilation.enabled) { return null; } @@ -130,23 +133,27 @@ class Api extends Emittery { // helpers from within the `resolveTestsFrom` directory. Without // arguments this is the `projectDir`, else it's `process.cwd()` // which may be nested too deeply. - return new AvaFiles({cwd: this.options.resolveTestsFrom}).findTestHelpers().then(helpers => { - return { - cacheDir: precompilation.cacheDir, - map: [...files, ...helpers].reduce((acc, file) => { - try { - const realpath = fs.realpathSync(file); - const cachePath = precompilation.precompileFile(realpath); - if (cachePath) { - acc[realpath] = cachePath; + return new AvaFiles({cwd: this.options.resolveTestsFrom, extensions: this._allExtensions}) + .findTestHelpers().then(helpers => { + return { + cacheDir: precompilation.cacheDir, + map: [...files, ...helpers].reduce((acc, file) => { + try { + const realpath = fs.realpathSync(file); + const filename = path.basename(realpath); + const cachePath = this._regexpFullExtensions.test(filename) ? + precompilation.precompileFull(realpath) : + precompilation.precompileEnhancementsOnly(realpath); + if (cachePath) { + acc[realpath] = cachePath; + } + } catch (err) { + throw Object.assign(err, {file}); } - } catch (err) { - throw Object.assign(err, {file}); - } - return acc; - }, {}) - }; - }); + return acc; + }, {}) + }; + }); }) .then(precompilation => { // Resolve the correct concurrency value. @@ -218,10 +225,24 @@ class Api extends Emittery { // Ensure cacheDir exists makeDir.sync(cacheDir); - const {projectDir, babelConfig, compileEnhancements} = this.options; + const {projectDir, babelConfig} = this.options; + const compileEnhancements = this.options.compileEnhancements !== false; + const precompileFull = babelConfig ? + babelPipeline.build(projectDir, cacheDir, babelConfig, compileEnhancements) : + filename => { + throw new Error(`Cannot apply full precompilation, possible bad usage: ${filename}`); + }; + const precompileEnhancementsOnly = compileEnhancements && this.options.extensions.enhancementsOnly.length > 0 ? + babelPipeline.build(projectDir, cacheDir, null, compileEnhancements) : + filename => { + throw new Error(`Cannot apply enhancement-only precompilation, possible bad usage: ${filename}`); + }; + this._precompiler = { cacheDir, - precompileFile: babelPipeline.build(projectDir, cacheDir, babelConfig, compileEnhancements !== false) + enabled: babelConfig || compileEnhancements, + precompileEnhancementsOnly, + precompileFull }; return this._precompiler; } diff --git a/docs/recipes/babel.md b/docs/recipes/babel.md index 2c6eb77fb..227e74150 100644 --- a/docs/recipes/babel.md +++ b/docs/recipes/babel.md @@ -37,6 +37,25 @@ Instead run the following to reset AVA's cache when you change the configuration $ npx ava --reset-cache ``` +## Add additional extensions + +You can configure AVA to recognize additional file extensions and compile those test & helper files using Babel: + +```json +{ + "ava": { + "babel": { + "extensions": [ + "js", + "jsx" + ] + } + } +} +``` + +See also AVA's [`extensions` option](../../readme.md#options). + ## Make AVA skip your project's Babel options You may not want AVA to use your project's Babel options, for example if your project is relying on Babel 6. You can set the `babelrc` option to `false`: diff --git a/docs/recipes/es-modules.md b/docs/recipes/es-modules.md index 43efccf55..9b513dc13 100644 --- a/docs/recipes/es-modules.md +++ b/docs/recipes/es-modules.md @@ -45,4 +45,43 @@ test('2 + 2 = 4', t => { }); ``` -Note that test files still need to use the `.js` extension. +You need to configure AVA to recognize `.mjs` extensions. If you want AVA to apply its Babel presets use: + +```json +{ + "ava": { + "babel": { + "extensions": [ + "js", + "mjs" + ] + } + } +} +``` + +Alternatively you can use: + +```json +{ + "ava": { + "babel": false, + "extensions": [ + "js", + "mjs" + ] + } +} +``` + +Or leave Babel enabled (which means it's applied to `.js` files), but don't apply it to `.mjs` files: + +```json +{ + "ava": { + "extensions": [ + "mjs" + ] + } +} +``` diff --git a/lib/ava-files.js b/lib/ava-files.js index 97c7c20c7..fa422c775 100644 --- a/lib/ava-files.js +++ b/lib/ava-files.js @@ -9,7 +9,7 @@ const autoBind = require('auto-bind'); const defaultIgnore = require('ignore-by-default').directories(); const multimatch = require('multimatch'); -function handlePaths(files, excludePatterns, globOptions) { +function handlePaths(files, extensions, excludePatterns, globOptions) { // Convert Promise to Bluebird files = Promise.resolve(globby(files.concat(excludePatterns), globOptions)); @@ -42,14 +42,15 @@ function handlePaths(files, excludePatterns, globOptions) { searchedParents.add(file); - let pattern = path.join(file, '**', '*.js'); + let pattern = path.join(file, '**', `*.${extensions.length === 1 ? + extensions[0] : `{${extensions.join(',')}}`}`); if (process.platform === 'win32') { // Always use `/` in patterns, harmonizing matching across platforms pattern = slash(pattern); } - return handlePaths([pattern], excludePatterns, globOptions); + return handlePaths([pattern], extensions, excludePatterns, globOptions); } // `globby` returns slashes even on Windows. Normalize here so the file @@ -57,7 +58,7 @@ function handlePaths(files, excludePatterns, globOptions) { return path.normalize(file); }) .then(flatten) - .filter(file => file && path.extname(file) === '.js') + .filter(file => file && extensions.includes(path.extname(file).substr(1))) .filter(file => { if (path.basename(file)[0] === '_' && globOptions.includeUnderscoredFiles !== true) { return false; @@ -79,19 +80,19 @@ const defaultExcludePatterns = () => [ '!**/helpers/**' ]; -const defaultIncludePatterns = () => [ - 'test.js', - 'test-*.js', - 'test', - '**/__tests__', - '**/*.test.js' +const defaultIncludePatterns = extPattern => [ + `test.${extPattern}`, + `test-*.${extPattern}`, + 'test', // Directory + '**/__tests__', // Directory + `**/*.test.${extPattern}` ]; -const defaultHelperPatterns = () => [ - '**/__tests__/helpers/**/*.js', - '**/__tests__/**/_*.js', - '**/test/helpers/**/*.js', - '**/test/**/_*.js' +const defaultHelperPatterns = extPattern => [ + `**/__tests__/helpers/**/*.${extPattern}`, + `**/__tests__/**/_*.${extPattern}`, + `**/test/helpers/**/*.${extPattern}`, + `**/test/**/_*.${extPattern}` ]; const getDefaultIgnorePatterns = () => defaultIgnore.map(dir => `${dir}/**/*`); @@ -114,11 +115,13 @@ class AvaFiles { return file; }); + this.extensions = options.extensions || ['js']; + this.extensionPattern = this.extensions.length === 1 ? + this.extensions[0] : `{${this.extensions.join(',')}}`; + this.excludePatterns = defaultExcludePatterns(); if (files.length === 0) { - files = defaultIncludePatterns(); + files = defaultIncludePatterns(this.extensionPattern); } - - this.excludePatterns = defaultExcludePatterns(); this.files = files; this.sources = options.sources || []; this.cwd = options.cwd || process.cwd(); @@ -133,7 +136,7 @@ class AvaFiles { } findTestFiles() { - return handlePaths(this.files, this.excludePatterns, Object.assign({ + return handlePaths(this.files, this.extensions, this.excludePatterns, Object.assign({ cwd: this.cwd, expandDirectories: false, nodir: false @@ -141,7 +144,7 @@ class AvaFiles { } findTestHelpers() { - return handlePaths(defaultHelperPatterns(), ['!**/node_modules/**'], Object.assign({ + return handlePaths(defaultHelperPatterns(this.extensionPattern), this.extensions, ['!**/node_modules/**'], Object.assign({ cwd: this.cwd, includeUnderscoredFiles: true, expandDirectories: false, @@ -151,7 +154,7 @@ class AvaFiles { isSource(filePath) { let mixedPatterns = []; - const defaultIgnorePatterns = getDefaultIgnorePatterns(); + const defaultIgnorePatterns = getDefaultIgnorePatterns(this.extensionPattern); const overrideDefaultIgnorePatterns = []; let hasPositivePattern = false; diff --git a/lib/babel-pipeline.js b/lib/babel-pipeline.js index c7e569f1a..653a55b3d 100644 --- a/lib/babel-pipeline.js +++ b/lib/babel-pipeline.js @@ -4,6 +4,7 @@ const path = require('path'); const writeFileAtomic = require('@ava/write-file-atomic'); const babel = require('@babel/core'); const convertSourceMap = require('convert-source-map'); +const isPlainObject = require('is-plain-object'); const md5Hex = require('md5-hex'); const packageHash = require('package-hash'); const stripBomBuf = require('strip-bom-buf'); @@ -20,6 +21,14 @@ function getSourceMap(filePath, code) { return sourceMap ? sourceMap.toObject() : undefined; } +function hasValidKeys(conf) { + return Object.keys(conf).every(key => key === 'extensions' || key === 'testOptions'); +} + +function isValidExtensions(extensions) { + return Array.isArray(extensions) && extensions.every(ext => typeof ext === 'string' && ext !== ''); +} + function validate(conf) { if (conf === false) { return null; @@ -31,11 +40,17 @@ function validate(conf) { return {testOptions: defaultOptions}; } - if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions) || Object.keys(conf).length > 1) { + if ( + !isPlainObject(conf) || + !hasValidKeys(conf) || + (conf.testOptions !== undefined && !isPlainObject(conf.testOptions)) || + (conf.extensions !== undefined && !isValidExtensions(conf.extensions)) + ) { throw new Error(`Unexpected Babel configuration for AVA. See ${chalk.underline('https://github.com/avajs/ava/blob/master/docs/recipes/babel.md')} for allowed values.`); } return { + extensions: conf.extensions, testOptions: Object.assign({}, defaultOptions, conf.testOptions) }; } diff --git a/lib/cli.js b/lib/cli.js index 613f53dbf..6f8ffc1c0 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -149,6 +149,7 @@ exports.run = () => { // eslint-disable-line complexity const TapReporter = require('./reporters/tap'); const Watcher = require('./watcher'); const babelPipeline = require('./babel-pipeline'); + const normalizeExtensions = require('./extensions'); let babelConfig = null; try { @@ -157,6 +158,13 @@ exports.run = () => { // eslint-disable-line complexity exit(err.message); } + let extensions; + try { + extensions = normalizeExtensions(conf.extensions || [], babelConfig); + } catch (err) { + exit(err.message); + } + // Copy resultant cli.flags into conf for use with Api and elsewhere Object.assign(conf, cli.flags); @@ -168,6 +176,7 @@ exports.run = () => { // eslint-disable-line complexity require: arrify(conf.require), cacheEnabled: conf.cache !== false, compileEnhancements: conf.compileEnhancements !== false, + extensions, match, babelConfig, resolveTestsFrom: cli.input.length === 0 ? projectDir : process.cwd(), diff --git a/lib/extensions.js b/lib/extensions.js new file mode 100644 index 000000000..499015cb2 --- /dev/null +++ b/lib/extensions.js @@ -0,0 +1,40 @@ +module.exports = (enhancementsOnly, babelConfig) => { + const {extensions: full = []} = babelConfig || {}; + + // Combine all extensions possible for testing. Remove duplicate extensions. + const duplicates = []; + const seen = new Set(); + for (const ext of [...enhancementsOnly, ...full]) { + if (seen.has(ext)) { + duplicates.push(ext); + } else { + seen.add(ext); + } + } + + // Decide if and where to add the default `js` extension. Keep in mind it's not + // added if extensions have been explicitly given. + if (!seen.has('js')) { + if (babelConfig && full.length === 0) { + seen.add('js'); + full.push('js'); + } + if (!babelConfig && enhancementsOnly.length === 0) { + seen.add('js'); + enhancementsOnly.push('js'); + } + } else if (babelConfig && full.length === 0) { + // If Babel is not disabled, and has the default extensions (or, explicitly, + // no configured extensions), thes the `js` extension must have come from + // the `enhancementsOnly` value. That's not allowed since it'd be a + // roundabout way of disabling Babel. + throw new Error(`Cannot specify generic 'js' extension without disabling AVA's Babel usage.`); + } + + if (duplicates.length > 0) { + throw new Error(`Unexpected duplicate extensions in options: '${duplicates.join('\', \'')}'.`); + } + + const all = [...seen]; + return {all, enhancementsOnly, full}; +}; diff --git a/package.json b/package.json index d12317c48..987d44a4b 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "emittery": "^0.3.0", "empower-core": "^0.6.1", "equal-length": "^1.0.0", + "escape-string-regexp": "^1.0.5", "figures": "^2.0.0", "get-port": "^3.2.0", "globby": "^7.1.1", @@ -101,6 +102,7 @@ "is-error": "^2.2.1", "is-generator-fn": "^1.0.0", "is-observable": "^1.1.0", + "is-plain-object": "^2.0.4", "is-promise": "^2.1.0", "lodash.clone": "^4.5.0", "lodash.clonedeep": "^4.5.0", diff --git a/readme.md b/readme.md index 8927824ec..f9834d402 100644 --- a/readme.md +++ b/readme.md @@ -262,6 +262,7 @@ To ignore a file or directory, prefix the pattern with an `!` (exclamation mark) "@babel/register" ], "babel": { + "extensions": ["jsx"], "testOptions": { "babelrc": false } @@ -274,7 +275,7 @@ Arguments passed to the CLI will always take precedence over the configuration i ### Options -- `files`: file & directory paths and glob patterns that select which files AVA will run tests from. Only files with a `.js` extension are used. Files with an underscore prefix are ignored. All `.js` files in selected directories are run +- `files`: file & directory paths and glob patterns that select which files AVA will run tests from. Files with an underscore prefix are ignored. All matched files in selected directories are run. By default only selects files with `js` extensions, even if the glob pattern matches other files. Specify `extensions` and `babel.extensions` to allow other file extensions - `source`: files that, when changed, cause tests to be re-run in watch mode. See the [watch mode recipe for details](https://github.com/avajs/ava/blob/master/docs/recipes/watch-mode.md#source-files-and-test-files) - `match`: not typically useful in the `package.json` configuration, but equivalent to [specifying `--match` on the CLI](#running-tests-with-matching-titles) - `cache`: cache compiled test and helper files under `node_modules/.cache/ava`. If `false`, files are cached in a temporary directory instead @@ -283,8 +284,10 @@ Arguments passed to the CLI will always take precedence over the configuration i - `tap`: if `true`, enables the [TAP reporter](#tap-reporter) - `snapshotDir`: specifies a fixed location for storing snapshot files. Use this if your snapshots are ending up in the wrong location - `compileEnhancements`: if `false`, disables [power-assert](https://github.com/power-assert-js/power-assert) — which otherwise helps provide more descriptive error messages — and detection of improper use of the `t.throws()` assertion +- `extensions`: extensions of test files that are not precompiled using AVA's Babel presets. Note that files are still compiled to enable power-assert and other features, so you may also need to set `compileEnhancements` to `false` if your files are not valid JavaScript. Setting this overrides the default `"js"` value, so make sure to include that extension in the list, as long as it's not included in `babel.extensions` - `require`: extra modules to require before tests are run. Modules are required in the [worker processes](#process-isolation) - `babel`: test file specific Babel options. See our [Babel recipe] for more details +- `babel.extensions`: extensions of test files that will be precompiled using AVA's Babel presets. Setting this overrides the default `"js"` value, so make sure to include that extension in the list Note that providing files on the CLI overrides the `files` option. If you've configured a glob pattern, for instance `test/**/*.test.js`, you may want to repeat it when using the CLI: `ava 'test/integration/*.test.js'`. diff --git a/test/api.js b/test/api.js index ff6ad1cd8..06b1781a5 100644 --- a/test/api.js +++ b/test/api.js @@ -30,6 +30,7 @@ function apiCreator(options) { options = options || {}; options.babelConfig = babelPipeline.validate(options.babelConfig); options.concurrency = 2; + options.extensions = options.extensions || {all: ['js'], enhancementsOnly: [], full: ['js']}; options.projectDir = options.projectDir || ROOT_DIR; options.resolveTestsFrom = options.resolveTestsFrom || options.projectDir; const instance = new Api(options); @@ -1013,6 +1014,38 @@ test('uses "development" Babel environment if NODE_ENV is the empty string', t = }); }); +test('full extensions take precedence over enhancements-only', t => { + t.plan(2); + + const api = apiCreator({ + babelConfig: { + testOptions: { + plugins: [testCapitalizerPlugin] + } + }, + extensions: { + all: ['foo.bar', 'bar'], + enhancementsOnly: ['bar'], + full: ['foo.bar'] + }, + cacheEnabled: false, + projectDir: path.join(__dirname, 'fixture/extensions') + }); + + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.ok(evt.title === 'FOO'); + } + }); + }); + + return api.run() + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); + }); +}); + test('using --match with matching tests will only report those passing tests', t => { t.plan(3); diff --git a/test/ava-files.js b/test/ava-files.js index ffdcc9371..4c14aa8d7 100644 --- a/test/ava-files.js +++ b/test/ava-files.js @@ -152,6 +152,45 @@ test('findFiles - finds the correct files by default', t => { }); }); +test('findFiles - finds the files with configured extensions (single)', t => { + const fixtureDir = fixture('custom-extension'); + process.chdir(fixtureDir); + + const expected = [ + 'test/foo.jsx', + 'test/sub/bar.jsx' + ].sort().map(file => path.join(fixtureDir, file)); + + const avaFiles = new AvaFiles({ + extensions: ['jsx'] + }); + + avaFiles.findTestFiles().then(files => { + t.deepEqual(files.sort(), expected); + t.end(); + }); +}); + +test('findFiles - finds the files with configured extensions (multiple)', t => { + const fixtureDir = fixture('custom-extension'); + process.chdir(fixtureDir); + + const expected = [ + 'test/do-not-compile.js', + 'test/foo.jsx', + 'test/sub/bar.jsx' + ].sort().map(file => path.join(fixtureDir, file)); + + const avaFiles = new AvaFiles({ + extensions: ['jsx', 'js'] + }); + + avaFiles.findTestFiles().then(files => { + t.deepEqual(files.sort(), expected); + t.end(); + }); +}); + test('findTestHelpers - finds the test helpers', t => { const fixtureDir = fixture('default-patterns'); process.chdir(fixtureDir); @@ -170,3 +209,42 @@ test('findTestHelpers - finds the test helpers', t => { t.end(); }); }); + +test('findFiles - finds the test helpers with configured extensions (single)', t => { + const fixtureDir = fixture('custom-extension'); + process.chdir(fixtureDir); + + const expected = [ + 'test/sub/_helper.jsx', + 'test/helpers/a.jsx' + ].sort().map(file => path.join(fixtureDir, file)); + + const avaFiles = new AvaFiles({ + extensions: ['jsx'] + }); + + avaFiles.findTestHelpers().then(files => { + t.deepEqual(files.sort(), expected); + t.end(); + }); +}); + +test('findFiles - finds the test helpers with configured extensions (multiple)', t => { + const fixtureDir = fixture('custom-extension'); + process.chdir(fixtureDir); + + const expected = [ + 'test/sub/_helper.jsx', + 'test/helpers/a.jsx', + 'test/helpers/b.js' + ].sort().map(file => path.join(fixtureDir, file)); + + const avaFiles = new AvaFiles({ + extensions: ['jsx', 'js'] + }); + + avaFiles.findTestHelpers().then(files => { + t.deepEqual(files.sort(), expected); + t.end(); + }); +}); diff --git a/test/cli.js b/test/cli.js index 9efc8ef56..74f5453f9 100644 --- a/test/cli.js +++ b/test/cli.js @@ -62,13 +62,39 @@ function execCli(args, opts, cb) { return child; } -test('disallow invalid babel config shortcuts', t => { - execCli(['es2015.js'], {dirname: 'fixture/invalid-babel-config'}, (err, stdout, stderr) => { +for (const which of [ + 'bad-key', + 'bad-shortcut', + 'array-test-options', + 'false-test-options', + 'null-test-options', + 'null-extensions', + 'obj-extensions', + 'string-extensions', + 'non-string-value-extensions', + 'empty-string-value-extensions' +]) { + test(`validates babel config: ${which}`, t => { + execCli(['es2015.js'], {dirname: `fixture/invalid-babel-config/${which}`}, (err, stdout, stderr) => { + t.ok(err); + + let expectedOutput = '\n'; + expectedOutput += figures.cross + ' Unexpected Babel configuration for AVA.'; + expectedOutput += ' See https://github.com/avajs/ava/blob/master/docs/recipes/babel.md for allowed values.'; + expectedOutput += '\n'; + + t.is(stderr, expectedOutput); + t.end(); + }); + }); +} + +test('errors if top-level extensions include "js" without babel=false', t => { + execCli(['es2015.js'], {dirname: `fixture/invalid-extensions/top-level`}, (err, stdout, stderr) => { t.ok(err); let expectedOutput = '\n'; - expectedOutput += figures.cross + ' Unexpected Babel configuration for AVA.'; - expectedOutput += ' See https://github.com/avajs/ava/blob/master/docs/recipes/babel.md for allowed values.'; + expectedOutput += figures.cross + ' Cannot specify generic \'js\' extension without disabling AVA\'s Babel usage.'; expectedOutput += '\n'; t.is(stderr, expectedOutput); @@ -76,6 +102,25 @@ test('disallow invalid babel config shortcuts', t => { }); }); +for (const [where, which, msg = '\'js\', \'jsx\''] of [ + ['top-level', 'top-level-duplicates'], + ['babel', 'babel-duplicates'], + ['top-level and babel', 'shared-duplicates', '\'jsx\''] +]) { + test(`errors if ${where} extensions include duplicates`, t => { + execCli(['es2015.js'], {dirname: `fixture/invalid-extensions/${which}`}, (err, stdout, stderr) => { + t.ok(err); + + let expectedOutput = '\n'; + expectedOutput += figures.cross + ` Unexpected duplicate extensions in options: ${msg}.`; + expectedOutput += '\n'; + + t.is(stderr, expectedOutput); + t.end(); + }); + }); +} + test('enabling long stack traces will provide detailed debug information', t => { execCli('fixture/long-stack-trace', (err, stdout, stderr) => { t.ok(err); @@ -823,6 +868,14 @@ test('power-assert when babel=false and compileEnhancements=true', t => { }); }); +test('power-assert with custom extension and no regular babel pipeline', t => { + execCli(['.'], {dirname: 'fixture/just-enhancement-compilation/custom-extension'}, (err, stdout) => { + t.ok(err); + t.match(stripAnsi(stdout), /bool\n.*=> false/); + t.end(); + }); +}); + test('workers load compiled helpers if in the require configuration', t => { execCli(['test/verify.js'], {dirname: 'fixture/require-compiled-helper'}, err => { t.ifError(err); diff --git a/test/fixture/ava-files/custom-extension/test/do-not-compile.js b/test/fixture/ava-files/custom-extension/test/do-not-compile.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/ava-files/custom-extension/test/foo.jsx b/test/fixture/ava-files/custom-extension/test/foo.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/ava-files/custom-extension/test/helpers/a.jsx b/test/fixture/ava-files/custom-extension/test/helpers/a.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/ava-files/custom-extension/test/helpers/b.js b/test/fixture/ava-files/custom-extension/test/helpers/b.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/ava-files/custom-extension/test/sub/_helper.jsx b/test/fixture/ava-files/custom-extension/test/sub/_helper.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/ava-files/custom-extension/test/sub/bar.jsx b/test/fixture/ava-files/custom-extension/test/sub/bar.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixture/extensions/package.json b/test/fixture/extensions/package.json new file mode 100644 index 000000000..daa8ae391 --- /dev/null +++ b/test/fixture/extensions/package.json @@ -0,0 +1,4 @@ +{ + "name": "application-name", + "version": "0.0.1" +} diff --git a/test/fixture/extensions/test.foo.bar b/test/fixture/extensions/test.foo.bar new file mode 100644 index 000000000..625909b6e --- /dev/null +++ b/test/fixture/extensions/test.foo.bar @@ -0,0 +1,11 @@ +import test from '../../..'; + +const one = {one: 1}; +const two = {two: 2}; + +test('foo', t => { + // Using object rest/spread to ensure it transpiles on Node.js 6, since this + // is a Node.js 8 feature + const actual = {...one, ...two}; + t.deepEqual(actual, {one: 1, two: 2}); +}); diff --git a/test/fixture/invalid-babel-config/array-test-options/package.json b/test/fixture/invalid-babel-config/array-test-options/package.json new file mode 100644 index 000000000..0f1edbcdc --- /dev/null +++ b/test/fixture/invalid-babel-config/array-test-options/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "testOptions": [] + } + } +} diff --git a/test/fixture/invalid-babel-config/bad-key/package.json b/test/fixture/invalid-babel-config/bad-key/package.json new file mode 100644 index 000000000..52a08b21e --- /dev/null +++ b/test/fixture/invalid-babel-config/bad-key/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "mafia": {} + } + } +} diff --git a/test/fixture/invalid-babel-config/package.json b/test/fixture/invalid-babel-config/bad-shortcut/package.json similarity index 100% rename from test/fixture/invalid-babel-config/package.json rename to test/fixture/invalid-babel-config/bad-shortcut/package.json diff --git a/test/fixture/invalid-babel-config/empty-string-value-extensions/package.json b/test/fixture/invalid-babel-config/empty-string-value-extensions/package.json new file mode 100644 index 000000000..1a9d5989a --- /dev/null +++ b/test/fixture/invalid-babel-config/empty-string-value-extensions/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": [""] + } + } +} diff --git a/test/fixture/invalid-babel-config/false-test-options/package.json b/test/fixture/invalid-babel-config/false-test-options/package.json new file mode 100644 index 000000000..5219ea457 --- /dev/null +++ b/test/fixture/invalid-babel-config/false-test-options/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "testOptions": false + } + } +} diff --git a/test/fixture/invalid-babel-config/non-string-value-extensions/package.json b/test/fixture/invalid-babel-config/non-string-value-extensions/package.json new file mode 100644 index 000000000..f891141a7 --- /dev/null +++ b/test/fixture/invalid-babel-config/non-string-value-extensions/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": [true] + } + } +} diff --git a/test/fixture/invalid-babel-config/null-extensions/package.json b/test/fixture/invalid-babel-config/null-extensions/package.json new file mode 100644 index 000000000..471156541 --- /dev/null +++ b/test/fixture/invalid-babel-config/null-extensions/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": null + } + } +} diff --git a/test/fixture/invalid-babel-config/null-test-options/package.json b/test/fixture/invalid-babel-config/null-test-options/package.json new file mode 100644 index 000000000..8ff244d38 --- /dev/null +++ b/test/fixture/invalid-babel-config/null-test-options/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "testOptions": null + } + } +} diff --git a/test/fixture/invalid-babel-config/obj-extensions/package.json b/test/fixture/invalid-babel-config/obj-extensions/package.json new file mode 100644 index 000000000..a7700935a --- /dev/null +++ b/test/fixture/invalid-babel-config/obj-extensions/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": {} + } + } +} diff --git a/test/fixture/invalid-babel-config/string-extensions/package.json b/test/fixture/invalid-babel-config/string-extensions/package.json new file mode 100644 index 000000000..eae2543c7 --- /dev/null +++ b/test/fixture/invalid-babel-config/string-extensions/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": "js" + } + } +} diff --git a/test/fixture/invalid-extensions/babel-duplicates/package.json b/test/fixture/invalid-extensions/babel-duplicates/package.json new file mode 100644 index 000000000..733431391 --- /dev/null +++ b/test/fixture/invalid-extensions/babel-duplicates/package.json @@ -0,0 +1,7 @@ +{ + "ava": { + "babel": { + "extensions": ["js", "js", "jsx", "jsx"] + } + } +} diff --git a/test/fixture/invalid-extensions/shared-duplicates/package.json b/test/fixture/invalid-extensions/shared-duplicates/package.json new file mode 100644 index 000000000..8c50a1e05 --- /dev/null +++ b/test/fixture/invalid-extensions/shared-duplicates/package.json @@ -0,0 +1,8 @@ +{ + "ava": { + "babel": { + "extensions": ["js", "jsx"] + }, + "extensions": ["jsx"] + } +} diff --git a/test/fixture/invalid-extensions/top-level-duplicates/package.json b/test/fixture/invalid-extensions/top-level-duplicates/package.json new file mode 100644 index 000000000..f198b78a1 --- /dev/null +++ b/test/fixture/invalid-extensions/top-level-duplicates/package.json @@ -0,0 +1,6 @@ +{ + "ava": { + "babel": false, + "extensions": ["js", "js", "jsx", "jsx"] + } +} diff --git a/test/fixture/invalid-extensions/top-level/package.json b/test/fixture/invalid-extensions/top-level/package.json new file mode 100644 index 000000000..9e75c7302 --- /dev/null +++ b/test/fixture/invalid-extensions/top-level/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "extensions": ["js"] + } +} diff --git a/test/fixture/just-enhancement-compilation/custom-extension/package.json b/test/fixture/just-enhancement-compilation/custom-extension/package.json new file mode 100644 index 000000000..c532cb01e --- /dev/null +++ b/test/fixture/just-enhancement-compilation/custom-extension/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "extensions": ["foo"] + } +} diff --git a/test/fixture/just-enhancement-compilation/custom-extension/power-assert.foo b/test/fixture/just-enhancement-compilation/custom-extension/power-assert.foo new file mode 100644 index 000000000..acbf11ade --- /dev/null +++ b/test/fixture/just-enhancement-compilation/custom-extension/power-assert.foo @@ -0,0 +1,8 @@ +'use strict'; + +const test = require('../../../..'); + +test('test', t => { + const bool = false; + t.true(bool); +}); diff --git a/test/helper/report.js b/test/helper/report.js index 8f6b2ecfd..250af3ba0 100644 --- a/test/helper/report.js +++ b/test/helper/report.js @@ -79,6 +79,11 @@ const run = (type, reporter) => { const projectDir = path.join(__dirname, '../fixture/report', type.toLowerCase()); const api = createApi({ + extensions: { + all: ['js'], + enhancementsOnly: [], + full: ['js'] + }, failFast: type === 'failFast' || type === 'failFast2', failWithoutAssertions: false, serial: type === 'failFast' || type === 'failFast2',