Skip to content

Commit 55ab2c1

Browse files
committed
Support helper glob configuration
Fixes #2105.
1 parent d1c8a71 commit 55ab2c1

File tree

13 files changed

+238
-48
lines changed

13 files changed

+238
-48
lines changed

docs/06-configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ To ignore files, prefix the pattern with an `!` (exclamation mark).
1616
"!test/exclude-files-in-this-directory",
1717
"!**/exclude-files-with-this-name.*"
1818
],
19+
"helpers": [
20+
"**/helpers/**/*"
21+
],
1922
"sources": [
2023
"src/**/*"
2124
],
@@ -48,6 +51,7 @@ Arguments passed to the CLI will always take precedence over the CLI options con
4851
## Options
4952

5053
- `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
54+
- `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
5155
- `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)
5256
- `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)
5357
- `cache`: cache compiled test and helper files under `node_modules/.cache/ava`. If `false`, files are cached in a temporary directory instead

lib/api.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,17 @@ class Api extends Emittery {
110110
const precompiler = await this._setupPrecompiler();
111111
let helpers = [];
112112
if (files.length === 0 || precompiler.enabled) {
113-
const found = await globs.findHelpersAndTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
113+
let found;
114+
if (precompiler.enabled) {
115+
found = await globs.findHelpersAndTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
116+
helpers = found.helpers;
117+
} else {
118+
found = await globs.findTests({cwd: this.options.resolveTestsFrom, ...apiOptions.globs});
119+
}
120+
114121
if (files.length === 0) {
115122
({tests: files} = found);
116123
}
117-
118-
({helpers} = found);
119124
}
120125

121126
if (this.options.parallelRuns) {

lib/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ exports.run = async () => { // eslint-disable-line complexity
180180

181181
let globs;
182182
try {
183-
globs = normalizeGlobs(conf.files, conf.sources, extensions.all);
183+
globs = normalizeGlobs(conf.files, conf.helpers, conf.sources, extensions.all);
184184
} catch (error) {
185185
exit(error.message);
186186
}

lib/globs.js

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ const normalizePatterns = patterns => {
2828
});
2929
};
3030

31-
function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
31+
function normalizeGlobs(testPatterns, helperPatterns, sourcePatterns, extensions) {
3232
if (typeof testPatterns !== 'undefined' && (!Array.isArray(testPatterns) || testPatterns.length === 0)) {
3333
throw new Error('The \'files\' configuration must be an array containing glob patterns.');
3434
}
3535

36+
if (typeof helperPatterns !== 'undefined' && (!Array.isArray(helperPatterns) || helperPatterns.length === 0)) {
37+
throw new Error('The \'helpers\' configuration must be an array containing glob patterns.');
38+
}
39+
3640
if (sourcePatterns && (!Array.isArray(sourcePatterns) || sourcePatterns.length === 0)) {
3741
throw new Error('The \'sources\' configuration must be an array containing glob patterns.');
3842
}
@@ -58,6 +62,12 @@ function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
5862
testPatterns = defaultTestPatterns;
5963
}
6064

65+
if (helperPatterns) {
66+
helperPatterns = normalizePatterns(helperPatterns);
67+
} else {
68+
helperPatterns = [];
69+
}
70+
6171
const defaultSourcePatterns = [
6272
'**/*.snap',
6373
'ava.config.js',
@@ -75,11 +85,13 @@ function normalizeGlobs(testPatterns, sourcePatterns, extensions) {
7585
sourcePatterns = defaultSourcePatterns;
7686
}
7787

78-
return {extensions, testPatterns, sourcePatterns};
88+
return {extensions, testPatterns, helperPatterns, sourcePatterns};
7989
}
8090

8191
exports.normalizeGlobs = normalizeGlobs;
8292

93+
const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
94+
8395
const findFiles = async (cwd, patterns) => {
8496
const files = await globby(patterns, {
8597
absolute: true,
@@ -108,26 +120,60 @@ const findFiles = async (cwd, patterns) => {
108120
return files;
109121
};
110122

111-
async function findHelpersAndTests({cwd, extensions, testPatterns}) {
112-
const helpers = [];
123+
async function findHelpersAndTests({cwd, extensions, testPatterns, helperPatterns}) {
124+
// Search for tests concurrently with finding helpers.
125+
const findingTests = findFiles(cwd, testPatterns);
126+
127+
const uniqueHelpers = new Set();
128+
if (helperPatterns.length > 0) {
129+
for (const file of await findFiles(cwd, helperPatterns)) {
130+
if (!hasExtension(extensions, file)) {
131+
continue;
132+
}
133+
134+
uniqueHelpers.add(file);
135+
}
136+
}
137+
113138
const tests = [];
114-
for (const file of await findFiles(cwd, testPatterns)) {
115-
if (!extensions.includes(path.extname(file).slice(1))) {
139+
for (const file of await findingTests) {
140+
if (!hasExtension(extensions, file)) {
116141
continue;
117142
}
118143

119144
if (path.basename(file).startsWith('_')) {
120-
helpers.push(file);
121-
} else {
145+
uniqueHelpers.add(file);
146+
} else if (!uniqueHelpers.has(file)) { // Helpers cannot be tests.
122147
tests.push(file);
123148
}
124149
}
125150

126-
return {helpers, tests};
151+
return {helpers: [...uniqueHelpers], tests};
127152
}
128153

129154
exports.findHelpersAndTests = findHelpersAndTests;
130155

156+
async function findTests({cwd, extensions, testPatterns, helperPatterns}) {
157+
const rejectHelpers = helperPatterns.length > 0;
158+
159+
const tests = [];
160+
for (const file of await findFiles(cwd, testPatterns)) {
161+
if (!hasExtension(extensions, file) || path.basename(file).startsWith('_')) {
162+
continue;
163+
}
164+
165+
if (rejectHelpers && matches(file, helperPatterns)) {
166+
continue;
167+
}
168+
169+
tests.push(file);
170+
}
171+
172+
return {tests};
173+
}
174+
175+
exports.findTests = findTests;
176+
131177
function getChokidarPatterns({sourcePatterns, testPatterns}) {
132178
const paths = [];
133179
const ignored = defaultIgnorePatterns.map(pattern => `${pattern}/**/*`);
@@ -175,10 +221,33 @@ const matches = (file, patterns) => {
175221
return micromatch.some(file, patterns, {ignore});
176222
};
177223

178-
function classify(file, {testPatterns, sourcePatterns}) {
179-
const isHelper = path.basename(file).startsWith('_');
180-
const isTest = !isHelper && matches(file, testPatterns);
181-
const isSource = !isHelper && !isTest && matches(file, sourcePatterns);
224+
const NOT_IGNORED = ['**/*'];
225+
226+
function classify(file, {extensions, helperPatterns, testPatterns, sourcePatterns}) {
227+
let isHelper = false;
228+
let isTest = false;
229+
let isSource = false;
230+
231+
if (hasExtension(extensions, file)) {
232+
if (path.basename(file).startsWith('_')) {
233+
isHelper = matches(file, NOT_IGNORED);
234+
} else {
235+
isHelper = helperPatterns.length > 0 && matches(file, helperPatterns);
236+
237+
if (!isHelper) {
238+
isTest = testPatterns.length > 0 && matches(file, testPatterns);
239+
240+
if (!isTest) {
241+
// Note: Don't check sourcePatterns.length since we still need to
242+
// check the file against the default ignore patterns.
243+
isSource = matches(file, sourcePatterns);
244+
}
245+
}
246+
}
247+
} else {
248+
isSource = matches(file, sourcePatterns);
249+
}
250+
182251
return {isHelper, isTest, isSource};
183252
}
184253

test/api.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function apiCreator(options = {}) {
3232
options.babelConfig = babelPipeline.validate(options.babelConfig);
3333
options.concurrency = 2;
3434
options.extensions = options.extensions || {all: ['js'], enhancementsOnly: [], full: ['js']};
35-
options.globs = normalizeGlobs(options.files, options.sources, options.extensions.all);
35+
options.globs = normalizeGlobs(options.files, options.helpers, options.sources, options.extensions.all);
3636
options.projectDir = options.projectDir || ROOT_DIR;
3737
options.resolveTestsFrom = options.resolveTestsFrom || options.projectDir;
3838
const instance = new Api(options);
@@ -579,16 +579,15 @@ test('test file in node_modules is ignored', t => {
579579
});
580580
});
581581

582-
// TODO: Re-enable to test helpers patterns.
583-
// test('test file in helpers is ignored', t => {
584-
// t.plan(1);
585-
//
586-
// const api = apiCreator();
587-
// return api.run([path.join(__dirname, 'fixture/ignored-dirs/helpers/test.js')])
588-
// .then(runStatus => {
589-
// t.is(runStatus.stats.declaredTests, 0);
590-
// });
591-
// });
582+
test('test file in helpers is ignored', t => {
583+
t.plan(1);
584+
585+
const api = apiCreator({helpers: ['**/helpers/*'], projectDir: path.join(__dirname, 'fixture/ignored-dirs')});
586+
return api.run()
587+
.then(runStatus => {
588+
t.is(runStatus.stats.declaredTests, 1);
589+
});
590+
});
592591

593592
test('Node.js-style --require CLI argument', t => {
594593
const requirePath = './' + path.relative('.', path.join(__dirname, 'fixture/install-global.js')).replace(/\\/g, '/');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// Empty

test/fixture/ignored-dirs/fixtures/test.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

test/fixture/ignored-dirs/test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import test from '../../..';
2+
3+
test('pass', t => t.pass());
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"ava": {
3+
"helpers": []
4+
}
5+
}

0 commit comments

Comments
 (0)