Skip to content

Commit ecc990b

Browse files
committed
Merge pull request #56 from sindresorhus/no-ignored-test-files
Add no-ignored-test-files rule (fixes #51)
2 parents 232048d + b0da32c commit ecc990b

File tree

7 files changed

+348
-1
lines changed

7 files changed

+348
-1
lines changed

docs/rules/no-ignored-test-files.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Ensure no tests are written in ignored files
2+
3+
When searching for tests, AVA ignores files contained in `node_modules` or folders named `fixtures` or `helpers`. By default, it will search in `test.js test-*.js test/**/*.js`, which you can override by specifying a path when launching AVA or in the [AVA configuration in the `package.json` file](https://github.com/sindresorhus/ava#configuration).
4+
5+
This rule will verify that files which create tests are in the searched files and not in ignored folders. It will consider the root of the project to be the closest folder containing a `package.json` file, and will not do anything if it can't find one. Test files in `node_modules` will not be linted as they are ignored by ESLint.
6+
7+
Note that this rule will not be able to warn correctly if you use AVA by specifying the files in the command line ( `ava "lib/**/*.test.js"` ). Prefer configuring AVA as described in the link above.
8+
9+
## Fail
10+
11+
```js
12+
// File: test/foo/fixtures/bar.js
13+
// Invalid because in `fixtures` folder
14+
import test from 'ava';
15+
16+
test('foo', t => {
17+
t.pass();
18+
});
19+
20+
// File: test/foo/helpers/bar.js
21+
// Invalid because in `helpers` folder
22+
import test from 'ava';
23+
24+
test('foo', t => {
25+
t.pass();
26+
});
27+
28+
// File: lib/foo.test.js
29+
// Invalid because not in the searched files
30+
import test from 'ava';
31+
32+
test('foo', t => {
33+
t.pass();
34+
});
35+
36+
// File: test.js
37+
// with { "files": ["lib/**/*.test.js", "utils/**/*.test.js"] }
38+
// in either `package.json` under 'ava key' or in the rule options
39+
// Invalid because not in the searched files
40+
import test from 'ava';
41+
42+
test('foo', t => {
43+
t.pass();
44+
});
45+
```
46+
47+
48+
## Pass
49+
50+
```js
51+
// File: test/foo/not-fixtures/bar.js
52+
import test from 'ava';
53+
54+
test('foo', t => {
55+
t.pass();
56+
});
57+
58+
// File: test/foo/not-helpers/bar.js
59+
import test from 'ava';
60+
61+
test('foo', t => {
62+
t.pass();
63+
});
64+
65+
// File: test.js
66+
import test from 'ava';
67+
68+
test('foo', t => {
69+
t.pass();
70+
});
71+
72+
// File: lib/foo.test.js
73+
// with { "files": ["lib/**/*.test.js", "utils/**/*.test.js"] }
74+
// in either `package.json` under 'ava key' or in the rule options
75+
import test from 'ava';
76+
77+
test('foo', t => {
78+
t.pass();
79+
});
80+
```
81+
82+
## Options
83+
84+
This rule supports the following options:
85+
86+
`files`: An array of strings representing the files glob that AVA will use to find test files. Overrides the default and the configuration found in the `package.json` file.
87+
88+
You can set the options like this:
89+
90+
```js
91+
"ava/no-ignored-test-files": [2, {"files": ["lib/**/*.test.js", "utils/**/*.test.js"]}]
92+
```

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
'max-asserts': require('./rules/max-asserts'),
66
'no-cb-test': require('./rules/no-cb-test'),
77
'no-identical-title': require('./rules/no-identical-title'),
8+
'no-ignored-test-files': require('./rules/no-ignored-test-files'),
89
'no-invalid-end': require('./rules/no-invalid-end'),
910
'no-only-test': require('./rules/no-only-test'),
1011
'no-skip-assert': require('./rules/no-skip-assert'),
@@ -31,6 +32,7 @@ module.exports = {
3132
'ava/max-asserts': [0, 5],
3233
'ava/no-cb-test': 0,
3334
'ava/no-identical-title': 2,
35+
'ava/no-ignored-test-files': 2,
3436
'ava/no-invalid-end': 2,
3537
'ava/no-only-test': 2,
3638
'ava/no-skip-assert': 2,

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@
6363
"dependencies": {
6464
"deep-strict-equal": "^0.1.0",
6565
"espurify": "^1.5.0",
66-
"object-assign": "^4.0.1"
66+
"multimatch": "^2.1.0",
67+
"object-assign": "^4.0.1",
68+
"pkg-up": "^1.0.0"
6769
},
6870
"devDependencies": {
6971
"ava": "*",

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Configure it in `package.json`.
3232
"ava/max-asserts": [2, 5],
3333
"ava/no-cb-test": 0,
3434
"ava/no-identical-title": 2,
35+
"ava/no-ignored-test-files": 2,
3536
"ava/no-invalid-end": 2,
3637
"ava/no-only-test": 2,
3738
"ava/no-skip-assert": 2,
@@ -57,6 +58,7 @@ The rules will only activate in test files.
5758
- [max-asserts](docs/rules/max-asserts.md) - Limit the number of assertions in a test.
5859
- [no-cb-test](docs/rules/no-cb-test.md) - Ensure no `test.cb()` is used.
5960
- [no-identical-title](docs/rules/no-identical-title.md) - Ensure no tests have the same title.
61+
- [no-ignored-test-files](docs/rules/no-ignored-test-files.md) - Ensure no tests are written in ignored files.
6062
- [no-invalid-end](docs/rules/no-invalid-end.md) - Ensure `t.end()` is only called inside `test.cb()`.
6163
- [no-only-test](docs/rules/no-only-test.md) - Ensure no `test.only()` are present.
6264
- [no-skip-assert](docs/rules/no-skip-assert.md) - Ensure no assertions are skipped.

rules/no-ignored-test-files.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
var path = require('path');
3+
var pkgUp = require('pkg-up');
4+
var multimatch = require('multimatch');
5+
var util = require('../util');
6+
var createAvaRule = require('../create-ava-rule');
7+
8+
var defaultFiles = [
9+
'test.js',
10+
'test-*.js',
11+
'test/**/*.js'
12+
];
13+
14+
var excludedFolders = [
15+
'**/fixtures/**',
16+
'**/helpers/**'
17+
];
18+
19+
function isIgnored(rootDir, files, filepath) {
20+
var relativeFilePath = path.relative(rootDir, filepath);
21+
if (multimatch([relativeFilePath], excludedFolders).length !== 0) {
22+
return 'Test file is ignored because it is in `' + excludedFolders.join(' ') + '`';
23+
}
24+
25+
if (multimatch([relativeFilePath], files).length === 0) {
26+
return 'Test file is ignored because it is not in `' + files.join(' ') + '`';
27+
}
28+
29+
return null;
30+
}
31+
32+
function getPackageInfo() {
33+
var packageFilePath = pkgUp.sync();
34+
return {
35+
rootDir: packageFilePath && path.dirname(packageFilePath),
36+
files: util.getAvaConfig(packageFilePath).files
37+
};
38+
}
39+
40+
/* eslint quote-props: [2, "as-needed"] */
41+
module.exports = function (context) {
42+
var ava = createAvaRule();
43+
var packageInfo = getPackageInfo();
44+
var options = context.options[0] || {};
45+
var files = options.files || packageInfo.files || defaultFiles;
46+
var hasTestCall = false;
47+
48+
if (!packageInfo.rootDir) {
49+
// Could not find a package.json folder
50+
return {};
51+
}
52+
53+
return ava.merge({
54+
CallExpression: function (node) {
55+
if (ava.isTestFile && ava.currentTestNode === node) {
56+
hasTestCall = true;
57+
}
58+
},
59+
'Program:exit': function (node) {
60+
if (!hasTestCall) {
61+
return;
62+
}
63+
64+
var ignoredReason = isIgnored(packageInfo.rootDir, files, context.getFilename());
65+
if (ignoredReason) {
66+
context.report(node, ignoredReason);
67+
}
68+
hasTestCall = false;
69+
}
70+
});
71+
};
72+
73+
module.exports.schema = [{
74+
type: 'object',
75+
properties: {
76+
files: {
77+
type: 'array'
78+
}
79+
}
80+
}];

test/no-ignored-test-files.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import path from 'path';
2+
import test from 'ava';
3+
import {RuleTester} from 'eslint';
4+
import util from '../util';
5+
import rule from '../rules/no-ignored-test-files';
6+
7+
const ruleTester = new RuleTester({
8+
env: {
9+
es6: true
10+
}
11+
});
12+
13+
const header = `const test = require('ava');\n`;
14+
const rootDir = path.dirname(process.cwd());
15+
16+
function toPath(subPath) {
17+
return path.join(rootDir, subPath);
18+
}
19+
20+
function code(hasHeader) {
21+
return (hasHeader ? header : '') + 'test(t => { t.pass(); });';
22+
}
23+
24+
test('without AVA config in package.json', () => {
25+
ruleTester.run('no-ignored-test-files', rule, {
26+
valid: [
27+
{
28+
code: code(true),
29+
filename: toPath('test/foo/bar.js')
30+
},
31+
{
32+
code: code(true),
33+
filename: toPath('test/foo/not-fixtures/bar.js')
34+
},
35+
{
36+
code: code(true),
37+
filename: toPath('test/foo/not-helpers/bar.js')
38+
},
39+
{
40+
code: header + 'foo(t => {});',
41+
filename: toPath('test/foo/fixtures/bar.js')
42+
},
43+
{
44+
code: header + 'foo(t => {});',
45+
filename: toPath('test/foo/helpers/bar.js')
46+
},
47+
{
48+
code: code(false),
49+
filename: toPath('test/foo/fixtures/bar.js')
50+
},
51+
{
52+
code: code(false),
53+
filename: toPath('test/foo/helpers/bar.js')
54+
},
55+
{
56+
code: code(true),
57+
filename: toPath('test.js')
58+
},
59+
{
60+
code: code(true),
61+
filename: toPath('test-foo.js')
62+
},
63+
{
64+
code: code(true),
65+
filename: toPath('lib/foo.test.js'),
66+
options: [{files: ['lib/**/*.test.js']}]
67+
}
68+
],
69+
invalid: [
70+
{
71+
code: code(true),
72+
filename: toPath('test/foo/fixtures/bar.js'),
73+
errors: [{message: 'Test file is ignored because it is in `**/fixtures/** **/helpers/**`'}]
74+
},
75+
{
76+
code: code(true),
77+
filename: toPath('test/foo/helpers/bar.js'),
78+
errors: [{message: 'Test file is ignored because it is in `**/fixtures/** **/helpers/**`'}]
79+
},
80+
{
81+
code: code(true),
82+
filename: toPath('lib/foo.test.js'),
83+
errors: [{message: 'Test file is ignored because it is not in `test.js test-*.js test/**/*.js`'}]
84+
},
85+
{
86+
code: code(true),
87+
filename: toPath('test/foo/bar.js'),
88+
options: [{files: ['lib/**/*.test.js']}],
89+
errors: [{message: 'Test file is ignored because it is not in `lib/**/*.test.js`'}]
90+
},
91+
{
92+
code: code(true),
93+
filename: toPath('lib/foo.not-test.js'),
94+
options: [{files: ['lib/**/*.test.js']}],
95+
errors: [{message: 'Test file is ignored because it is not in `lib/**/*.test.js`'}]
96+
}
97+
]
98+
});
99+
});
100+
101+
test('with AVA config in package.json', () => {
102+
const oldGetAvaConfig = util.getAvaConfig;
103+
104+
util.getAvaConfig = function mockGetAvaConfig() {
105+
return {
106+
files: ['lib/**/*.test.js']
107+
};
108+
};
109+
110+
ruleTester.run('no-ignored-test-files', rule, {
111+
valid: [
112+
{
113+
code: code(true),
114+
filename: toPath('lib/foo.test.js')
115+
},
116+
{
117+
code: code(true),
118+
filename: toPath('bar/foo.test.js'),
119+
options: [{files: ['bar/**/*.test.js']}]
120+
}
121+
],
122+
invalid: [
123+
{
124+
code: code(true),
125+
filename: toPath('lib/foo/fixtures/bar.test.js'),
126+
errors: [{message: 'Test file is ignored because it is in `**/fixtures/** **/helpers/**`'}]
127+
},
128+
{
129+
code: code(true),
130+
filename: toPath('lib/foo/helpers/bar.test.js'),
131+
errors: [{message: 'Test file is ignored because it is in `**/fixtures/** **/helpers/**`'}]
132+
},
133+
{
134+
code: code(true),
135+
filename: toPath('test.js'),
136+
errors: [{message: 'Test file is ignored because it is not in `lib/**/*.test.js`'}]
137+
},
138+
{
139+
code: code(true),
140+
filename: toPath('bar/foo.test.js'),
141+
errors: [{message: 'Test file is ignored because it is not in `lib/**/*.test.js`'}]
142+
},
143+
{
144+
code: code(true),
145+
filename: toPath('lib/foo.test.js'),
146+
options: [{files: ['bar/**/*.test.js']}],
147+
errors: [{message: 'Test file is ignored because it is not in `bar/**/*.test.js`'}]
148+
}
149+
]
150+
});
151+
152+
util.getAvaConfig = oldGetAvaConfig;
153+
});

util.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
'use strict';
22

3+
var fs = require('fs');
4+
35
exports.nameOfRootObject = function (node) {
46
if (node.object.type === 'MemberExpression') {
57
return exports.nameOfRootObject(node.object);
68
}
79

810
return node.object.name;
911
};
12+
13+
exports.getAvaConfig = function (filepath) {
14+
var defaultResult = {};
15+
if (!filepath) {
16+
return defaultResult;
17+
}
18+
19+
try {
20+
var packageContent = JSON.parse(fs.readFileSync(filepath, 'utf8'));
21+
return packageContent && packageContent.ava || defaultResult;
22+
} catch (e) {
23+
return defaultResult;
24+
}
25+
};

0 commit comments

Comments
 (0)