Skip to content

Commit

Permalink
Support helper glob configuration
Browse files Browse the repository at this point in the history
Fixes #2105.
  • Loading branch information
novemberborn committed May 12, 2019
1 parent 501572c commit 4f42295
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 48 deletions.
4 changes: 4 additions & 0 deletions docs/06-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ To ignore files, prefix the pattern with an `!` (exclamation mark).
"!test/exclude-files-in-this-directory/*",
"!**/exclude-files-with-this-name.*"
],
"helpers": [
"**/helpers/**/*"
],
"sources": [
"src/**/*",
"!dist/**/*"
Expand Down Expand Up @@ -49,6 +52,7 @@ Arguments passed to the CLI will always take precedence over the CLI options con
## Options

- `files`: an array of glob patterns to select test files. Files with an underscore prefix are ignored. By default only selects files with `js` extensions, even if the pattern matches other files. Specify `extensions` and `babel.extensions` to allow other file extensions
- `helpers`: an array of glob patterns to select helper files. Files matched here are never considered as tests. By default only selects files with `js` extensions, even if the pattern matches other files. Specify `extensions` and `babel.extensions` to allow other file extensions
- `sources`: an array of glob patterns to match files that, when changed, cause tests to be re-run (when 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
Expand Down
11 changes: 8 additions & 3 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,17 @@ class Api extends Emittery {
const precompiler = await this._setupPrecompiler();
let helpers = [];
if (files.length === 0 || precompiler.enabled) {
const found = await globs.findHelpersAndTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
let found;
if (precompiler.enabled) {
found = await globs.findHelpersAndTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
helpers = found.helpers;
} else {
found = await globs.findTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
}

if (files.length === 0) {
({tests: files} = found);
}

({helpers} = found);
}

if (this.options.parallelRuns) {
Expand Down
2 changes: 1 addition & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ exports.run = async () => { // eslint-disable-line complexity

let globs;
try {
globs = normalizeGlobs(conf.files, conf.sources, extensions.all);
globs = normalizeGlobs(conf.files, conf.helpers, conf.sources, extensions.all);
} catch (error) {
exit(error.message);
}
Expand Down
95 changes: 82 additions & 13 deletions lib/globs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ const normalizePatterns = patterns => {
});
};

function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
function normalizeGlobs(testPatterns, helperPatterns, 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 (typeof helperPatterns !== 'undefined' && (!Array.isArray(helperPatterns) || helperPatterns.length === 0)) {
throw new Error('The \'helpers\' 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.');
}
Expand All @@ -58,6 +62,12 @@ function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
testPatterns = defaultTestPatterns;
}

if (helperPatterns) {
helperPatterns = normalizePatterns(helperPatterns);
} else {
helperPatterns = [];
}

const defaultSourcePatterns = [
'**/*.snap',
'ava.config.js',
Expand All @@ -75,11 +85,13 @@ function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
sourcePatterns = defaultSourcePatterns;
}

return {extensions, testPatterns, sourcePatterns};
return {extensions, testPatterns, helperPatterns, sourcePatterns};
}

exports.normalizeGlobs = normalizeGlobs;

const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));

const findFiles = async (cwd, patterns) => {
const files = await globby(patterns, {
absolute: true,
Expand Down Expand Up @@ -108,26 +120,60 @@ const findFiles = async (cwd, patterns) => {
return files;
};

async function findHelpersAndTests({cwd, extensions, testPatterns}) {
const helpers = [];
async function findHelpersAndTests({cwd, extensions, testPatterns, helperPatterns}) {
// Search for tests concurrently with finding helpers.
const findingTests = findFiles(cwd, testPatterns);

const uniqueHelpers = new Set();
if (helperPatterns.length > 0) {
for (const file of await findFiles(cwd, helperPatterns)) {
if (!hasExtension(extensions, file)) {
continue;
}

uniqueHelpers.add(file);
}
}

const tests = [];
for (const file of await findFiles(cwd, testPatterns)) {
if (!extensions.includes(path.extname(file).slice(1))) {
for (const file of await findingTests) {
if (!hasExtension(extensions, file)) {
continue;
}

if (path.basename(file).startsWith('_')) {
helpers.push(file);
} else {
uniqueHelpers.add(file);
} else if (!uniqueHelpers.has(file)) { // Helpers cannot be tests.
tests.push(file);
}
}

return {helpers, tests};
return {helpers: [...uniqueHelpers], tests};
}

exports.findHelpersAndTests = findHelpersAndTests;

async function findTests({cwd, extensions, testPatterns, helperPatterns}) {
const rejectHelpers = helperPatterns.length > 0;

const tests = [];
for (const file of await findFiles(cwd, testPatterns)) {
if (!hasExtension(extensions, file) || path.basename(file).startsWith('_')) {
continue;
}

if (rejectHelpers && matches(file, helperPatterns)) {
continue;
}

tests.push(file);
}

return {tests};
}

exports.findTests = findTests;

function getChokidarPatterns({sourcePatterns, testPatterns}) {
const paths = [];
const ignored = defaultIgnorePatterns.map(pattern => `${pattern}/**/*`);
Expand Down Expand Up @@ -175,10 +221,33 @@ const matches = (file, patterns) => {
return micromatch.some(file, patterns, {ignore});
};

function classify(file, {testPatterns, sourcePatterns}) {
const isHelper = path.basename(file).startsWith('_');
const isTest = !isHelper && matches(file, testPatterns);
const isSource = !isHelper && !isTest && matches(file, sourcePatterns);
const NOT_IGNORED = ['**/*'];

function classify(file, {extensions, helperPatterns, testPatterns, sourcePatterns}) {
let isHelper = false;
let isTest = false;
let isSource = false;

if (hasExtension(extensions, file)) {
if (path.basename(file).startsWith('_')) {
isHelper = matches(file, NOT_IGNORED);
} else {
isHelper = helperPatterns.length > 0 && matches(file, helperPatterns);

if (!isHelper) {
isTest = testPatterns.length > 0 && matches(file, testPatterns);

if (!isTest) {
// Note: Don't check sourcePatterns.length since we still need to
// check the file against the default ignore patterns.
isSource = matches(file, sourcePatterns);
}
}
}
} else {
isSource = matches(file, sourcePatterns);
}

return {isHelper, isTest, isSource};
}

Expand Down
21 changes: 10 additions & 11 deletions test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +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.globs = normalizeGlobs(options.files, options.helpers, options.sources, options.extensions.all);
options.projectDir = options.projectDir || ROOT_DIR;
options.resolveTestsFrom = options.resolveTestsFrom || options.projectDir;
const instance = new Api(options);
Expand Down Expand Up @@ -579,16 +579,15 @@ test('test file in node_modules is ignored', t => {
});
});

// 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('test file in helpers is ignored', t => {
t.plan(1);

const api = apiCreator({helpers: ['**/helpers/*'], projectDir: path.join(__dirname, 'fixture/ignored-dirs')});
return api.run()
.then(runStatus => {
t.is(runStatus.stats.declaredTests, 1);
});
});

test('Node.js-style --require CLI argument', t => {
const requirePath = './' + path.relative('.', path.join(__dirname, 'fixture/install-global.js')).replace(/\\/g, '/');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Empty
5 changes: 0 additions & 5 deletions test/fixture/ignored-dirs/fixtures/test.js

This file was deleted.

3 changes: 3 additions & 0 deletions test/fixture/ignored-dirs/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import test from '../../..';

test('pass', t => t.pass());
5 changes: 5 additions & 0 deletions test/fixture/invalid-globs/helpers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ava": {
"helpers": []
}
}
Loading

0 comments on commit 4f42295

Please sign in to comment.