From 20e963b30c98b9ffbeb3e4b650eee16bce878000 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 22 Apr 2019 14:21:10 +0200 Subject: [PATCH 1/3] Sort options when instantiating API --- lib/cli.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index c7cfb6388..6d9b0c38c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -192,25 +192,25 @@ exports.run = async () => { // eslint-disable-line complexity const match = arrify(conf.match); const resolveTestsFrom = cli.input.length === 0 ? projectDir : process.cwd(); const api = new Api({ - failFast: conf.failFast, - failWithoutAssertions: conf.failWithoutAssertions !== false, - serial: conf.serial, - require: arrify(conf.require), + babelConfig, cacheEnabled: conf.cache !== false, + color: conf.color, compileEnhancements: conf.compileEnhancements !== false, + concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0, extensions, + failFast: conf.failFast, + failWithoutAssertions: conf.failWithoutAssertions !== false, match, - babelConfig, - resolveTestsFrom, + parallelRuns, projectDir, + ranFromCli: true, + require: arrify(conf.require), + resolveTestsFrom, + serial: conf.serial, + snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null, timeout: conf.timeout, - concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0, updateSnapshots: conf.updateSnapshots, - snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null, - color: conf.color, - workerArgv: cli.flags['--'], - parallelRuns, - ranFromCli: true + workerArgv: cli.flags['--'] }); let reporter; From d30d4377cffa7e0c63f6842969a0faeca2ad3e84 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 22 Apr 2019 14:23:25 +0200 Subject: [PATCH 2/3] Remove warning for legacy 'source' configuration --- lib/cli.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 6d9b0c38c..30bc6c364 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -153,10 +153,6 @@ exports.run = async () => { // eslint-disable-line complexity exit('The --concurrency or -c flag must be provided with a nonnegative integer.'); } - if ('source' in conf) { - exit('The \'source\' option has been renamed. Use \'sources\' instead.'); - } - const ciParallelVars = require('ci-parallel-vars'); const Api = require('./api'); const VerboseReporter = require('./reporters/verbose'); From 36473218fb6abfa6b009ed444a19304944108604 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 22 Apr 2019 18:04:28 +0200 Subject: [PATCH 3/3] Refactor glob handling * Require globs to match files, not directories * files and sources must be an array of one or more patterns, not a string * Only accept files via the CLI, not globs or directories * Upgrade globby * At this stage, only test files starting with _ are treated as helpers. Helper globs should be made configurable. * It's no longer possible to override an exclusion pattern. We may reintroduce this at a later stage --- docs/05-command-line.md | 17 +- docs/06-configuration.md | 8 +- docs/08-common-pitfalls.md | 4 - docs/recipes/watch-mode.md | 2 - lib/api.js | 32 +- lib/ava-files.js | 290 ----------- lib/cli.js | 21 +- lib/globs.js | 187 ++++++++ lib/watcher.js | 27 +- package-lock.json | 453 +++++++++++++----- package.json | 4 +- test/api.js | 48 +- test/ava-files.js | 252 ---------- .../ava-files/no-duplicates/lib/bar.js | 1 - .../ava-files/no-duplicates/lib/foo.js | 1 - .../custom-extension/test/do-not-compile.js | 0 .../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 .../cwd/dir-a/test/bar.js | 0 .../cwd/dir-a/test/foo.js | 0 .../cwd/dir-b/test/baz.js | 0 .../sub/directory/__tests__/_foo.js | 0 .../sub/directory/__tests__/fixtures/foo.js | 0 .../sub/directory/__tests__/foo.js | 0 .../sub/directory/__tests__/helpers/foo.js | 0 .../sub/directory/bar.test.js | 0 .../default-patterns/test-foo.js | 0 .../default-patterns/test.js | 0 .../default-patterns/test/_foo-help.js | 0 .../default-patterns/test/baz.js | 0 .../default-patterns/test/deep/deep.js | 0 .../test/fixtures/foo-fixt.js | 0 .../default-patterns/test/helpers/test.js | 0 test/fixture/globs/no-files/package.json | 1 + test/fixture/invalid-globs/files/package.json | 5 + .../invalid-globs/sources/package.json | 5 + test/fixture/parallel-runs/package.json | 5 + .../fixture/pkg-conf/resolve-dir/package.json | 2 +- .../snapshots/test-content/package.json | 2 +- .../snapshots/test-sourcemaps/package.json | 2 +- test/fixture/snapshots/tests-dir/package.json | 2 +- test/globs.js | 181 +++++++ test/helper/report.js | 20 +- test/integration/assorted.js | 2 +- test/integration/compilation.js | 2 +- test/integration/config.js | 11 - test/integration/globs.js | 30 ++ test/integration/parallel-runs.js | 2 +- test/integration/stack-traces.js | 2 +- test/integration/watcher.js | 2 +- test/watcher.js | 185 +------ 54 files changed, 882 insertions(+), 926 deletions(-) delete mode 100644 lib/ava-files.js create mode 100644 lib/globs.js delete mode 100644 test/ava-files.js delete mode 100644 test/fixture/ava-files/no-duplicates/lib/bar.js delete mode 100644 test/fixture/ava-files/no-duplicates/lib/foo.js rename test/fixture/{ava-files => globs}/custom-extension/test/do-not-compile.js (100%) rename test/fixture/{ava-files => globs}/custom-extension/test/foo.jsx (100%) rename test/fixture/{ava-files => globs}/custom-extension/test/helpers/a.jsx (100%) rename test/fixture/{ava-files => globs}/custom-extension/test/helpers/b.js (100%) rename test/fixture/{ava-files => globs}/custom-extension/test/sub/_helper.jsx (100%) rename test/fixture/{ava-files => globs}/custom-extension/test/sub/bar.jsx (100%) rename test/fixture/{ava-files => globs}/cwd/dir-a/test/bar.js (100%) rename test/fixture/{ava-files => globs}/cwd/dir-a/test/foo.js (100%) rename test/fixture/{ava-files => globs}/cwd/dir-b/test/baz.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/sub/directory/__tests__/_foo.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/sub/directory/__tests__/fixtures/foo.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/sub/directory/__tests__/foo.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/sub/directory/__tests__/helpers/foo.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/sub/directory/bar.test.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test-foo.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test/_foo-help.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test/baz.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test/deep/deep.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test/fixtures/foo-fixt.js (100%) rename test/fixture/{ava-files => globs}/default-patterns/test/helpers/test.js (100%) create mode 100644 test/fixture/globs/no-files/package.json create mode 100644 test/fixture/invalid-globs/files/package.json create mode 100644 test/fixture/invalid-globs/sources/package.json create mode 100644 test/fixture/parallel-runs/package.json create mode 100644 test/globs.js create mode 100644 test/integration/globs.js diff --git a/docs/05-command-line.md b/docs/05-command-line.md index 5d69b2ffd..bc0b191d4 100644 --- a/docs/05-command-line.md +++ b/docs/05-command-line.md @@ -6,7 +6,7 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/do $ npx ava --help Usage - ava [ ...] + ava [ ...] Options --watch, -w Re-run tests when tests and source files change @@ -28,13 +28,22 @@ $ npx ava --help ava test-*.js ava test - Default patterns when no arguments: - test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js + The above relies on your shell expanding the glob patterns. + Without arguments, AVA uses the following patterns: + **/test.js **/test-*.js **/*.test.js **/test/**/*.js **/__tests__/**/*.js ``` *Note that the CLI will use your local install of AVA when available, even when run globally.* -Directories are recursed, with all `*.js` files being treated as test files. Directories named `fixtures`, `helpers` and `node_modules` are *always* ignored. So are files starting with `_` which allows you to place helpers in the same directory as your test files. +AVA searches for test files using the following patterns: + +* `**/test.js` +* `**/test-*.js` +* `**/*.test.js` +* `**/test/**/*.js` +* `**/__tests__/**/*.js` + +Files inside `node_modules` are *always* ignored. So are files starting with `_`. These are treated as helpers. When using `npm test`, you can pass positional arguments directly `npm test test2.js`, but flags needs to be passed like `npm test -- --verbose`. diff --git a/docs/06-configuration.md b/docs/06-configuration.md index e86f1d140..7724271b3 100644 --- a/docs/06-configuration.md +++ b/docs/06-configuration.md @@ -13,12 +13,12 @@ To ignore a file or directory, prefix the pattern with an `!` (exclamation mark) "ava": { "files": [ "my-test-directory/**/*.js", - "!my-test-directory/exclude-this-directory/**/*.js", + "!my-test-directory/exclude-this-directory", "!**/exclude-this-file.js" ], "sources": [ "**/*.{js,jsx}", - "!dist/**/*" + "!dist" ], "match": [ "*oo", @@ -48,7 +48,7 @@ Arguments passed to the CLI will always take precedence over the CLI options con ## Options -- `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 +- `files`: glob patterns that select which files AVA will run tests from. Files with an underscore prefix are ignored. 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 - `sources`: 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](./05-command-line.md#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 @@ -64,7 +64,7 @@ Arguments passed to the CLI will always take precedence over the CLI options con - `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 - `timeout`: Timeouts in AVA behave differently than in other test frameworks. AVA resets a timer after each test, forcing tests to quit if no new test results were received within the specified timeout. This can be used to handle stalled tests. See our [timeout documentation](./07-test-timeouts.md) for more options. -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'`. +Note that providing files on the CLI overrides the `files` option. ## Using `ava.config.js` diff --git a/docs/08-common-pitfalls.md b/docs/08-common-pitfalls.md index a9f51c91c..068ef3237 100644 --- a/docs/08-common-pitfalls.md +++ b/docs/08-common-pitfalls.md @@ -83,10 +83,6 @@ test('one is one', t => { }); ``` -### Helpers are not compiled when using a non-default test folder - -This is a [known issue](https://github.com/avajs/ava/issues/1319). You should put your tests in a folder called `test` or `__tests__`. - --- Is your problem not listed here? Submit a pull request or comment on [this issue](https://github.com/avajs/ava/issues/404). diff --git a/docs/recipes/watch-mode.md b/docs/recipes/watch-mode.md index 2c97d0519..710eb271f 100644 --- a/docs/recipes/watch-mode.md +++ b/docs/recipes/watch-mode.md @@ -71,8 +71,6 @@ By default AVA watches for changes to the test files, snapshot files, `package.j You can configure patterns for the source files in the [`ava` section of your `package.json`, or `ava.config.js` file][config], using the `sources` key. -You can specify patterns to match files in the folders that would otherwise be ignored, e.g. use `node_modules/some-dependency/*.js` to specify all `.js` files in `node_modules/some-dependency` as a source, even though normally all files in `node_modules` are ignored. Note that you need to specify an exact directory; `{bower_components,node_modules}/**/*.js` won't work. - If your tests write to disk they may trigger the watcher to rerun your tests. Configure patterns for the source files to avoid this. ## Dependency tracking diff --git a/lib/api.js b/lib/api.js index 9ee9f1d32..446eb77ae 100644 --- a/lib/api.js +++ b/lib/api.js @@ -16,8 +16,8 @@ const ms = require('ms'); const chunkd = require('chunkd'); const Emittery = require('emittery'); const babelPipeline = require('./babel-pipeline'); +const globs = require('./globs'); const RunStatus = require('./run-status'); -const AvaFiles = require('./ava-files'); const fork = require('./fork'); const serializeError = require('./serialize-error'); @@ -50,7 +50,9 @@ class Api extends Emittery { } } - async run(files, runtimeOptions = {}) { + async run(files = [], runtimeOptions = {}) { + files = files.map(file => path.resolve(this.options.resolveTestsFrom, file)); + const apiOptions = this.options; // Each run will have its own status. It can only be created when test files @@ -105,8 +107,17 @@ class Api extends Emittery { }; try { - // Find all test files. - files = await new AvaFiles({cwd: apiOptions.resolveTestsFrom, files, extensions: this._allExtensions}).findTestFiles(); + const precompiler = await this._setupPrecompiler(); + let helpers = []; + if (files.length === 0 || precompiler.enabled) { + const found = await globs.findHelpersAndTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs}); + if (files.length === 0) { + ({tests: files} = found); + } + + ({helpers} = found); + } + if (this.options.parallelRuns) { const {currentIndex, totalRuns} = this.options.parallelRuns; const fileCount = files.length; @@ -157,22 +168,21 @@ class Api extends Emittery { } }); - let precompilation = await this._setupPrecompiler(); - if (precompilation.enabled) { + let precompilation = null; + if (precompiler.enabled) { // Compile all test and helper files. Assumes the tests only load // helpers from within the `resolveTestsFrom` directory. Without // arguments this is the `projectDir`, else it's `process.cwd()` // which may be nested too deeply. - const helpers = await new AvaFiles({cwd: this.options.resolveTestsFrom, extensions: this._allExtensions}).findTestHelpers(); precompilation = { - cacheDir: precompilation.cacheDir, + cacheDir: precompiler.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); + precompiler.precompileFull(realpath) : + precompiler.precompileEnhancementsOnly(realpath); if (cachePath) { acc[realpath] = cachePath; } @@ -183,8 +193,6 @@ class Api extends Emittery { return acc; }, {}) }; - } else { - precompilation = null; } // Resolve the correct concurrency value. diff --git a/lib/ava-files.js b/lib/ava-files.js deleted file mode 100644 index 7d16a26ab..000000000 --- a/lib/ava-files.js +++ /dev/null @@ -1,290 +0,0 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); -const Promise = require('bluebird'); -const slash = require('slash'); -const globby = require('globby'); -const flatten = require('lodash.flatten'); -const defaultIgnore = require('ignore-by-default').directories(); -const multimatch = require('multimatch'); - -function handlePaths(files, extensions, excludePatterns, globOptions) { - // Convert Promise to Bluebird - files = Promise.resolve(globby(files.concat(excludePatterns), globOptions)); - - const searchedParents = new Set(); - const foundFiles = new Set(); - - function alreadySearchingParent(dir) { - if (searchedParents.has(dir)) { - return true; - } - - const parentDir = path.dirname(dir); - - if (parentDir === dir) { - // We have reached the root path - return false; - } - - return alreadySearchingParent(parentDir); - } - - return files - .map(file => { - file = path.resolve(globOptions.cwd, file); - - if (fs.statSync(file).isDirectory()) { - if (alreadySearchingParent(file)) { - return null; - } - - searchedParents.add(file); - - 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], extensions, excludePatterns, globOptions); - } - - // `globby` returns slashes even on Windows. Normalize here so the file - // paths are consistently platform-accurate as tests are run. - return path.normalize(file); - }) - .then(flatten) // eslint-disable-line promise/prefer-await-to-then - .filter(file => file && extensions.includes(path.extname(file).substr(1))) - .filter(file => { - if (path.basename(file)[0] === '_' && globOptions.includeUnderscoredFiles !== true) { - return false; - } - - return true; - }) - .map(file => path.resolve(file)) - .filter(file => { - const alreadyFound = foundFiles.has(file); - foundFiles.add(file); - return !alreadyFound; - }); -} - -const defaultExcludePatterns = () => [ - '!**/node_modules/**', - '!**/fixtures/**', - '!**/helpers/**' -]; - -const defaultIncludePatterns = extPattern => [ - `test.${extPattern}`, - `test-*.${extPattern}`, - 'test', // Directory - '**/__tests__', // Directory - `**/*.test.${extPattern}` -]; - -const defaultHelperPatterns = extPattern => [ - `**/__tests__/helpers/**/*.${extPattern}`, - `**/__tests__/**/_*.${extPattern}`, - `**/test/helpers/**/*.${extPattern}`, - `**/test/**/_*.${extPattern}` -]; - -const getDefaultIgnorePatterns = () => defaultIgnore.map(dir => `${dir}/**/*`); - -// Used on paths before they're passed to multimatch to harmonize matching -// across platforms -const matchable = process.platform === 'win32' ? slash : (path => path); - -class AvaFiles { - constructor(options = {}) { - let files = (options.files || []).map(file => { - // `./` should be removed from the beginning of patterns because - // otherwise they won't match change events from Chokidar - if (file.slice(0, 2) === './') { - return file.slice(2); - } - - 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(this.extensionPattern); - } - - this.files = files; - this.sources = options.sources || []; - this.cwd = options.cwd || process.cwd(); - this.globCaches = { - cache: Object.create(null), - statCache: Object.create(null), - realpathCache: Object.create(null), - symlinks: Object.create(null) - }; - } - - findTestFiles() { - return handlePaths(this.files, this.extensions, this.excludePatterns, { - cwd: this.cwd, - expandDirectories: false, - nodir: false, - ...this.globCaches - }); - } - - findTestHelpers() { - return handlePaths(defaultHelperPatterns(this.extensionPattern), this.extensions, ['!**/node_modules/**'], { - cwd: this.cwd, - includeUnderscoredFiles: true, - expandDirectories: false, - nodir: false, - ...this.globCaches - }); - } - - isSource(filePath) { - let mixedPatterns = []; - const defaultIgnorePatterns = getDefaultIgnorePatterns(this.extensionPattern); - const overrideDefaultIgnorePatterns = []; - - let hasPositivePattern = false; - for (const pattern of this.sources) { - mixedPatterns.push(pattern); - - // TODO: Why not just `pattern[0] !== '!'`? - if (!hasPositivePattern && pattern[0] !== '!') { - hasPositivePattern = true; - } - - // Extract patterns that start with an ignored directory. These need to be - // rematched separately. - if (defaultIgnore.includes(pattern.split('/')[0])) { - overrideDefaultIgnorePatterns.push(pattern); - } - } - - // Same defaults as used for Chokidar - if (!hasPositivePattern) { - mixedPatterns = ['package.json', '**/*.js', '**/*.snap'].concat(mixedPatterns); - } - - filePath = matchable(filePath); - - // Ignore paths outside the current working directory. - // They can't be matched to a pattern. - if (filePath.startsWith('../')) { - return false; - } - - const isSource = multimatch(filePath, mixedPatterns).length === 1; - if (!isSource) { - return false; - } - - const isIgnored = multimatch(filePath, defaultIgnorePatterns).length === 1; - if (!isIgnored) { - return true; - } - - const isErroneouslyIgnored = multimatch(filePath, overrideDefaultIgnorePatterns).length === 1; - if (isErroneouslyIgnored) { - return true; - } - - return false; - } - - isTest(filePath) { - const {excludePatterns} = this; - const initialPatterns = this.files.concat(excludePatterns); - - // Like in `api.js`, tests must be `.js` files and not start with `_` - if (path.extname(filePath) !== '.js' || path.basename(filePath)[0] === '_') { - return false; - } - - // Check if the entire path matches a pattern - if (multimatch(matchable(filePath), initialPatterns).length === 1) { - return true; - } - - // Check if the path contains any directory components - const dirname = path.dirname(filePath); - if (dirname === '.') { - return false; - } - - // Compute all possible subpaths. Note that the dirname is assumed to be - // relative to the working directory, without a leading `./`. - const subpaths = dirname.split(/[\\/]/).reduce((subpaths, component) => { - const parent = subpaths[subpaths.length - 1]; - - if (parent) { - // Always use `/`` to makes multimatch consistent across platforms - subpaths.push(`${parent}/${component}`); - } else { - subpaths.push(component); - } - - return subpaths; - }, []); - - // Check if any of the possible subpaths match a pattern. If so, generate a - // new pattern with **/*.js. - const recursivePatterns = subpaths - .filter(subpath => multimatch(subpath, initialPatterns).length === 1) - // Always use `/` to makes multimatch consistent across platforms - .map(subpath => `${subpath}/**/*.js`); - - // See if the entire path matches any of the subpaths patterns, taking the - // excludePatterns into account. This mimicks the behavior in api.js - return multimatch(matchable(filePath), recursivePatterns.concat(excludePatterns)).length === 1; - } - - getChokidarPatterns() { - let paths = []; - let ignored = []; - - for (const pattern of this.sources) { - if (pattern[0] === '!') { - ignored.push(pattern.slice(1)); - } else { - paths.push(pattern); - } - } - - // Allow source patterns to override the default ignore patterns. Chokidar - // ignores paths that match the list of ignored patterns. It uses anymatch - // under the hood, which supports negation patterns. For any source pattern - // that starts with an ignored directory, ensure the corresponding negation - // pattern is added to the ignored paths. - const overrideDefaultIgnorePatterns = paths - .filter(pattern => defaultIgnore.includes(pattern.split('/')[0])) - .map(pattern => `!${pattern}`); - - ignored = getDefaultIgnorePatterns().concat(ignored, overrideDefaultIgnorePatterns); - - if (paths.length === 0) { - paths = ['package.json', '**/*.js', '**/*.snap']; - } - - paths = paths.concat(this.files); - - return { - paths, - ignored - }; - } -} - -module.exports = AvaFiles; -module.exports.defaultIncludePatterns = defaultIncludePatterns; -module.exports.defaultExcludePatterns = defaultExcludePatterns; diff --git a/lib/cli.js b/lib/cli.js index 30bc6c364..3a7bb558f 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -28,7 +28,7 @@ exports.run = async () => { // eslint-disable-line complexity const cli = meow(` Usage - ava [ ...] + ava [ ...] Options --watch, -w Re-run tests when tests and source files change @@ -50,8 +50,9 @@ exports.run = async () => { // eslint-disable-line complexity ava test-*.js ava test - Default patterns when no arguments: - test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js + The above relies on your shell expanding the glob patterns. + Without arguments, AVA uses the following patterns: + **/test.js **/test-*.js **/*.test.js **/test/**/*.js **/__tests__/**/*.js `, { flags: { watch: { @@ -161,6 +162,7 @@ exports.run = async () => { // eslint-disable-line complexity const Watcher = require('./watcher'); const babelPipeline = require('./babel-pipeline'); const normalizeExtensions = require('./extensions'); + const {normalizeGlobs} = require('./globs'); let babelConfig = null; try { @@ -176,6 +178,13 @@ exports.run = async () => { // eslint-disable-line complexity exit(error.message); } + let globs; + try { + globs = normalizeGlobs(conf.files, conf.sources, extensions.all); + } catch (error) { + exit(error.message); + } + // Copy resultant cli.flags into conf for use with Api and elsewhere Object.assign(conf, cli.flags); @@ -196,6 +205,7 @@ exports.run = async () => { // eslint-disable-line complexity extensions, failFast: conf.failFast, failWithoutAssertions: conf.failWithoutAssertions !== false, + globs, match, parallelRuns, projectDir, @@ -240,15 +250,14 @@ exports.run = async () => { // eslint-disable-line complexity }); }); - const files = cli.input.length > 0 ? cli.input : arrify(conf.files); + const files = cli.input.map(file => path.relative(resolveTestsFrom, path.resolve(process.cwd(), file))); if (conf.watch) { const watcher = new Watcher({ api, reporter, - extensions: extensions.all, files, - sources: arrify(conf.sources), + globs, resolveTestsFrom }); watcher.observeStdin(process.stdin); diff --git a/lib/globs.js b/lib/globs.js new file mode 100644 index 000000000..2a9cd6048 --- /dev/null +++ b/lib/globs.js @@ -0,0 +1,187 @@ +'use strict'; +const path = require('path'); +const globby = require('globby'); +const ignoreByDefault = require('ignore-by-default'); +const micromatch = require('micromatch'); +const slash = require('slash'); + +const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules']; + +const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`; + +const normalizePatterns = patterns => { + // Always use `/` in patterns, harmonizing matching across platforms + if (process.platform === 'win32') { + patterns = patterns.map(pattern => slash(pattern)); + } + + return patterns.map(pattern => { + if (pattern.startsWith('./')) { + return pattern.slice(2); + } + + if (pattern.startsWith('!./')) { + return `!${pattern.slice(3)}`; + } + + return pattern; + }); +}; + +function normalizeGlobs(testPatterns, sourcePatterns, extensions) { + if (typeof testPatterns !== 'undefined' && (!Array.isArray(testPatterns) || testPatterns.length === 0)) { + throw new Error('The \'files\' configuration must be an array containing glob patterns.'); + } + + if (sourcePatterns && (!Array.isArray(sourcePatterns) || sourcePatterns.length === 0)) { + throw new Error('The \'sources\' configuration must be an array containing glob patterns.'); + } + + const extensionPattern = buildExtensionPattern(extensions); + const defaultTestPatterns = [ + `**/__tests__/**/*.${extensionPattern}`, + `**/*.test.${extensionPattern}`, + `**/test-*.${extensionPattern}`, + `**/test.${extensionPattern}`, + `**/test/**/*.${extensionPattern}` + ]; + + if (testPatterns) { + testPatterns = normalizePatterns(testPatterns); + + if (testPatterns.every(pattern => pattern.startsWith('!'))) { + // Use defaults if patterns only contains exclusions. + testPatterns = [...defaultTestPatterns, ...testPatterns]; + } + } else { + testPatterns = defaultTestPatterns; + } + + const defaultSourcePatterns = [ + '**/*.snap', + 'ava.config.js', + 'package.json', + `**/*.${extensionPattern}` + ]; + if (sourcePatterns) { + sourcePatterns = normalizePatterns(sourcePatterns); + + if (sourcePatterns.every(pattern => pattern.startsWith('!'))) { + // Use defaults if patterns only contains exclusions. + sourcePatterns = [...defaultSourcePatterns, ...sourcePatterns]; + } + } else { + sourcePatterns = defaultSourcePatterns; + } + + return {extensions, testPatterns, sourcePatterns}; +} + +exports.normalizeGlobs = normalizeGlobs; + +const findFiles = async (cwd, patterns) => { + const files = await globby(patterns, { + absolute: true, + brace: true, + case: false, + cwd, + dot: false, + expandDirectories: false, + extglob: true, + followSymlinkedDirectories: true, + gitignore: false, + globstar: true, + ignore: defaultIgnorePatterns, + matchBase: false, + onlyFiles: true, + stats: false, + unique: true + }); + + // `globby` returns slashes even on Windows. Normalize here so the file + // paths are consistently platform-accurate as tests are run. + if (process.platform === 'win32') { + return files.map(file => path.normalize(file)); + } + + return files; +}; + +async function findHelpersAndTests({cwd, extensions, testPatterns}) { + const helpers = []; + const tests = []; + for (const file of await findFiles(cwd, testPatterns)) { + if (!extensions.includes(path.extname(file).slice(1))) { + continue; + } + + if (path.basename(file).startsWith('_')) { + helpers.push(file); + } else { + tests.push(file); + } + } + + return {helpers, tests}; +} + +exports.findHelpersAndTests = findHelpersAndTests; + +function getChokidarPatterns({sourcePatterns, testPatterns}) { + const paths = []; + const ignored = defaultIgnorePatterns.map(pattern => `${pattern}/**/*`); + + for (const pattern of [...sourcePatterns, ...testPatterns]) { + if (!pattern.startsWith('!')) { + paths.push(pattern); + } + } + + return {paths, ignored}; +} + +exports.getChokidarPatterns = getChokidarPatterns; + +const matchingCache = new WeakMap(); +const processMatchingPatterns = input => { + let result = matchingCache.get(input); + if (!result) { + const ignore = [ + ...defaultIgnorePatterns, + // Unlike globby(), micromatch needs a complete pattern when ignoring directories. + ...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`) + ]; + const patterns = input.filter(pattern => { + if (pattern.startsWith('!')) { + // Unlike globby(), micromatch needs a complete pattern when ignoring directories. + ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`); + return false; + } + + return true; + }); + + result = {patterns, ignore}; + matchingCache.set(input, result); + } + + return result; +}; + +const matches = (file, patterns) => { + let ignore; + ({patterns, ignore} = processMatchingPatterns(patterns)); + return micromatch.some(file, patterns, {ignore}); +}; + +function isSource(file, {testPatterns, sourcePatterns}) { + return !isTest(file, {testPatterns}) && matches(file, sourcePatterns); +} + +exports.isSource = isSource; + +function isTest(file, {testPatterns}) { + return !path.basename(file).startsWith('_') && matches(file, testPatterns); +} + +exports.isTest = isTest; diff --git a/lib/watcher.js b/lib/watcher.js index cf55036f2..4cf881c95 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -7,7 +7,7 @@ const flatten = require('arr-flatten'); const union = require('array-union'); const uniq = require('array-uniq'); const chalk = require('./chalk').get(); -const AvaFiles = require('./ava-files'); +const globs = require('./globs'); function rethrowAsync(err) { // Don't swallow exceptions. Note that any @@ -79,19 +79,14 @@ class TestDependency { } class Watcher { - constructor({reporter, api, extensions, files, sources, resolveTestsFrom}) { + constructor({reporter, api, files, globs, resolveTestsFrom}) { this.debouncer = new Debouncer(this); - this.avaFiles = new AvaFiles({ - cwd: resolveTestsFrom, - extensions, - files, - sources - }); this.clearLogOnNextRun = true; this.runVector = 0; - this.previousFiles = files; - this.run = (specificFiles, updateSnapshots) => { + this.previousFiles = []; + this.globs = {cwd: resolveTestsFrom, ...globs}; + this.run = (specificFiles = files, updateSnapshots) => { const clearLogOnNextRun = this.clearLogOnNextRun && this.runVector > 0; if (this.runVector > 0) { this.clearLogOnNextRun = true; @@ -100,7 +95,7 @@ class Watcher { this.runVector++; let runOnlyExclusive = false; - if (specificFiles) { + if (specificFiles.length > 0) { const exclusiveFiles = specificFiles.filter(file => this.filesWithExclusiveTests.includes(file)); runOnlyExclusive = exclusiveFiles.length !== this.filesWithExclusiveTests.length; if (runOnlyExclusive) { @@ -114,7 +109,7 @@ class Watcher { } this.touchedFiles.clear(); - this.previousFiles = specificFiles || files; + this.previousFiles = specificFiles; this.busy = api.run(this.previousFiles, { clearLogOnNextRun, previousFailures: this.sumPreviousFailures(this.runVector), @@ -142,7 +137,7 @@ class Watcher { }; this.testDependencies = []; - this.trackTestDependencies(api, sources); + this.trackTestDependencies(api); this.touchedFiles = new Set(); this.trackTouchedFiles(api); @@ -159,7 +154,7 @@ class Watcher { } watchFiles() { - const patterns = this.avaFiles.getChokidarPatterns(); + const patterns = globs.getChokidarPatterns(this.globs); chokidar.watch(patterns.paths, { ignored: patterns.ignored, @@ -182,7 +177,7 @@ class Watcher { return; } - const sourceDeps = evt.dependencies.map(x => relative(x)).filter(filePath => this.avaFiles.isSource(filePath)); + const sourceDeps = evt.dependencies.map(x => relative(x)).filter(filePath => globs.isSource(filePath, this.globs)); this.updateTestDependencies(evt.testFile, sourceDeps); }); }); @@ -365,7 +360,7 @@ class Watcher { return true; }); - const dirtyTests = dirtyPaths.filter(filePath => this.avaFiles.isTest(filePath)); + const dirtyTests = dirtyPaths.filter(filePath => globs.isTest(filePath, this.globs)); const dirtySources = diff(dirtyPaths, dirtyTests); const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink'); const unlinkedTests = diff(dirtyTests, addedOrChangedTests); diff --git a/package-lock.json b/package-lock.json index 7bbf03c39..0c5329724 100644 --- a/package-lock.json +++ b/package-lock.json @@ -552,7 +552,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, "requires": { "call-me-maybe": "^1.0.1", "glob-to-regexp": "^0.3.0" @@ -561,8 +560,7 @@ "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, "@sinonjs/commons": { "version": "1.4.0", @@ -600,6 +598,31 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/node": { + "version": "11.13.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.6.tgz", + "integrity": "sha512-Xoo/EBzEe8HxTSwaZNLZjaW6M6tA/+GmD3/DZ6uo8qSaolE/9Oarko0oV1fVfrLqOz0tx0nXJB4rdD5c+vixLw==" + }, "abbrev": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", @@ -674,6 +697,57 @@ "normalize-path": "^2.1.1" }, "dependencies": { + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -681,6 +755,28 @@ "requires": { "remove-trailing-separator": "^1.0.1" } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + } + } } } }, @@ -719,11 +815,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-differ": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-2.0.3.tgz", - "integrity": "sha1-AZW7AMzM8nEQbv7kpHhkiLcYBxI=" - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -834,9 +925,9 @@ } }, "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -899,11 +990,11 @@ } }, "brace-expansion": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -983,8 +1074,7 @@ "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, "call-signature": { "version": "0.0.2", @@ -1726,11 +1816,10 @@ "dev": true }, "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", "requires": { - "arrify": "^1.0.1", "path-type": "^3.0.0" }, "dependencies": { @@ -2604,7 +2693,6 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", - "dev": true, "requires": { "@mrmlnc/readdir-enhanced": "^2.2.1", "@nodelib/fs.stat": "^1.1.2", @@ -2612,6 +2700,81 @@ "is-glob": "^4.0.0", "merge2": "^1.2.3", "micromatch": "^3.1.10" + }, + "dependencies": { + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + } + } + } } }, "fast-json-stable-stringify": { @@ -3738,8 +3901,7 @@ "glob-to-regexp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" }, "global-dirs": { "version": "0.1.1", @@ -3779,27 +3941,37 @@ "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==" }, "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" } } }, @@ -4010,9 +4182,9 @@ } }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==" + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, "ignore-by-default": { "version": "1.0.1", @@ -5007,80 +5179,44 @@ "merge2": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", - "dev": true + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==" }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" }, "dependencies": { - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "extend-shallow": { + "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "fill-range": "^7.0.1" } }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { - "is-plain-object": "^2.0.4" + "to-regex-range": "^5.0.1" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - } + "is-number": "^7.0.0" } } } @@ -5189,17 +5325,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, - "multimatch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz", - "integrity": "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==", - "requires": { - "array-differ": "^2.0.3", - "array-union": "^1.0.2", - "arrify": "^1.0.1", - "minimatch": "^3.0.4" - } - }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -6813,6 +6938,11 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.5.tgz", + "integrity": "sha512-Zisqgaq/4P05ZclrU/g5XrzFqVo7YiJx+EP4haeVI9S7kvtZmZgmQMZfcvjEus9JcMhqZfQZObimT5ZydvKJGA==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -7201,6 +7331,81 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "dependencies": { + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + } + } + } } }, "redent": { @@ -9144,6 +9349,12 @@ "xo-init": "^0.7.0" }, "dependencies": { + "array-differ": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz", + "integrity": "sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==", + "dev": true + }, "dir-glob": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.1.tgz", @@ -9227,6 +9438,18 @@ "path-exists": "^3.0.0" } }, + "multimatch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz", + "integrity": "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==", + "dev": true, + "requires": { + "array-differ": "^2.0.3", + "array-union": "^1.0.2", + "arrify": "^1.0.1", + "minimatch": "^3.0.4" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", diff --git a/package.json b/package.json index f0e1eb218..5a0883104 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "figures": "^2.0.0", "find-up": "^3.0.0", "get-port": "^4.2.0", - "globby": "^7.1.1", + "globby": "^9.2.0", "ignore-by-default": "^1.0.0", "import-local": "^2.0.0", "indent-string": "^3.2.0", @@ -117,8 +117,8 @@ "matcher": "^1.1.1", "md5-hex": "^2.0.0", "meow": "^5.0.0", + "micromatch": "^4.0.2", "ms": "^2.1.1", - "multimatch": "^3.0.0", "observable-to-promise": "^0.5.0", "ora": "^3.2.0", "package-hash": "^3.0.0", diff --git a/test/api.js b/test/api.js index a2c368f05..d1c7023f0 100644 --- a/test/api.js +++ b/test/api.js @@ -8,6 +8,7 @@ const del = require('del'); const {test} = require('tap'); const Api = require('../lib/api'); const babelPipeline = require('../lib/babel-pipeline'); +const {normalizeGlobs} = require('../lib/globs'); const testCapitalizerPlugin = require.resolve('./fixture/babel-plugin-test-capitalizer'); @@ -31,6 +32,7 @@ function apiCreator(options = {}) { options.babelConfig = babelPipeline.validate(options.babelConfig); options.concurrency = 2; options.extensions = options.extensions || {all: ['js'], enhancementsOnly: [], full: ['js']}; + options.globs = normalizeGlobs(options.files, options.sources, options.extensions.all); options.projectDir = options.projectDir || ROOT_DIR; options.resolveTestsFrom = options.resolveTestsFrom || options.projectDir; const instance = new Api(options); @@ -550,9 +552,9 @@ test('absolute paths', t => { }); test('symlink to directory containing test files', t => { - const api = apiCreator(); + const api = apiCreator({files: ['test/fixture/symlink/*.js']}); - return api.run([path.join(__dirname, 'fixture/symlink')]) + return api.run() .then(runStatus => { t.is(runStatus.stats.passedTests, 1); }); @@ -567,16 +569,6 @@ test('symlink to test file directly', t => { }); }); -test('search directories recursively for files', t => { - const api = apiCreator(); - - return api.run([path.join(__dirname, 'fixture/subdir')]) - .then(runStatus => { - t.is(runStatus.stats.passedTests, 2); - t.is(runStatus.stats.failedTests, 1); - }); -}); - test('test file in node_modules is ignored', t => { t.plan(1); @@ -587,25 +579,16 @@ test('test file in node_modules is ignored', t => { }); }); -test('test file in fixtures is ignored', t => { - t.plan(1); - - const api = apiCreator(); - return api.run([path.join(__dirname, 'fixture/ignored-dirs/fixtures/test.js')]) - .then(runStatus => { - t.is(runStatus.stats.declaredTests, 0); - }); -}); - -test('test file in helpers is ignored', t => { - t.plan(1); - - const api = apiCreator(); - return api.run([path.join(__dirname, 'fixture/ignored-dirs/helpers/test.js')]) - .then(runStatus => { - t.is(runStatus.stats.declaredTests, 0); - }); -}); +// TODO: Re-enable to test helpers patterns. +// test('test file in helpers is ignored', t => { +// t.plan(1); +// +// const api = apiCreator(); +// return api.run([path.join(__dirname, 'fixture/ignored-dirs/helpers/test.js')]) +// .then(runStatus => { +// t.is(runStatus.stats.declaredTests, 0); +// }); +// }); test('Node.js-style --require CLI argument', t => { const requirePath = './' + path.relative('.', path.join(__dirname, 'fixture/install-global.js')).replace(/\\/g, '/'); @@ -686,6 +669,7 @@ test('emits dependencies for test files', t => { t.plan(8); const api = apiCreator({ + files: ['test/fixture/with-dependencies/*test*.js'], require: [path.resolve('test/fixture/with-dependencies/require-custom.js')] }); @@ -711,7 +695,7 @@ test('emits dependencies for test files', t => { }); }); - return api.run(['test/fixture/with-dependencies/*test*.js']); + return api.run(); }); test('verify test count', t => { diff --git a/test/ava-files.js b/test/ava-files.js deleted file mode 100644 index c3b82ad03..000000000 --- a/test/ava-files.js +++ /dev/null @@ -1,252 +0,0 @@ -'use strict'; -const path = require('path'); -const tap = require('tap'); -const AvaFiles = require('../lib/ava-files'); - -const {test} = tap; - -tap.afterEach(done => { - // We changed the CWD in some of the tests - process.chdir(path.resolve(__dirname, '..')); - done(); -}); - -function fixture(...args) { - args.unshift(__dirname, 'fixture', 'ava-files'); - return path.join(...args); -} - -test('ignores relativeness in patterns', t => { - const avaFiles = new AvaFiles({files: ['./foo']}); - const file = avaFiles.files[0]; - - t.is(file, 'foo'); - t.end(); -}); - -test('testMatcher', t => { - const avaFiles = new AvaFiles({files: ['**/foo*']}); - - function isTest(file) { - t.true(avaFiles.isTest(file), `${file} should be a test`); - } - - function notTest(file) { - t.false(avaFiles.isTest(file), `${file} should not be a test`); - } - - isTest('foo-bar.js'); - isTest('foo.js'); - isTest('foo/blah.js'); - isTest('bar/foo.js'); - isTest('bar/foo-bar/baz/buz.js'); - notTest('bar/baz/buz.js'); - notTest('bar.js'); - notTest('bar/bar.js'); - notTest('_foo-bar.js'); - notTest('foo/_foo-bar.js'); - notTest('foo-bar.txt'); - notTest('node_modules/foo.js'); - notTest('fixtures/foo.js'); - notTest('helpers/foo.js'); - t.end(); -}); - -test('sourceMatcher - defaults', t => { - const avaFiles = new AvaFiles({files: ['**/foo*']}); - - function isSource(file) { - t.true(avaFiles.isSource(file), `${file} should be a source`); - } - - function notSource(file) { - t.false(avaFiles.isSource(file), `${file} should not be a source`); - } - - isSource('foo-bar.js'); - isSource('foo.js'); - isSource('foo/blah.js'); - isSource('bar/foo.js'); - - isSource('_foo-bar.js'); - isSource('foo/_foo-bar.js'); - isSource('fixtures/foo.js'); - isSource('helpers/foo.js'); - - isSource('snapshots/foo.js.snap'); - isSource('snapshots/bar.js.snap'); - - // TODO: Watcher should probably track any required file that matches the source pattern and has a require extension installed for the given extension. - notSource('foo-bar.json'); - notSource('foo-bar.coffee'); - - // These seem OK - isSource('bar.js'); - isSource('bar/bar.js'); - notSource('node_modules/foo.js'); - t.end(); -}); - -test('sourceMatcher - allow matching specific node_modules directories', t => { - const avaFiles = new AvaFiles({ - files: ['**/foo*'], - sources: ['node_modules/foo/**'] - }); - - t.true(avaFiles.isSource('node_modules/foo/foo.js')); - t.false(avaFiles.isSource('node_modules/bar/foo.js')); - t.end(); -}); - -test('sourceMatcher - providing negation patterns', t => { - const avaFiles = new AvaFiles({ - files: ['**/foo*'], - sources: ['!**/bar*'] - }); - - t.false(avaFiles.isSource('node_modules/foo/foo.js')); - t.false(avaFiles.isSource('bar.js')); - t.false(avaFiles.isSource('foo/bar.js')); - t.end(); -}); - -test('findFiles - does not return duplicates of the same file', t => { - const avaFiles = new AvaFiles({files: ['**/ava-files/no-duplicates/**']}); - - avaFiles.findTestFiles().then(files => { - t.is(files.length, 2); - t.end(); - }); -}); - -test('findFiles - honors cwd option', t => { - const avaFiles = new AvaFiles({ - files: ['**/test/*.js'], - cwd: fixture('cwd', 'dir-b') - }); - - avaFiles.findTestFiles().then(files => { - t.is(files.length, 1); - t.is(path.basename(files[0]), 'baz.js'); - t.end(); - }); -}); - -test('findFiles - finds the correct files by default', t => { - const fixtureDir = fixture('default-patterns'); - process.chdir(fixtureDir); - - const expected = [ - 'sub/directory/__tests__/foo.js', - 'sub/directory/bar.test.js', - 'test-foo.js', - 'test.js', - 'test/baz.js', - 'test/deep/deep.js' - ].map(file => path.join(fixtureDir, file)).sort(); - - const avaFiles = new AvaFiles(); - - avaFiles.findTestFiles().then(files => { - files.sort(); - t.deepEqual(files, expected); - t.end(); - }); -}); - -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); - - const expected = [ - 'sub/directory/__tests__/helpers/foo.js', - 'sub/directory/__tests__/_foo.js', - 'test/helpers/test.js', - 'test/_foo-help.js' - ].sort().map(file => path.join(fixtureDir, file)); - - const avaFiles = new AvaFiles(); - - avaFiles.findTestHelpers().then(files => { - t.deepEqual(files.sort(), expected); - 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/fixture/ava-files/no-duplicates/lib/bar.js b/test/fixture/ava-files/no-duplicates/lib/bar.js deleted file mode 100644 index d15abba59..000000000 --- a/test/fixture/ava-files/no-duplicates/lib/bar.js +++ /dev/null @@ -1 +0,0 @@ -// Empty diff --git a/test/fixture/ava-files/no-duplicates/lib/foo.js b/test/fixture/ava-files/no-duplicates/lib/foo.js deleted file mode 100644 index d15abba59..000000000 --- a/test/fixture/ava-files/no-duplicates/lib/foo.js +++ /dev/null @@ -1 +0,0 @@ -// Empty diff --git a/test/fixture/ava-files/custom-extension/test/do-not-compile.js b/test/fixture/globs/custom-extension/test/do-not-compile.js similarity index 100% rename from test/fixture/ava-files/custom-extension/test/do-not-compile.js rename to test/fixture/globs/custom-extension/test/do-not-compile.js diff --git a/test/fixture/ava-files/custom-extension/test/foo.jsx b/test/fixture/globs/custom-extension/test/foo.jsx similarity index 100% rename from test/fixture/ava-files/custom-extension/test/foo.jsx rename to test/fixture/globs/custom-extension/test/foo.jsx diff --git a/test/fixture/ava-files/custom-extension/test/helpers/a.jsx b/test/fixture/globs/custom-extension/test/helpers/a.jsx similarity index 100% rename from test/fixture/ava-files/custom-extension/test/helpers/a.jsx rename to test/fixture/globs/custom-extension/test/helpers/a.jsx diff --git a/test/fixture/ava-files/custom-extension/test/helpers/b.js b/test/fixture/globs/custom-extension/test/helpers/b.js similarity index 100% rename from test/fixture/ava-files/custom-extension/test/helpers/b.js rename to test/fixture/globs/custom-extension/test/helpers/b.js diff --git a/test/fixture/ava-files/custom-extension/test/sub/_helper.jsx b/test/fixture/globs/custom-extension/test/sub/_helper.jsx similarity index 100% rename from test/fixture/ava-files/custom-extension/test/sub/_helper.jsx rename to test/fixture/globs/custom-extension/test/sub/_helper.jsx diff --git a/test/fixture/ava-files/custom-extension/test/sub/bar.jsx b/test/fixture/globs/custom-extension/test/sub/bar.jsx similarity index 100% rename from test/fixture/ava-files/custom-extension/test/sub/bar.jsx rename to test/fixture/globs/custom-extension/test/sub/bar.jsx diff --git a/test/fixture/ava-files/cwd/dir-a/test/bar.js b/test/fixture/globs/cwd/dir-a/test/bar.js similarity index 100% rename from test/fixture/ava-files/cwd/dir-a/test/bar.js rename to test/fixture/globs/cwd/dir-a/test/bar.js diff --git a/test/fixture/ava-files/cwd/dir-a/test/foo.js b/test/fixture/globs/cwd/dir-a/test/foo.js similarity index 100% rename from test/fixture/ava-files/cwd/dir-a/test/foo.js rename to test/fixture/globs/cwd/dir-a/test/foo.js diff --git a/test/fixture/ava-files/cwd/dir-b/test/baz.js b/test/fixture/globs/cwd/dir-b/test/baz.js similarity index 100% rename from test/fixture/ava-files/cwd/dir-b/test/baz.js rename to test/fixture/globs/cwd/dir-b/test/baz.js diff --git a/test/fixture/ava-files/default-patterns/sub/directory/__tests__/_foo.js b/test/fixture/globs/default-patterns/sub/directory/__tests__/_foo.js similarity index 100% rename from test/fixture/ava-files/default-patterns/sub/directory/__tests__/_foo.js rename to test/fixture/globs/default-patterns/sub/directory/__tests__/_foo.js diff --git a/test/fixture/ava-files/default-patterns/sub/directory/__tests__/fixtures/foo.js b/test/fixture/globs/default-patterns/sub/directory/__tests__/fixtures/foo.js similarity index 100% rename from test/fixture/ava-files/default-patterns/sub/directory/__tests__/fixtures/foo.js rename to test/fixture/globs/default-patterns/sub/directory/__tests__/fixtures/foo.js diff --git a/test/fixture/ava-files/default-patterns/sub/directory/__tests__/foo.js b/test/fixture/globs/default-patterns/sub/directory/__tests__/foo.js similarity index 100% rename from test/fixture/ava-files/default-patterns/sub/directory/__tests__/foo.js rename to test/fixture/globs/default-patterns/sub/directory/__tests__/foo.js diff --git a/test/fixture/ava-files/default-patterns/sub/directory/__tests__/helpers/foo.js b/test/fixture/globs/default-patterns/sub/directory/__tests__/helpers/foo.js similarity index 100% rename from test/fixture/ava-files/default-patterns/sub/directory/__tests__/helpers/foo.js rename to test/fixture/globs/default-patterns/sub/directory/__tests__/helpers/foo.js diff --git a/test/fixture/ava-files/default-patterns/sub/directory/bar.test.js b/test/fixture/globs/default-patterns/sub/directory/bar.test.js similarity index 100% rename from test/fixture/ava-files/default-patterns/sub/directory/bar.test.js rename to test/fixture/globs/default-patterns/sub/directory/bar.test.js diff --git a/test/fixture/ava-files/default-patterns/test-foo.js b/test/fixture/globs/default-patterns/test-foo.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test-foo.js rename to test/fixture/globs/default-patterns/test-foo.js diff --git a/test/fixture/ava-files/default-patterns/test.js b/test/fixture/globs/default-patterns/test.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test.js rename to test/fixture/globs/default-patterns/test.js diff --git a/test/fixture/ava-files/default-patterns/test/_foo-help.js b/test/fixture/globs/default-patterns/test/_foo-help.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test/_foo-help.js rename to test/fixture/globs/default-patterns/test/_foo-help.js diff --git a/test/fixture/ava-files/default-patterns/test/baz.js b/test/fixture/globs/default-patterns/test/baz.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test/baz.js rename to test/fixture/globs/default-patterns/test/baz.js diff --git a/test/fixture/ava-files/default-patterns/test/deep/deep.js b/test/fixture/globs/default-patterns/test/deep/deep.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test/deep/deep.js rename to test/fixture/globs/default-patterns/test/deep/deep.js diff --git a/test/fixture/ava-files/default-patterns/test/fixtures/foo-fixt.js b/test/fixture/globs/default-patterns/test/fixtures/foo-fixt.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test/fixtures/foo-fixt.js rename to test/fixture/globs/default-patterns/test/fixtures/foo-fixt.js diff --git a/test/fixture/ava-files/default-patterns/test/helpers/test.js b/test/fixture/globs/default-patterns/test/helpers/test.js similarity index 100% rename from test/fixture/ava-files/default-patterns/test/helpers/test.js rename to test/fixture/globs/default-patterns/test/helpers/test.js diff --git a/test/fixture/globs/no-files/package.json b/test/fixture/globs/no-files/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/globs/no-files/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/invalid-globs/files/package.json b/test/fixture/invalid-globs/files/package.json new file mode 100644 index 000000000..eaa79f1ff --- /dev/null +++ b/test/fixture/invalid-globs/files/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "files": [] + } +} diff --git a/test/fixture/invalid-globs/sources/package.json b/test/fixture/invalid-globs/sources/package.json new file mode 100644 index 000000000..5548dbe8d --- /dev/null +++ b/test/fixture/invalid-globs/sources/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "sources": [] + } +} diff --git a/test/fixture/parallel-runs/package.json b/test/fixture/parallel-runs/package.json new file mode 100644 index 000000000..0a8129b18 --- /dev/null +++ b/test/fixture/parallel-runs/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "files": ["*.js"] + } +} diff --git a/test/fixture/pkg-conf/resolve-dir/package.json b/test/fixture/pkg-conf/resolve-dir/package.json index 7634802f2..2e6f0c272 100644 --- a/test/fixture/pkg-conf/resolve-dir/package.json +++ b/test/fixture/pkg-conf/resolve-dir/package.json @@ -2,6 +2,6 @@ "name": "application-name", "version": "0.0.1", "ava": { - "files": "dir-a/*.js" + "files": ["dir-a/*.js"] } } diff --git a/test/fixture/snapshots/test-content/package.json b/test/fixture/snapshots/test-content/package.json index 07510b0f5..7a403aaa7 100644 --- a/test/fixture/snapshots/test-content/package.json +++ b/test/fixture/snapshots/test-content/package.json @@ -1,5 +1,5 @@ { "ava": { - "files": "tests" + "files": ["tests/test.js"] } } diff --git a/test/fixture/snapshots/test-sourcemaps/package.json b/test/fixture/snapshots/test-sourcemaps/package.json index 813a3f0ba..56e413658 100644 --- a/test/fixture/snapshots/test-sourcemaps/package.json +++ b/test/fixture/snapshots/test-sourcemaps/package.json @@ -1,7 +1,7 @@ { "ava": { "files": [ - "build/**/*test.js" + "build/**/test.js" ] } } diff --git a/test/fixture/snapshots/tests-dir/package.json b/test/fixture/snapshots/tests-dir/package.json index 07510b0f5..101d85b3c 100644 --- a/test/fixture/snapshots/tests-dir/package.json +++ b/test/fixture/snapshots/tests-dir/package.json @@ -1,5 +1,5 @@ { "ava": { - "files": "tests" + "files": ["tests/**/*.js"] } } diff --git a/test/globs.js b/test/globs.js new file mode 100644 index 000000000..b326bca69 --- /dev/null +++ b/test/globs.js @@ -0,0 +1,181 @@ +'use strict'; +const path = require('path'); +const tap = require('tap'); +const globs = require('../lib/globs'); + +const {test} = tap; + +tap.afterEach(done => { + // We changed the CWD in some of the tests + process.chdir(path.resolve(__dirname, '..')); + done(); +}); + +function fixture(...args) { + args.unshift(__dirname, 'fixture', 'globs'); + return path.join(...args); +} + +test('ignores relativeness in patterns', t => { + const {testPatterns} = globs.normalizeGlobs(['./foo.js', '!./bar'], undefined, ['js']); + t.deepEqual(testPatterns, ['foo.js', '!bar']); + t.end(); +}); + +test('isTest', t => { + const options = globs.normalizeGlobs( + ['**/foo*.js', '**/foo*/**/*.js', '!**/fixtures', '!**/helpers'], + undefined, + ['js'] + ); + + function isTest(file) { + t.true(globs.isTest(fixture(file), options), `${file} should be a test`); + } + + function notTest(file) { + t.false(globs.isTest(fixture(file), options), `${file} should not be a test`); + } + + isTest('foo-bar.js'); + isTest('foo.js'); + isTest('foo/blah.js'); + isTest('bar/foo.js'); + isTest('bar/foo-bar/baz/buz.js'); + notTest('bar/baz/buz.js'); + notTest('bar.js'); + notTest('bar/bar.js'); + notTest('_foo-bar.js'); + notTest('foo/_foo-bar.js'); + notTest('foo-bar.txt'); + notTest('node_modules/foo.js'); + notTest('fixtures/foo.js'); + notTest('helpers/foo.js'); + t.end(); +}); + +test('isSource with defaults', t => { + const options = globs.normalizeGlobs(undefined, undefined, ['js']); + + function isSource(file) { + t.true(globs.isSource(file, options), `${file} should be a source`); + } + + function notSource(file) { + t.false(globs.isSource(file, options), `${file} should not be a source`); + } + + isSource('foo-bar.js'); + isSource('foo.js'); + isSource('foo/blah.js'); + isSource('bar/foo.js'); + + isSource('_foo-bar.js'); + isSource('foo/_foo-bar.js'); + isSource('fixtures/foo.js'); + isSource('helpers/foo.js'); + + isSource('snapshots/foo.js.snap'); + isSource('snapshots/bar.js.snap'); + + // TODO: Watcher should probably track any required file that matches the source pattern and has a require extension installed for the given extension. + notSource('foo-bar.json'); + notSource('foo-bar.coffee'); + + // These seem OK + isSource('bar.js'); + isSource('bar/bar.js'); + notSource('node_modules/foo.js'); + t.end(); +}); + +test('isSource with negation negation patterns', t => { + const options = globs.normalizeGlobs( + ['**/foo*'], + ['!**/bar*'], + ['js'] + ); + + t.false(globs.isSource('node_modules/foo/foo.js', options)); + t.false(globs.isSource('bar.js', options)); + t.false(globs.isSource('foo/bar.js', options)); + t.end(); +}); + +test('findHelpersAndTests finds tests (just .js)', async t => { + const fixtureDir = fixture('default-patterns'); + process.chdir(fixtureDir); + + const expected = [ + 'sub/directory/__tests__/foo.js', + 'sub/directory/bar.test.js', + 'test-foo.js', + 'test.js', + 'test/baz.js', + 'test/deep/deep.js' + ].map(file => path.join(fixtureDir, file)).sort(); + + const {tests: actual} = await globs.findHelpersAndTests({ + cwd: fixtureDir, + ...globs.normalizeGlobs(['!**/fixtures/*.*', '!**/helpers/*.*'], undefined, ['js']) + }); + actual.sort(); + t.deepEqual(actual, expected); +}); + +test('findHelpersAndTests finds tests (.js, .jsx)', async 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 {tests: actual} = await globs.findHelpersAndTests({ + cwd: fixtureDir, + ...globs.normalizeGlobs(['!**/fixtures/*.*', '!**/helpers/*.*'], undefined, ['js', 'jsx']) + }); + actual.sort(); + t.deepEqual(actual, expected); +}); + +test('findHelpersAndTests finds helpers (just .js)', async t => { + const fixtureDir = fixture('default-patterns'); + process.chdir(fixtureDir); + + // TODO: Support pattern to match helpers directories. + const expected = [ + // 'sub/directory/__tests__/helpers/foo.js', + 'sub/directory/__tests__/_foo.js', + // 'test/helpers/test.js', + 'test/_foo-help.js' + ].sort().map(file => path.join(fixtureDir, file)); + + const {helpers: actual} = await globs.findHelpersAndTests({ + cwd: fixtureDir, + ...globs.normalizeGlobs(undefined, undefined, ['js']) + }); + actual.sort(); + t.deepEqual(actual, expected); +}); + +test('findHelpersAndTests finds helpers (.js and .jsx)', async t => { + const fixtureDir = fixture('custom-extension'); + process.chdir(fixtureDir); + + // TODO: Support pattern to match helpers directories. + const expected = [ + 'test/sub/_helper.jsx' + // 'test/helpers/a.jsx', + // 'test/helpers/b.js' + ].sort().map(file => path.join(fixtureDir, file)); + + const {helpers: actual} = await globs.findHelpersAndTests({ + cwd: fixtureDir, + ...globs.normalizeGlobs(undefined, undefined, ['js', 'jsx']) + }); + actual.sort(); + t.deepEqual(actual, expected); +}); diff --git a/test/helper/report.js b/test/helper/report.js index cd49855f5..792f13b1a 100644 --- a/test/helper/report.js +++ b/test/helper/report.js @@ -6,6 +6,7 @@ const globby = require('globby'); const proxyquire = require('proxyquire'); const replaceString = require('replace-string'); const pkg = require('../../package.json'); +const {normalizeGlobs} = require('../../lib/globs'); let _Api = null; const createApi = options => { @@ -93,10 +94,27 @@ const run = (type, reporter, match = []) => { pattern = '*.ts'; } + options.globs = normalizeGlobs(undefined, undefined, options.extensions.all); + const api = createApi(options); api.on('run', plan => reporter.startRun(plan)); - const files = globby.sync(pattern, {cwd: projectDir}).sort(); + const files = globby.sync(pattern, { + absolute: true, + brace: true, + case: false, + cwd: projectDir, + dot: false, + expandDirectories: false, + extglob: true, + followSymlinkedDirectories: true, + gitignore: false, + globstar: true, + matchBase: false, + onlyFiles: true, + stats: false, + unique: true + }).sort(); if (type !== 'watch') { return api.run(files).then(() => { reporter.endRun(); diff --git a/test/integration/assorted.js b/test/integration/assorted.js index a88f9d103..d85dbdeb9 100644 --- a/test/integration/assorted.js +++ b/test/integration/assorted.js @@ -61,7 +61,7 @@ test('handles NODE_PATH', t => { }); test('works when no files are found', t => { - execCli('!*', (err, stdout) => { + execCli([], {dirname: 'fixture/globs/no-files'}, (err, stdout) => { t.is(err.code, 1); t.match(stdout, 'Couldn\'t find any files to test'); t.end(); diff --git a/test/integration/compilation.js b/test/integration/compilation.js index 0060c119d..f667b375b 100644 --- a/test/integration/compilation.js +++ b/test/integration/compilation.js @@ -55,7 +55,7 @@ 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) => { + execCli(['power-assert.foo'], {dirname: 'fixture/just-enhancement-compilation/custom-extension'}, (err, stdout) => { t.ok(err); t.match(stripAnsi(stdout), /bool\n.*=> false/); t.end(); diff --git a/test/integration/config.js b/test/integration/config.js index 2332070a3..36171e5ae 100644 --- a/test/integration/config.js +++ b/test/integration/config.js @@ -43,17 +43,6 @@ test('pkg-conf(resolve-dir): resolves tests from the package.json dir if none ar }); }); -test('pkg-conf(resolve-dir): resolves tests process.cwd() if globs are passed on the command line', t => { - execCli(['--verbose', 'dir-a/*.js'], {dirname: 'fixture/pkg-conf/resolve-dir/dir-a-wrapper'}, (err, stdout) => { - t.ifError(err); - t.match(stdout, /dir-a-wrapper-3/); - t.match(stdout, /dir-a-wrapper-4/); - t.notMatch(stdout, /dir-a-base/); - t.notMatch(stdout, /dir-a-base/); - t.end(); - }); -}); - test('use current working directory if `package.json` is not found', () => { const cwd = uniqueTempDir({create: true}); const testFilePath = path.join(cwd, 'test.js'); diff --git a/test/integration/globs.js b/test/integration/globs.js new file mode 100644 index 000000000..8be848601 --- /dev/null +++ b/test/integration/globs.js @@ -0,0 +1,30 @@ +'use strict'; +const {test} = require('tap'); +const figures = require('figures'); +const {execCli} = require('../helper/cli'); + +test('errors if top-level files is an empty array', t => { + execCli(['es2015.js'], {dirname: 'fixture/invalid-globs/files'}, (err, stdout, stderr) => { + t.ok(err); + + let expectedOutput = '\n'; + expectedOutput += figures.cross + ' The \'files\' configuration must be an array containing glob patterns.'; + expectedOutput += '\n'; + + t.is(stderr, expectedOutput); + t.end(); + }); +}); + +test('errors if top-level sources is an empty array', t => { + execCli(['es2015.js'], {dirname: 'fixture/invalid-globs/sources'}, (err, stdout, stderr) => { + t.ok(err); + + let expectedOutput = '\n'; + expectedOutput += figures.cross + ' The \'sources\' configuration must be an array containing glob patterns.'; + expectedOutput += '\n'; + + t.is(stderr, expectedOutput); + t.end(); + }); +}); diff --git a/test/integration/parallel-runs.js b/test/integration/parallel-runs.js index af8fe6cb5..41c3726b5 100644 --- a/test/integration/parallel-runs.js +++ b/test/integration/parallel-runs.js @@ -5,7 +5,7 @@ const {execCli} = require('../helper/cli'); test('correctly distributes the test files', t => { t.plan(3); for (let i = 0; i < 3; i++) { - execCli('*.js', { + execCli([], { dirname: 'fixture/parallel-runs', env: { CI: '1', diff --git a/test/integration/stack-traces.js b/test/integration/stack-traces.js index 51efe3ff1..ad82a4688 100644 --- a/test/integration/stack-traces.js +++ b/test/integration/stack-traces.js @@ -3,7 +3,7 @@ const {test} = require('tap'); const {execCli} = require('../helper/cli'); test('enabling long stack traces will provide detailed debug information', t => { - execCli('long-stack-trace', (err, stdout, stderr) => { + execCli('long-stack-trace/test.js', (err, stdout, stderr) => { t.ok(err); t.match(stderr, /From previous event/); t.end(); diff --git a/test/integration/watcher.js b/test/integration/watcher.js index faa1469bf..c501f3237 100644 --- a/test/integration/watcher.js +++ b/test/integration/watcher.js @@ -61,7 +61,7 @@ test('watcher respects custom test file extensions', t => { test('watcher reruns test files when source dependencies change', t => { let killed = false; - const child = execCli(['--verbose', '--watch', 'test-*.js'], {dirname: 'fixture/watcher/with-dependencies', env: {CI: ''}}, err => { + const child = execCli(['--verbose', '--watch', 'test-1.js', 'test-2.js'], {dirname: 'fixture/watcher/with-dependencies', env: {CI: ''}}, err => { t.ok(killed); t.ifError(err); t.end(); diff --git a/test/watcher.js b/test/watcher.js index 6423ba9d6..5f59af522 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -7,7 +7,7 @@ const lolex = require('lolex'); const proxyquire = require('proxyquire'); const sinon = require('sinon'); const {test} = require('tap'); -const AvaFiles = require('../lib/ava-files'); +const {normalizeGlobs} = require('../lib/globs'); const {setImmediate} = require('../lib/now-and-timers'); require('../lib/chalk').set({}); @@ -42,7 +42,6 @@ group('chokidar', (beforeEach, test, group) => { let debug; let reporter; let api; - let avaFiles; let Subject; let runStatus; let resetRunStatus; @@ -60,8 +59,7 @@ group('chokidar', (beforeEach, test, group) => { return (...args) => { debug(...[name, ...args]); }; - }, - './ava-files': avaFiles + } }); } @@ -125,13 +123,11 @@ group('chokidar', (beforeEach, test, group) => { chokidarEmitter = new EventEmitter(); chokidar.watch.returns(chokidarEmitter); - avaFiles = AvaFiles; - api.run.returns(new Promise(() => {})); files = [ 'test.js', 'test-*.js', - 'test' + 'test/**/*.js' ]; defaultApiOptions = { clearLogOnNextRun: false, @@ -149,7 +145,7 @@ group('chokidar', (beforeEach, test, group) => { Subject = proxyWatcher(); }); - const start = (specificFiles, sources) => new Subject({reporter, api, files: specificFiles || files, sources: sources || []}); + const start = (specificFiles, sources) => new Subject({reporter, api, files: specificFiles || [], globs: normalizeGlobs(files, sources, ['js'])}); const emitChokidar = (event, path) => { chokidarEmitter.emit('all', event, path); @@ -189,9 +185,9 @@ group('chokidar', (beforeEach, test, group) => { t.ok(chokidar.watch.calledOnce); t.strictDeepEqual(chokidar.watch.firstCall.args, [ - ['package.json', '**/*.js', '**/*.snap'].concat(files), + ['**/*.snap', 'ava.config.js', 'package.json', '**/*.js', ...files], { - ignored: defaultIgnore.map(dir => `${dir}/**/*`), + ignored: [...defaultIgnore.map(dir => `${dir}/**/*`), '**/node_modules/**/*'], ignoreInitial: true } ]); @@ -205,21 +201,7 @@ group('chokidar', (beforeEach, test, group) => { t.strictDeepEqual(chokidar.watch.firstCall.args, [ ['foo.js', 'baz.js'].concat(files), { - ignored: defaultIgnore.map(dir => `${dir}/**/*`).concat('bar.js', 'qux.js'), - ignoreInitial: true - } - ]); - }); - - test('configured sources can override default ignore patterns', t => { - t.plan(2); - start(null, ['node_modules/foo/*.js']); - - t.ok(chokidar.watch.calledOnce); - t.strictDeepEqual(chokidar.watch.firstCall.args, [ - ['node_modules/foo/*.js'].concat(files), - { - ignored: defaultIgnore.map(dir => `${dir}/**/*`).concat('!node_modules/foo/*.js'), + ignored: [...defaultIgnore.map(dir => `${dir}/**/*`), '**/node_modules/**/*'], ignoreInitial: true } ]); @@ -237,7 +219,7 @@ group('chokidar', (beforeEach, test, group) => { start(); t.ok(api.run.calledOnce); - t.strictDeepEqual(api.run.firstCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.firstCall.args, [[], defaultApiOptions]); // The endRun and lineWriter.writeLine methods are only called after the run promise fulfils t.ok(reporter.endRun.notCalled); @@ -307,7 +289,7 @@ group('chokidar', (beforeEach, test, group) => { return debounce().then(() => { t.ok(api.run.calledTwice); // No explicit files are provided - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -349,7 +331,7 @@ group('chokidar', (beforeEach, test, group) => { api.run.returns(Promise.resolve(resetRunStatus())); change(); return debounce().then(() => { - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: false, runVector: 2 @@ -358,7 +340,7 @@ group('chokidar', (beforeEach, test, group) => { change(); return debounce(); }).then(() => { - t.strictDeepEqual(api.run.thirdCall.args, [files, { + t.strictDeepEqual(api.run.thirdCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 3 @@ -533,7 +515,7 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // No explicit files are provided - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -571,56 +553,6 @@ group('chokidar', (beforeEach, test, group) => { }); }); - test('initial exclude patterns override whether something is a test file', t => { - t.plan(2); - - avaFiles = function (options) { - const ret = new AvaFiles(options); - // Note: There is no way for users to actually set exclude patterns yet. - // This test just validates that internal updates to the default excludes pattern will be obeyed. - ret.excludePatterns = ['!*bar*']; - return ret; - }; - - Subject = proxyWatcher(); - - files = ['foo-{bar,baz}.js']; - api.run.returns(Promise.resolve(runStatus)); - start(); - - add('foo-bar.js'); - add('foo-baz.js'); - return debounce(2).then(() => { - t.ok(api.run.calledTwice); - // `foo-bar.js` is excluded from being a test file, thus the initial tests - // are run - t.strictDeepEqual(api.run.secondCall.args, [files, { - ...defaultApiOptions, - clearLogOnNextRun: true, - runVector: 2 - }]); - }); - }); - - test('test files must end in .js', t => { - t.plan(2); - - files = ['foo.bar']; - api.run.returns(Promise.resolve(runStatus)); - start(); - - add('foo.bar'); - return debounce(2).then(() => { - t.ok(api.run.calledTwice); - // `foo.bar` cannot be a test file, thus the initial tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, { - ...defaultApiOptions, - clearLogOnNextRun: true, - runVector: 2 - }]); - }); - }); - test('test files must not start with an underscore', t => { t.plan(2); @@ -632,64 +564,7 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // `_foo.bar` cannot be a test file, thus the initial tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, { - ...defaultApiOptions, - clearLogOnNextRun: true, - runVector: 2 - }]); - }); - }); - - test('files patterns may match directories', t => { - t.plan(2); - - files = ['dir', 'another-dir/*/deeper']; - api.run.returns(Promise.resolve(runStatus)); - start(); - - add(path.join('dir', 'test.js')); - add(path.join('dir', 'nested', 'test.js')); - add(path.join('another-dir', 'nested', 'deeper', 'test.js')); - return debounce(3).then(() => { - t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [ - [ - path.join('dir', 'test.js'), - path.join('dir', 'nested', 'test.js'), - path.join('another-dir', 'nested', 'deeper', 'test.js') - ], - { - ...defaultApiOptions, - clearLogOnNextRun: true, - runVector: 2 - } - ]); - }); - }); - - test('exclude patterns override directory matches', t => { - t.plan(2); - - avaFiles = function (options) { - const ret = new AvaFiles(options); - // Note: There is no way for users to actually set exclude patterns yet. - // This test just validates that internal updates to the default excludes pattern will be obeyed. - ret.excludePatterns = ['!**/exclude/**']; - return ret; - }; - - Subject = proxyWatcher(); - - files = ['dir']; - api.run.returns(Promise.resolve(runStatus)); - start(); - - add(path.join('dir', 'exclude', 'foo.js')); - return debounce(2).then(() => { - t.ok(api.run.calledTwice); - // `dir/exclude/foo.js` is excluded from being a test file, thus the initial - // tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -706,13 +581,13 @@ group('chokidar', (beforeEach, test, group) => { stdin.write(`${input}\n`); return delay().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [files, {...defaultApiOptions, runVector: 2}]); + t.strictDeepEqual(api.run.secondCall.args, [[], {...defaultApiOptions, runVector: 2}]); stdin.write(`\t${input} \n`); return delay(); }).then(() => { t.ok(api.run.calledThrice); - t.strictDeepEqual(api.run.thirdCall.args, [files, {...defaultApiOptions, runVector: 3}]); + t.strictDeepEqual(api.run.thirdCall.args, [[], {...defaultApiOptions, runVector: 3}]); }); }); } @@ -746,7 +621,7 @@ group('chokidar', (beforeEach, test, group) => { stdin.write(`${input}\n`); return delay().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: false, runVector: 2, @@ -979,7 +854,7 @@ group('chokidar', (beforeEach, test, group) => { change('cannot-be-mapped.js'); return debounce().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -1076,7 +951,7 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // Expect all tests to be rerun since `dep-2.js` is not a tracked // dependency - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -1110,7 +985,7 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledThrice); // Expect all tests to be rerun since `foo.bar` is not a tracked // dependency - t.strictDeepEqual(api.run.thirdCall.args, [files, { + t.strictDeepEqual(api.run.thirdCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 3 @@ -1148,25 +1023,7 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // Since the excluded files are not tracked as a dependency, all tests // are expected to be rerun - t.strictDeepEqual(api.run.secondCall.args, [files, { - ...defaultApiOptions, - clearLogOnNextRun: true, - runVector: 2 - }]); - }); - }); - - test('allows default exclusion patterns to be overriden', t => { - t.plan(2); - seed(['node_modules/foo/*.js']); - - const dep = path.join('node_modules', 'foo', 'index.js'); - emitDependencies(path.join('test', '1.js'), [path.resolve(dep)]); - change(dep); - - return debounce(1).then(() => { - t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2 @@ -1193,7 +1050,7 @@ group('chokidar', (beforeEach, test, group) => { // wouldn't even be picked up. However this lets us test dependency // tracking without directly inspecting the internal state of the // watcher. - t.strictDeepEqual(api.run.secondCall.args, [files, { + t.strictDeepEqual(api.run.secondCall.args, [[], { ...defaultApiOptions, clearLogOnNextRun: true, runVector: 2