mirror of https://github.com/lukechilds/ava.git
Browse Source
* ava-files should honor a `cwd` option. * Properly resolve test files in non-watch mode. * update test/watcher.js to reflect new AvaFiles Api * extract ava-files to it's own thingfailure-output
James Talmage
8 years ago
committed by
GitHub
27 changed files with 81 additions and 431 deletions
@ -1,262 +0,0 @@ |
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
var Promise = require('bluebird'); |
|||
var slash = require('slash'); |
|||
var globby = require('globby'); |
|||
var flatten = require('arr-flatten'); |
|||
var defaultIgnore = require('ignore-by-default').directories(); |
|||
var multimatch = require('multimatch'); |
|||
|
|||
function defaultExcludePatterns() { |
|||
return [ |
|||
'!**/node_modules/**', |
|||
'!**/fixtures/**', |
|||
'!**/helpers/**' |
|||
]; |
|||
} |
|||
|
|||
function defaultIncludePatterns() { |
|||
return [ |
|||
'test.js', |
|||
'test-*.js', |
|||
'test', |
|||
'**/__tests__', |
|||
'**/*.test.js' |
|||
]; |
|||
} |
|||
|
|||
function AvaFiles(files, sources) { |
|||
if (!(this instanceof AvaFiles)) { |
|||
throw new TypeError('Class constructor AvaFiles cannot be invoked without \'new\''); |
|||
} |
|||
|
|||
if (!files || !files.length) { |
|||
files = defaultIncludePatterns(); |
|||
} |
|||
|
|||
this.excludePatterns = defaultExcludePatterns(); |
|||
|
|||
this.files = files; |
|||
this.sources = sources || []; |
|||
} |
|||
|
|||
AvaFiles.prototype.findTestFiles = function () { |
|||
return handlePaths(this.files, this.excludePatterns, { |
|||
cache: Object.create(null), |
|||
statCache: Object.create(null), |
|||
realpathCache: Object.create(null), |
|||
symlinks: Object.create(null) |
|||
}); |
|||
}; |
|||
|
|||
function getDefaultIgnorePatterns() { |
|||
return defaultIgnore.map(function (dir) { |
|||
return dir + '/**/*'; |
|||
}); |
|||
} |
|||
|
|||
// Used on paths before they're passed to multimatch to harmonize matching
|
|||
// across platforms.
|
|||
var matchable = process.platform === 'win32' ? slash : function (path) { |
|||
return path; |
|||
}; |
|||
|
|||
AvaFiles.prototype.makeSourceMatcher = function () { |
|||
var mixedPatterns = []; |
|||
var defaultIgnorePatterns = getDefaultIgnorePatterns(); |
|||
var overrideDefaultIgnorePatterns = []; |
|||
|
|||
var hasPositivePattern = false; |
|||
this.sources.forEach(function (pattern) { |
|||
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.indexOf(pattern.split('/')[0]) >= 0) { |
|||
overrideDefaultIgnorePatterns.push(pattern); |
|||
} |
|||
}); |
|||
|
|||
// Same defaults as used for Chokidar.
|
|||
if (!hasPositivePattern) { |
|||
mixedPatterns = ['package.json', '**/*.js'].concat(mixedPatterns); |
|||
} |
|||
|
|||
return function (path) { |
|||
path = matchable(path); |
|||
|
|||
// Ignore paths outside the current working directory. They can't be matched
|
|||
// to a pattern.
|
|||
if (/^\.\.\//.test(path)) { |
|||
return false; |
|||
} |
|||
|
|||
var isSource = multimatch(path, mixedPatterns).length === 1; |
|||
if (!isSource) { |
|||
return false; |
|||
} |
|||
|
|||
var isIgnored = multimatch(path, defaultIgnorePatterns).length === 1; |
|||
if (!isIgnored) { |
|||
return true; |
|||
} |
|||
|
|||
var isErroneouslyIgnored = multimatch(path, overrideDefaultIgnorePatterns).length === 1; |
|||
if (isErroneouslyIgnored) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
}; |
|||
}; |
|||
|
|||
AvaFiles.prototype.makeTestMatcher = function () { |
|||
var excludePatterns = this.excludePatterns; |
|||
var initialPatterns = this.files.concat(excludePatterns); |
|||
|
|||
return function (filepath) { |
|||
// 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.
|
|||
var 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 `./`.
|
|||
var subpaths = dirname.split(/[\\\/]/).reduce(function (subpaths, component) { |
|||
var 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.
|
|||
var recursivePatterns = subpaths.filter(function (subpath) { |
|||
return multimatch(subpath, initialPatterns).length === 1; |
|||
}).map(function (subpath) { |
|||
// Always use / to makes multimatch consistent across platforms.
|
|||
return 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; |
|||
}; |
|||
}; |
|||
|
|||
AvaFiles.prototype.getChokidarPatterns = function () { |
|||
var paths = []; |
|||
var ignored = []; |
|||
|
|||
this.sources.forEach(function (pattern) { |
|||
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.
|
|||
var overrideDefaultIgnorePatterns = paths.filter(function (pattern) { |
|||
return defaultIgnore.indexOf(pattern.split('/')[0]) >= 0; |
|||
}).map(function (pattern) { |
|||
return '!' + pattern; |
|||
}); |
|||
ignored = getDefaultIgnorePatterns().concat(ignored, overrideDefaultIgnorePatterns); |
|||
|
|||
if (paths.length === 0) { |
|||
paths = ['package.json', '**/*.js']; |
|||
} |
|||
paths = paths.concat(this.files); |
|||
|
|||
return { |
|||
paths: paths, |
|||
ignored: ignored |
|||
}; |
|||
}; |
|||
|
|||
function handlePaths(files, excludePatterns, globOptions) { |
|||
// convert pinkie-promise to Bluebird promise
|
|||
files = Promise.resolve(globby(files.concat(excludePatterns), globOptions)); |
|||
|
|||
var searchedParents = Object.create(null); |
|||
var foundFiles = Object.create(null); |
|||
|
|||
function alreadySearchingParent(dir) { |
|||
if (searchedParents[dir]) { |
|||
return true; |
|||
} |
|||
|
|||
var parentDir = path.dirname(dir); |
|||
|
|||
if (parentDir === dir) { |
|||
// We have reached the root path.
|
|||
return false; |
|||
} |
|||
|
|||
return alreadySearchingParent(parentDir); |
|||
} |
|||
|
|||
return files |
|||
.map(function (file) { |
|||
if (fs.statSync(file).isDirectory()) { |
|||
if (alreadySearchingParent(file)) { |
|||
return null; |
|||
} |
|||
|
|||
searchedParents[file] = true; |
|||
|
|||
var pattern = path.join(file, '**', '*.js'); |
|||
|
|||
if (process.platform === 'win32') { |
|||
// Always use / in patterns, harmonizing matching across platforms.
|
|||
pattern = slash(pattern); |
|||
} |
|||
|
|||
return handlePaths([pattern], 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) |
|||
.filter(function (file) { |
|||
return file && path.extname(file) === '.js' && path.basename(file)[0] !== '_'; |
|||
}) |
|||
.map(function (file) { |
|||
return path.resolve(file); |
|||
}) |
|||
.filter(function (file) { |
|||
var alreadyFound = foundFiles[file]; |
|||
foundFiles[file] = true; |
|||
return !alreadyFound; |
|||
}); |
|||
} |
|||
|
|||
module.exports = AvaFiles; |
|||
module.exports.defaultIncludePatterns = defaultIncludePatterns; |
|||
module.exports.defaultExcludePatterns = defaultExcludePatterns; |
@ -1,144 +0,0 @@ |
|||
'use strict'; |
|||
var path = require('path'); |
|||
var tap = require('tap'); |
|||
var AvaFiles = require('../lib/ava-files'); |
|||
|
|||
var test = tap.test; |
|||
|
|||
tap.afterEach(function (done) { |
|||
// We changed the CWD in some of the tests.
|
|||
process.chdir(path.join(__dirname, '..')); |
|||
done(); |
|||
}); |
|||
|
|||
function fixture() { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
args.unshift(__dirname, 'fixture', 'ava-files'); |
|||
return path.join.apply(path, args); |
|||
} |
|||
|
|||
test('requires new', function (t) { |
|||
var avaFiles = AvaFiles; |
|||
t.throws(function () { |
|||
avaFiles(['**/foo*']); |
|||
}, 'Class constructor AvaFiles cannot be invoked without \'new\''); |
|||
t.end(); |
|||
}); |
|||
|
|||
test('testMatcher', function (t) { |
|||
var avaFiles = new AvaFiles(['**/foo*']); |
|||
|
|||
var matcher = avaFiles.makeTestMatcher(); |
|||
|
|||
function isTest(file) { |
|||
t.true(matcher(file), file + ' should be a test'); |
|||
} |
|||
|
|||
function notTest(file) { |
|||
t.false(matcher(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', function (t) { |
|||
var avaFiles = new AvaFiles(['**/foo*']); |
|||
|
|||
var matcher = avaFiles.makeSourceMatcher(); |
|||
|
|||
function isSource(file) { |
|||
t.true(matcher(file), file + ' should be a source'); |
|||
} |
|||
|
|||
function notSource(file) { |
|||
t.false(matcher(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'); |
|||
|
|||
// 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', function (t) { |
|||
var avaFiles = new AvaFiles(['**/foo*'], ['node_modules/foo/**']); |
|||
|
|||
var matcher = avaFiles.makeSourceMatcher(); |
|||
|
|||
t.true(matcher('node_modules/foo/foo.js')); |
|||
t.false(matcher('node_modules/bar/foo.js')); |
|||
t.end(); |
|||
}); |
|||
|
|||
test('sourceMatcher - providing negation patterns', function (t) { |
|||
var avaFiles = new AvaFiles(['**/foo*'], ['!**/bar*']); |
|||
|
|||
var matcher = avaFiles.makeSourceMatcher(); |
|||
|
|||
t.false(matcher('node_modules/foo/foo.js')); |
|||
t.false(matcher('bar.js')); |
|||
t.false(matcher('foo/bar.js')); |
|||
t.end(); |
|||
}); |
|||
|
|||
test('findFiles - does not return duplicates of the same file', function (t) { |
|||
var avaFiles = new AvaFiles(['**/ava-files/no-duplicates/**']); |
|||
|
|||
avaFiles.findTestFiles().then(function (files) { |
|||
t.is(files.length, 2); |
|||
t.end(); |
|||
}); |
|||
}); |
|||
|
|||
test('findFiles - finds the correct files by default', function (t) { |
|||
var fixtureDir = fixture('default-patterns'); |
|||
process.chdir(fixtureDir); |
|||
|
|||
var expected = [ |
|||
'sub/directory/__tests__/foo.js', |
|||
'sub/directory/bar.test.js', |
|||
'test-foo.js', |
|||
'test.js', |
|||
'test/baz.js', |
|||
'test/deep/deep.js' |
|||
].map(function (file) { |
|||
return path.join(fixtureDir, file); |
|||
}).sort(); |
|||
|
|||
var avaFiles = new AvaFiles(); |
|||
avaFiles.findTestFiles().then(function (files) { |
|||
files.sort(); |
|||
t.deepEqual(files, expected); |
|||
t.end(); |
|||
}); |
|||
}); |
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -1 +0,0 @@ |
|||
// empty
|
@ -0,0 +1,5 @@ |
|||
import test from '../../../../../../'; |
|||
|
|||
test(t => { |
|||
t.pass(); |
|||
}); |
@ -0,0 +1,5 @@ |
|||
import test from '../../../../../../'; |
|||
|
|||
test(t => { |
|||
t.pass(); |
|||
}); |
@ -0,0 +1,5 @@ |
|||
import test from '../../../../../'; |
|||
|
|||
test(t => { |
|||
t.pass(); |
|||
}); |
@ -0,0 +1,5 @@ |
|||
import test from '../../../../../'; |
|||
|
|||
test(t => { |
|||
t.pass(); |
|||
}); |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"name": "application-name", |
|||
"version": "0.0.1", |
|||
"ava": { |
|||
"files": "dir-a/*.js" |
|||
} |
|||
} |
Loading…
Reference in new issue