Repository: avajs/eslint-plugin-ava (js)

Issue:
Rule proposal: Enforce hooks to be at the top of the test file
<!-- Issuehunt Badges -->

[<img alt="Issuehunt badges" src="https://issuehunt.io/r/avajs/eslint-plugin-ava/issues/196/badge.svg" />](https://issuehunt.io/r/avajs/eslint-plugin-ava/issues/196)

<!-- /Issuehunt Badges -->

It's useful to be able to see the hooks first for readability.

Proposed order: `before`, `after`, `beforeEach`, `afterEach`, `test`

Thoughts?
<!-- Issuehunt content -->



> There is a $60.00 open bounty on this issue. Add more on [Issuehunt](https://issuehunt.io/r/avajs/eslint-plugin-ava/issues/196).
> - Checkout the [Issuehunt explorer](https://issuehunt.io/r/avajs/eslint-plugin-ava/) to discover more funded issues.
> - Need some help from other developers? [Add your repositories](https://issuehunt.io/r/new) on Issuehunt to raise funds.


<!-- /Issuehunt content-->

Pull request description:
Add `hooks-order` rule
Closes #196.


Patch:
diff --git a/docs/rules/hooks-order.md b/docs/rules/hooks-order.md
new file mode 100644
index 0000000..670cb66
--- /dev/null
+++ b/docs/rules/hooks-order.md
@@ -0,0 +1,79 @@
+# Enforce test hook ordering
+
+Hooks should be placed before any tests and in the proper semantic order:
+
+- `test.before(…);`
+- `test.after(…);`
+- `test.after.always(…);`
+- `test.beforeEach(…);`
+- `test.afterEach(…);`
+- `test.afterEach.always(…);`
+- `test(…);`
+
+This rule is fixable as long as no other code is between the hooks that need to be reordered.
+
+
+## Fail
+
+```js
+import test from 'ava';
+
+test.after(t => {
+	doFoo();
+});
+
+test.before(t => {
+	doFoo();
+});
+
+test('foo', t => {
+	t.true(true);
+});
+```
+
+```js
+import test from 'ava';
+
+test('foo', t => {
+	t.true(true);
+});
+
+test.before(t => {
+	doFoo();
+});
+```
+
+
+## Pass
+
+```js
+import test from 'ava';
+
+test.before(t => {
+	doFoo();
+});
+
+test.after(t => {
+	doFoo();
+});
+
+test.after.always(t => {
+	doFoo();
+});
+
+test.beforeEach(t => {
+	doFoo();
+});
+
+test.afterEach(t => {
+	doFoo();
+});
+
+test.afterEach.always(t => {
+	doFoo();
+});
+
+test('foo', t => {
+	t.true(true);
+});
+```
diff --git a/docs/rules/no-inline-assertions.md b/docs/rules/no-inline-assertions.md
new file mode 100644
index 0000000..2b09210
--- /dev/null
+++ b/docs/rules/no-inline-assertions.md
@@ -0,0 +1,25 @@
+# Ensure assertions are not called from inline arrow functions
+
+The test implementation should not purely consist of an inline assertion as assertions do not return a value and having them inline also makes the tests less readable.
+
+This rule is fixable. It will wrap the assertion in braces `{}`. It will not do any whitespace or style changes.
+
+
+## Fail
+
+```js
+import test from 'ava';
+
+test('foo', t => t.true(fn()));
+```
+
+
+## Pass
+
+```js
+import test from 'ava';
+
+test('foo', t => {
+	t.true(fn());
+});
+```
diff --git a/docs/rules/prefer-t-regex.md b/docs/rules/prefer-t-regex.md
index af3d318..5f98ec4 100644
--- a/docs/rules/prefer-t-regex.md
+++ b/docs/rules/prefer-t-regex.md
@@ -1,5 +1,7 @@
 # Prefer using `t.regex()` to test regular expressions
 
+Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/related/eslint-plugin-ava/docs/rules/prefer-t-regex.md)
+
 The AVA [`t.regex()` assertion](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#regexcontents-regex-message) can test a string against a regular expression.
 
 This rule will enforce the use of `t.regex()` instead of manually using `RegExp#test()`, which will make your code look clearer and produce better failure output.
diff --git a/index.js b/index.js
index 2cd3aec..ef5418f 100644
--- a/index.js
+++ b/index.js
@@ -18,6 +18,7 @@ module.exports = {
 			],
 			rules: {
 				'ava/assertion-arguments': 'error',
+				'ava/hooks-order': 'error',
 				'ava/max-asserts': [
 					'off',
 					5
@@ -28,6 +29,7 @@ module.exports = {
 				'ava/no-identical-title': 'error',
 				'ava/no-ignored-test-files': 'error',
 				'ava/no-import-test-files': 'error',
+				'ava/no-inline-assertions': 'error',
 				'ava/no-invalid-end': 'error',
 				'ava/no-nested-tests': 'error',
 				'ava/no-only-test': 'error',
diff --git a/package.json b/package.json
index 5080e30..9bd7010 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
 	"dependencies": {
 		"deep-strict-equal": "^0.2.0",
 		"enhance-visitors": "^1.0.0",
-		"espree": "^5.0.0",
+		"espree": "^6.0.0",
 		"espurify": "^2.0.0",
 		"import-modules": "^1.1.0",
 		"pkg-dir": "^4.2.0",
@@ -41,16 +41,17 @@
 		"ava": "^2.0.0",
 		"babel-eslint": "^10.0.1",
 		"codecov": "^3.5.0",
-		"eslint": "^5.12.0",
+		"eslint": "^6.0.0",
 		"eslint-ava-rule-tester": "^3.0.0",
 		"eslint-plugin-eslint-plugin": "2.1.0",
 		"js-combinatorics": "^0.5.4",
 		"nyc": "^14.1.1",
+		"outdent": "^0.7.0",
 		"pify": "^4.0.1",
 		"xo": "^0.24.0"
 	},
 	"peerDependencies": {
-		"eslint": ">=5.9.0"
+		"eslint": ">=6"
 	},
 	"ava": {
 		"files": [
diff --git a/readme.md b/readme.md
index 24813d1..74397b0 100644
--- a/readme.md
+++ b/readme.md
@@ -36,6 +36,7 @@ Configure it in `package.json`.
 		],
 		"rules": {
 			"ava/assertion-arguments": "error",
+			"ava/hooks-order": "error",
 			"ava/max-asserts": [
 				"off",
 				5
@@ -46,6 +47,7 @@ Configure it in `package.json`.
 			"ava/no-identical-title": "error",
 			"ava/no-ignored-test-files": "error",
 			"ava/no-import-test-files": "error",
+			"ava/no-inline-assertions": "error",
 			"ava/no-invalid-end": "error",
 			"ava/no-nested-tests": "error",
 			"ava/no-only-test": "error",
@@ -76,6 +78,7 @@ Configure it in `package.json`.
 The rules will only activate in test files.
 
 - [assertion-arguments](docs/rules/assertion-arguments.md) - Enforce passing correct arguments to assertions.
+- [hooks-order](docs/rules/hooks-order.md) - Enforce test hook ordering. *(fixable)*
 - [max-asserts](docs/rules/max-asserts.md) - Limit the number of assertions in a test.
 - [no-async-fn-without-await](docs/rules/no-async-fn-without-await.md) - Ensure that async tests use `await`.
 - [no-cb-test](docs/rules/no-cb-test.md) - Ensure no `test.cb()` is used.
@@ -83,6 +86,7 @@ The rules will only activate in test files.
 - [no-identical-title](docs/rules/no-identical-title.md) - Ensure no tests have the same title.
 - [no-ignored-test-files](docs/rules/no-ignored-test-files.md) - Ensure no tests are written in ignored files.
 - [no-import-test-files](docs/rules/no-import-test-files.md) - Ensure no test files are imported anywhere.
+- [no-inline-assertions](docs/rules/no-inline-assertions.md) - Ensure assertions are not called from inline arrow functions. *(fixable)*
 - [no-invalid-end](docs/rules/no-invalid-end.md) - Ensure `t.end()` is only called inside `test.cb()`.
 - [no-nested-tests](docs/rules/no-nested-tests.md) - Ensure no tests are nested.
 - [no-only-test](docs/rules/no-only-test.md) - Ensure no `test.only()` are present. *(fixable)*
diff --git a/rules/hooks-order.js b/rules/hooks-order.js
new file mode 100644
index 0000000..b421a48
--- /dev/null
+++ b/rules/hooks-order.js
@@ -0,0 +1,158 @@
+'use strict';
+const {visitIf} = require('enhance-visitors');
+const createAvaRule = require('../create-ava-rule');
+const util = require('../util');
+
+const MESSAGE_ID = 'hooks-order';
+
+const buildOrders = names => {
+	const orders = {};
+	for (const nameLater of names) {
+		for (const nameEarlier in orders) {
+			if (orders[nameEarlier]) {
+				orders[nameEarlier].push(nameLater);
+			}
+		}
+
+		orders[nameLater] = [];
+	}
+
+	return orders;
+};
+
+const buildMessage = (name, orders, visited) => {
+	const checks = orders[name] || [];
+
+	for (const check of checks) {
+		const nodeEarlier = visited[check];
+		if (nodeEarlier) {
+			return {
+				messageId: MESSAGE_ID,
+				data: {
+					current: name,
+					invalid: check
+				},
+				node: nodeEarlier
+			};
+		}
+	}
+
+	return null;
+};
+
+const create = context => {
+	const ava = createAvaRule();
+
+	const orders = buildOrders([
+		'before',
+		'after',
+		'after.always',
+		'beforeEach',
+		'afterEach',
+		'afterEach.always',
+		'test'
+	]);
+
+	const visited = {};
+
+	const checks = [
+		{
+			selector: 'CallExpression[callee.object.name="test"][callee.property.name="before"]',
+			name: 'before'
+		},
+		{
+			selector: 'CallExpression[callee.object.name="test"][callee.property.name="after"]',
+			name: 'after'
+		},
+		{
+			selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="after"][callee.property.name="always"]',
+			name: 'after.always'
+		},
+		{
+			selector: 'CallExpression[callee.object.name="test"][callee.property.name="beforeEach"]',
+			name: 'beforeEach'
+		},
+		{
+			selector: 'CallExpression[callee.object.name="test"][callee.property.name="afterEach"]',
+			name: 'afterEach'
+		},
+		{
+			selector: 'CallExpression[callee.object.object.name="test"][callee.object.property.name="afterEach"][callee.property.name="always"]',
+			name: 'afterEach.always'
+		},
+		{
+			selector: 'CallExpression[callee.name="test"]',
+			name: 'test'
+		}
+	];
+
+	const sourceCode = context.getSourceCode();
+
+	const selectors = checks.reduce((result, check) => {
+		result[check.selector] = visitIf([
+			ava.isInTestFile,
+			ava.isTestNode
+		])(node => {
+			visited[check.name] = node;
+
+			const message = buildMessage(check.name, orders, visited);
+			if (message) {
+				const nodeEarlier = message.node;
+
+				context.report({
+					node,
+					messageId: message.messageId,
+					data: message.data,
+					fix: fixer => {
+						const tokensBetween = sourceCode.getTokensBetween(nodeEarlier.parent, node.parent);
+
+						if (tokensBetween && tokensBetween.length > 0) {
+							return;
+						}
+
+						const source = sourceCode.getText();
+						let [insertStart, insertEnd] = nodeEarlier.parent.range;
+
+						// Grab the node and all comments and whitespace before the node
+						const start = nodeEarlier.parent.range[1];
+						const end = node.parent.range[1];
+
+						let text = sourceCode.getText().substring(start, end);
+
+						// Preserve newline previously between hooks
+						if (source.length >= (start + 1) && source[start + 1] === '\n') {
+							text = text.substring(1) + '\n';
+						}
+
+						// Preserve newline that was previously before hooks
+						if ((insertStart - 1) > 0 && source[insertStart - 1] === '\n') {
+							insertStart -= 1;
+						}
+
+						return [
+							fixer.insertTextBeforeRange([insertStart, insertEnd], text),
+							fixer.removeRange([start, end])
+						];
+					}
+				});
+			}
+		});
+		return result;
+	}, {});
+
+	return ava.merge(selectors);
+};
+
+module.exports = {
+	create,
+	meta: {
+		docs: {
+			url: util.getDocsUrl(__filename)
+		},
+		messages: {
+			[MESSAGE_ID]: '`{{current}}` hook must come before `{{invalid}}`'
+		},
+		type: 'suggestion',
+		fixable: 'code'
+	}
+};
diff --git a/rules/no-inline-assertions.js b/rules/no-inline-assertions.js
new file mode 100644
index 0000000..d1b313b
--- /dev/null
+++ b/rules/no-inline-assertions.js
@@ -0,0 +1,46 @@
+'use strict';
+const {visitIf} = require('enhance-visitors');
+const createAvaRule = require('../create-ava-rule');
+const util = require('../util');
+
+const create = context => {
+	const ava = createAvaRule();
+
+	return ava.merge({
+		CallExpression: visitIf([
+			ava.isInTestFile,
+			ava.isTestNode
+		])(node => {
+			const functionArgIndex = node.arguments.length - 1;
+			if (functionArgIndex > 1) {
+				return;
+			}
+
+			const functionArg = node.arguments[functionArgIndex];
+
+			if (!util.isFunctionExpression(functionArg)) {
+				return;
+			}
+
+			const {body} = functionArg;
+			if (body.type === 'CallExpression') {
+				context.report({
+					node,
+					message: 'The test implementation should not be an inline arrow function.',
+					fix: fixer => [fixer.insertTextBefore(body, '{'), fixer.insertTextAfter(body, '}')]
+				});
+			}
+		})
+	});
+};
+
+module.exports = {
+	create,
+	meta: {
+		docs: {
+			url: util.getDocsUrl(__filename)
+		},
+		type: 'suggestion',
+		fixable: 'code'
+	}
+};


Test patch:
diff --git a/docs/rules/test-title-format.md b/docs/rules/test-title-format.md
index 39cef99..0548342 100644
--- a/docs/rules/test-title-format.md
+++ b/docs/rules/test-title-format.md
@@ -1,5 +1,7 @@
 # Ensure test titles have a certain format
 
+Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/related/eslint-plugin-ava/docs/rules/test-title-format.md)
+
 This rule is useful when you want to make sure all test titles match a common pattern to increase readability when tests fail.
 
 For example, titles like `'Should throw when invalid.'`, `'Should fail when called.'` or `'Should pass when using any number.'` could be enforced with the following pattern `'^Should (pass|fail|throw) when [\\w ]+\\.$'` (Note the escaped `\`).
diff --git a/rules/no-nested-tests.js b/rules/no-nested-tests.js
index 28add74..7806140 100644
--- a/rules/no-nested-tests.js
+++ b/rules/no-nested-tests.js
@@ -16,7 +16,7 @@ const create = context => {
 			if (nestedCount >= 2) {
 				context.report({
 					node,
-					message: 'Tests should not be nested'
+					message: 'Tests should not be nested.'
 				});
 			}
 		}),
diff --git a/rules/test-title-format.js b/rules/test-title-format.js
index 756274c..378f025 100644
--- a/rules/test-title-format.js
+++ b/rules/test-title-format.js
@@ -27,7 +27,7 @@ const create = context => {
 				if (title.type === 'Literal' && !titleRegExp.test(title.value)) {
 					context.report({
 						node,
-						message: `The test title doesn't match the required format: \`${titleRegExp}\``
+						message: `The test title doesn't match the required format: \`${titleRegExp}\`.`
 					});
 				}
 			}
diff --git a/test/hooks-order.js b/test/hooks-order.js
new file mode 100644
index 0000000..f91d858
--- /dev/null
+++ b/test/hooks-order.js
@@ -0,0 +1,723 @@
+import test from 'ava';
+import avaRuleTester from 'eslint-ava-rule-tester';
+import {outdent} from 'outdent';
+import rule from '../rules/hooks-order';
+
+const ruleTester = avaRuleTester(test, {
+	env: {
+		es6: true
+	}
+});
+
+const errors = [{ruleId: 'no-todo-test'}];
+const header = 'const test = require(\'ava\');';
+
+ruleTester.run('no-todo-test', rule, {
+	valid: [
+		outdent`
+			${header}
+
+			test.before(t => {
+				doFoo();
+			});
+
+			test.after(t => {
+				doFoo();
+			});
+
+			test.after.always(t => {
+				doFoo();
+			});
+
+			test.beforeEach(t => {
+				doFoo();
+			});
+
+			test.afterEach(t => {
+				doFoo();
+			});
+
+			test.afterEach.always(t => {
+				doFoo();
+			});
+
+			test('foo', t => {
+				t.true(true);
+			});
+		`,
+		outdent`
+			${header}
+
+			test.before(t => {
+				doFoo();
+			});
+
+			console.log('foo');
+
+			test.after.always(t => {
+				doFoo();
+			});
+
+			const foo = 'foo';
+
+			test.afterEach(t => {
+				doFoo();
+			});
+
+			test('foo', t => {
+				t.true(true);
+			});
+		`,
+		outdent`
+			test.before(t => {
+				doFoo();
+			});
+
+			test.after(t => {
+				doFoo();
+			});
+
+			test.after.always(t => {
+				doFoo();
+			});
+
+			test.beforeEach(t => {
+				doFoo();
+			});
+
+			test.afterEach(t => {
+				doFoo();
+			});
+
+			test.afterEach.always(t => {
+				doFoo();
+			});
+
+			test('foo', t => {
+				t.true(true);
+			});
+		`,
+		outdent`
+			${header}
+
+			test.before(t => {
+				doFoo();
+			});
+
+			test.after(t => {
+				doFoo();
+			});
+		`,
+		outdent`
+			test.after(t => {
+				doFoo();
+			});
+
+			test.before(t => {
+				doFoo();
+			});
+		`,
+		outdent`
+			${header}
+
+			test('foo', t => {
+				t.true(true);
+			});
+		`
+	],
+	invalid: [
+		{
+			code: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.after.always(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.after.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.after.always(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.before(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+
+		{
+			code: outdent`
+				${header}
+
+				test.after.always(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.after.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+
+		{
+			code: outdent`
+				${header}
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.beforeEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.afterEach(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.afterEach(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+
+		{
+			code: outdent`
+				${header}
+
+				test('foo', t => {
+					t.true(true);
+				});
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.afterEach.always(t => {
+					doFoo();
+				});
+
+				test('foo', t => {
+					t.true(true);
+				});
+			`,
+			errors
+		},
+
+		{
+			code: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				console.log('foo');
+
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				console.log('foo');
+
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				// comments
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				// comments
+				test.before(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+			`,
+			errors
+		},
+		{
+			code: outdent`
+				${header}
+
+				test.after(t => {
+					doFoo();
+				});
+
+				/* comments */
+				test.before(t => {
+					doFoo();
+				});
+			`,
+			output: outdent`
+				${header}
+
+				/* comments */
+				test.before(t => {
+					doFoo();
+				});
+
+				test.after(t => {
+					doFoo();
+				});
+			`,
+			errors
+		}
+	]
+});
diff --git a/test/no-async-fn-without-await.js b/test/no-async-fn-without-await.js
index e28e157..dcdbc2d 100644
--- a/test/no-async-fn-without-await.js
+++ b/test/no-async-fn-without-await.js
@@ -14,7 +14,7 @@ const ruleTesterOptions = [
 	}
 	// Disabled for now because of `eslint-ava-rule-tester` problem
 	// {
-	// 	parser: 'babel-eslint',
+	// 	parser: require.resolve('babel-eslint'),
 	// 	env: {
 	// 		es6: true
 	// 	}
diff --git a/test/no-inline-assertions.js b/test/no-inline-assertions.js
new file mode 100644
index 0000000..834aacf
--- /dev/null
+++ b/test/no-inline-assertions.js
@@ -0,0 +1,47 @@
+import test from 'ava';
+import avaRuleTester from 'eslint-ava-rule-tester';
+import rule from '../rules/no-inline-assertions';
+
+const ruleTester = avaRuleTester(test, {
+	env: {
+		es6: true
+	}
+});
+
+const errors = [{ruleId: 'no-inline-assertions'}];
+const header = 'const test = require(\'ava\');\n';
+
+ruleTester.run('no-todo-test', rule, {
+	valid: [
+		// Shouldn't be triggered as the test implementation is not an inline arrow function
+		header + 'test("my test name", t => {\n t.true(fn()); \n});',
+		header + 'test("my test name", function (t) { foo(); });',
+		// Shouldn't be triggered since test body is empty
+		header + 'test("my test name", () => {});',
+		header + 'test("my test name", t => {});',
+		// Shouldn't be triggered since test body is ill-defined
+		header + 'test("my test name", t => "foo");',
+		// Shouldn't be triggered since it's not a test file
+		'test.todo("my test name");',
+		// Shouldn't be triggered since the signature is incorrect
+		header + 'test.todo("my test name", "bar");',
+		header + 'test.todo("my test name", undefined, t => {})'
+	],
+	invalid: [
+		{
+			code: header + 'test("my test name", t => t.skip());',
+			errors,
+			output: header + 'test("my test name", t => {t.skip()});'
+		},
+		{
+			code: header + 'test("my test name", t => t.true(fn()));',
+			errors,
+			output: header + 'test("my test name", t => {t.true(fn())});'
+		},
+		{
+			code: header + 'test("my test name", t => \n t.true(fn()));',
+			errors,
+			output: header + 'test("my test name", t => \n {t.true(fn())});'
+		}
+	]
+});
diff --git a/test/no-nested-tests.js b/test/no-nested-tests.js
index e5e0ac4..7d23e1e 100644
--- a/test/no-nested-tests.js
+++ b/test/no-nested-tests.js
@@ -11,7 +11,7 @@ const ruleTester = avaRuleTester(test, {
 const header = 'const test = require(\'ava\');\n';
 const error = {
 	ruleId: 'no-nested-tests',
-	message: 'Tests should not be nested'
+	message: 'Tests should not be nested.'
 };
 
 ruleTester.run('no-nested-tests', rule, {