Skip to content

Commit

Permalink
Add use-t-throws-async-well rule (#278)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
stroncium and sindresorhus committed Mar 8, 2020
1 parent d9f9a49 commit eea71c3
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 0 deletions.
29 changes: 29 additions & 0 deletions docs/rules/use-t-throws-async-well.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited

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.

This rule is fixable inside `async` functions. It will insert `await` before `t.throwsAsync()` and `t.notThrowsAsync()`.

## Fail

```js
import test from 'ava';

test('main', t => {
t.throwsAsync(somePromise);
t.notThrowsAsync(somePromise);
});
```

## Pass

```js
import test from 'ava';

test('main', t => {
await t.throwsAsync(somePromise);
await t.notThrowsAsync(somePromise);
const p = t.throwsAsync(somePromise);
t.throwsAsync(somePromise).then(…);
});
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module.exports = {
'ava/test-title-format': 'off',
'ava/use-t-well': 'error',
'ava/use-t': 'error',
'ava/use-t-throws-async-well': 'error',
'ava/use-test': 'error',
'ava/use-true-false': 'error'
}
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Configure it in `package.json`.
"ava/test-title": "error",
"ava/test-title-format": "off",
"ava/use-t": "error",
"ava/use-t-throws-async-well": "error",
"ava/use-t-well": "error",
"ava/use-test": "error",
"ava/use-true-false": "error"
Expand Down Expand Up @@ -105,6 +106,7 @@ The rules will only activate in test files.
- [test-title](docs/rules/test-title.md) - Ensure tests have a title.
- [test-title-format](docs/rules/test-title-format.md) - Ensure test titles have a certain format.
- [use-t](docs/rules/use-t.md) - Ensure test functions use `t` as their parameter.
- [use-t-throws-async-well](docs/rules/use-t-throws-async-well.md) - Ensure that `t.throwsAsync()` and `t.notThrowsAsync()` are awaited. *(partly fixable)*
- [use-t-well](docs/rules/use-t-well.md) - Prevent the incorrect use of `t`. *(partly fixable)*
- [use-test](docs/rules/use-test.md) - Ensure that AVA is imported with `test` as the variable name.
- [use-true-false](docs/rules/use-true-false.md) - Ensure that `t.true()`/`t.false()` are used instead of `t.truthy()`/`t.falsy()`.
Expand Down
47 changes: 47 additions & 0 deletions rules/use-t-throws-async-well.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';
const {visitIf} = require('enhance-visitors');
const util = require('../util');
const createAvaRule = require('../create-ava-rule');

const create = context => {
const ava = createAvaRule();

return ava.merge({
CallExpression: visitIf([
ava.isInTestFile,
ava.isInTestNode
])(node => {
if (
node.parent.type === 'ExpressionStatement' &&
node.callee.type === 'MemberExpression' &&
(node.callee.property.name === 'throwsAsync' || node.callee.property.name === 'notThrowsAsync') &&
node.callee.object.name === 't'
) {
const message = `Use \`await\` with \`t.${node.callee.property.name}()\`.`;
if (ava.isInTestNode().arguments[0].async) {
context.report({
node,
message,
fix: fixer => fixer.replaceText(node.callee, `await ${context.getSourceCode().getText(node.callee)}`)
});
} else {
context.report({
node,
message
});
}
}
})
});
};

module.exports = {
create,
meta: {
docs: {
url: util.getDocsUrl(__filename)
},
fixable: 'code',
type: 'problem'
}
};
77 changes: 77 additions & 0 deletions test/use-t-throws-async-well.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import test from 'ava';
import avaRuleTester from 'eslint-ava-rule-tester';
import rule from '../rules/use-t-throws-async-well';

const ruleTester = avaRuleTester(test, {
parserOptions: {
ecmaVersion: 2020
}
});

const header = 'const test = require(\'ava\');\n';

function asyncTestCase(contents, prependHeader) {
const content = `test(async t => { ${contents} });`;

if (prependHeader !== false) {
return header + content;
}

return content;
}

function syncTestCase(contents, prependHeader) {
const content = `test(t => { ${contents} });`;

if (prependHeader !== false) {
return header + content;
}

return content;
}

ruleTester.run('use-t-throws-async-well', rule, {
valid: [
asyncTestCase('await t.throwsAsync(f)'),
asyncTestCase('await t.notThrowsAsync(f)'),
asyncTestCase('t.throws(f)'),
asyncTestCase('t.notThrows(f)'),
asyncTestCase('f(t.throwsAsync(f))'),
asyncTestCase('let p = t.throwsAsync(f)'),
asyncTestCase('p = t.throwsAsync(f)'),
asyncTestCase('t.throwsAsync(f)', false), // Shouldn't be triggered since it's not a test file
syncTestCase('t.throwsAsync(f)', false) // Shouldn't be triggered since it's not a test file
],
invalid: [
{
code: syncTestCase('t.throwsAsync(f)'),
errors: [{
ruleId: 'use-t-throws-async-well',
message: 'Use `await` with `t.throwsAsync()`.'
}]
},
{
code: syncTestCase('t.notThrowsAsync(f)'),
errors: [{
ruleId: 'use-t-throws-async-well',
message: 'Use `await` with `t.notThrowsAsync()`.'
}]
},
{
code: asyncTestCase('t.throwsAsync(f)'),
output: asyncTestCase('await t.throwsAsync(f)'),
errors: [{
ruleId: 'use-t-throws-async-well',
message: 'Use `await` with `t.throwsAsync()`.'
}]
},
{
code: asyncTestCase('t.notThrowsAsync(f)'),
output: asyncTestCase('await t.notThrowsAsync(f)'),
errors: [{
ruleId: 'use-t-throws-async-well',
message: 'Use `await` with `t.notThrowsAsync()`.'
}]
}
]
});

0 comments on commit eea71c3

Please sign in to comment.