Skip to content

Commit eea71c3

Browse files
Add use-t-throws-async-well rule (#278)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent d9f9a49 commit eea71c3

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

docs/rules/use-t-throws-async-well.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited
2+
3+
When you use the `t.throwsAsync()` and `t.notThrowsAsync()` assertions, you must await the promise they return. If the test function completes before the assertions do, the test will fail.
4+
5+
This rule is fixable inside `async` functions. It will insert `await` before `t.throwsAsync()` and `t.notThrowsAsync()`.
6+
7+
## Fail
8+
9+
```js
10+
import test from 'ava';
11+
12+
test('main', t => {
13+
t.throwsAsync(somePromise);
14+
t.notThrowsAsync(somePromise);
15+
});
16+
```
17+
18+
## Pass
19+
20+
```js
21+
import test from 'ava';
22+
23+
test('main', t => {
24+
await t.throwsAsync(somePromise);
25+
await t.notThrowsAsync(somePromise);
26+
const p = t.throwsAsync(somePromise);
27+
t.throwsAsync(somePromise).then(…);
28+
});
29+
```

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module.exports = {
4848
'ava/test-title-format': 'off',
4949
'ava/use-t-well': 'error',
5050
'ava/use-t': 'error',
51+
'ava/use-t-throws-async-well': 'error',
5152
'ava/use-test': 'error',
5253
'ava/use-true-false': 'error'
5354
}

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Configure it in `package.json`.
6565
"ava/test-title": "error",
6666
"ava/test-title-format": "off",
6767
"ava/use-t": "error",
68+
"ava/use-t-throws-async-well": "error",
6869
"ava/use-t-well": "error",
6970
"ava/use-test": "error",
7071
"ava/use-true-false": "error"
@@ -105,6 +106,7 @@ The rules will only activate in test files.
105106
- [test-title](docs/rules/test-title.md) - Ensure tests have a title.
106107
- [test-title-format](docs/rules/test-title-format.md) - Ensure test titles have a certain format.
107108
- [use-t](docs/rules/use-t.md) - Ensure test functions use `t` as their parameter.
109+
- [use-t-throws-async-well](docs/rules/use-t-throws-async-well.md) - Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited. *(partly fixable)*
108110
- [use-t-well](docs/rules/use-t-well.md) - Prevent the incorrect use of `t`. *(partly fixable)*
109111
- [use-test](docs/rules/use-test.md) - Ensure that AVA is imported with `test` as the variable name.
110112
- [use-true-false](docs/rules/use-true-false.md) - Ensure that `t.true()`/`t.false()` are used instead of `t.truthy()`/`t.falsy()`.

rules/use-t-throws-async-well.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
const {visitIf} = require('enhance-visitors');
3+
const util = require('../util');
4+
const createAvaRule = require('../create-ava-rule');
5+
6+
const create = context => {
7+
const ava = createAvaRule();
8+
9+
return ava.merge({
10+
CallExpression: visitIf([
11+
ava.isInTestFile,
12+
ava.isInTestNode
13+
])(node => {
14+
if (
15+
node.parent.type === 'ExpressionStatement' &&
16+
node.callee.type === 'MemberExpression' &&
17+
(node.callee.property.name === 'throwsAsync' || node.callee.property.name === 'notThrowsAsync') &&
18+
node.callee.object.name === 't'
19+
) {
20+
const message = `Use \`await\` with \`t.${node.callee.property.name}()\`.`;
21+
if (ava.isInTestNode().arguments[0].async) {
22+
context.report({
23+
node,
24+
message,
25+
fix: fixer => fixer.replaceText(node.callee, `await ${context.getSourceCode().getText(node.callee)}`)
26+
});
27+
} else {
28+
context.report({
29+
node,
30+
message
31+
});
32+
}
33+
}
34+
})
35+
});
36+
};
37+
38+
module.exports = {
39+
create,
40+
meta: {
41+
docs: {
42+
url: util.getDocsUrl(__filename)
43+
},
44+
fixable: 'code',
45+
type: 'problem'
46+
}
47+
};

test/use-t-throws-async-well.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import test from 'ava';
2+
import avaRuleTester from 'eslint-ava-rule-tester';
3+
import rule from '../rules/use-t-throws-async-well';
4+
5+
const ruleTester = avaRuleTester(test, {
6+
parserOptions: {
7+
ecmaVersion: 2020
8+
}
9+
});
10+
11+
const header = 'const test = require(\'ava\');\n';
12+
13+
function asyncTestCase(contents, prependHeader) {
14+
const content = `test(async t => { ${contents} });`;
15+
16+
if (prependHeader !== false) {
17+
return header + content;
18+
}
19+
20+
return content;
21+
}
22+
23+
function syncTestCase(contents, prependHeader) {
24+
const content = `test(t => { ${contents} });`;
25+
26+
if (prependHeader !== false) {
27+
return header + content;
28+
}
29+
30+
return content;
31+
}
32+
33+
ruleTester.run('use-t-throws-async-well', rule, {
34+
valid: [
35+
asyncTestCase('await t.throwsAsync(f)'),
36+
asyncTestCase('await t.notThrowsAsync(f)'),
37+
asyncTestCase('t.throws(f)'),
38+
asyncTestCase('t.notThrows(f)'),
39+
asyncTestCase('f(t.throwsAsync(f))'),
40+
asyncTestCase('let p = t.throwsAsync(f)'),
41+
asyncTestCase('p = t.throwsAsync(f)'),
42+
asyncTestCase('t.throwsAsync(f)', false), // Shouldn't be triggered since it's not a test file
43+
syncTestCase('t.throwsAsync(f)', false) // Shouldn't be triggered since it's not a test file
44+
],
45+
invalid: [
46+
{
47+
code: syncTestCase('t.throwsAsync(f)'),
48+
errors: [{
49+
ruleId: 'use-t-throws-async-well',
50+
message: 'Use `await` with `t.throwsAsync()`.'
51+
}]
52+
},
53+
{
54+
code: syncTestCase('t.notThrowsAsync(f)'),
55+
errors: [{
56+
ruleId: 'use-t-throws-async-well',
57+
message: 'Use `await` with `t.notThrowsAsync()`.'
58+
}]
59+
},
60+
{
61+
code: asyncTestCase('t.throwsAsync(f)'),
62+
output: asyncTestCase('await t.throwsAsync(f)'),
63+
errors: [{
64+
ruleId: 'use-t-throws-async-well',
65+
message: 'Use `await` with `t.throwsAsync()`.'
66+
}]
67+
},
68+
{
69+
code: asyncTestCase('t.notThrowsAsync(f)'),
70+
output: asyncTestCase('await t.notThrowsAsync(f)'),
71+
errors: [{
72+
ruleId: 'use-t-throws-async-well',
73+
message: 'Use `await` with `t.notThrowsAsync()`.'
74+
}]
75+
}
76+
]
77+
});

0 commit comments

Comments
 (0)