diff --git a/docs/rules/test-title-format.md b/docs/rules/test-title-format.md new file mode 100644 index 00000000..39cef992 --- /dev/null +++ b/docs/rules/test-title-format.md @@ -0,0 +1,69 @@ +# Ensure test titles have a certain format + +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 `\`). + + +## Fail + +```js +/* eslint ava/test-title: ["error", {format: "^Should"}] */ +import test from 'ava'; + +test('Not starting with `Should`', t => { + t.pass(); +}); +``` + +```js +/* eslint ava/test-title: ["error", {format: "\\.$"}] */ +import test from 'ava'; + +test('Doesn\'t end with a dot', t => { + t.pass(); +}); +``` + + +## Pass + +```js +/* eslint ava/test-title: ["error", {format: "^Should"}] */ +import test from 'ava'; + +test('Should pass tests', t => { + t.pass(); +}); + +test('Should behave as expected', t => { + t.pass(); +}); +``` + +```js +/* eslint ava/test-title: ["error", {format: "\\.$"}] */ +import test from 'ava'; + +test('End with a dot.', t => { + t.pass(); +}); +``` + + +## Options + +This rule supports the following options: + +`format`: A regular expression string to match against the test titles. + +You can set the options like this: + +```json +"ava/test-title-format": [ + "error", + { + "format": "^Should" + } +] +``` diff --git a/index.js b/index.js index 88bb1748..86484312 100644 --- a/index.js +++ b/index.js @@ -41,6 +41,7 @@ module.exports = { 'ava/prefer-power-assert': 'off', 'ava/test-ended': 'error', 'ava/test-title': 'error', + 'ava/test-title-format': 'off', 'ava/use-t-well': 'error', 'ava/use-t': 'error', 'ava/use-test': 'error', diff --git a/readme.md b/readme.md index 5b382daf..6505290e 100644 --- a/readme.md +++ b/readme.md @@ -59,6 +59,7 @@ Configure it in `package.json`. "ava/prefer-power-assert": "off", "ava/test-ended": "error", "ava/test-title": "error", + "ava/test-title-format": "off", "ava/use-t": "error", "ava/use-t-well": "error", "ava/use-test": "error", @@ -94,6 +95,7 @@ The rules will only activate in test files. - [prefer-power-assert](docs/rules/prefer-power-assert.md) - Allow only use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative. - [test-ended](docs/rules/test-ended.md) - Ensure callback tests are explicitly ended. - [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-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. diff --git a/rules/test-title-format.js b/rules/test-title-format.js new file mode 100644 index 00000000..dcadc373 --- /dev/null +++ b/rules/test-title-format.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(); + + let titleRegExp; + if (context.options[0] && context.options[0].format) { + titleRegExp = new RegExp(context.options[0].format); + } else { + return {}; + } + + return ava.merge({ + CallExpression: visitIf([ + ava.isInTestFile, + ava.isTestNode, + ava.hasNoHookModifier + ])(node => { + const requiredLength = ava.hasTestModifier('todo') ? 1 : 2; + const hasTitle = node.arguments.length >= requiredLength; + + if (hasTitle) { + const title = node.arguments[0]; + if (title.type === 'Literal' && !titleRegExp.test(title.value)) { + context.report({ + node, + message: `The test title doesn't match the required format: \`${titleRegExp}\`` + }); + } + } + }) + }); +}; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + url: util.getDocsUrl(__filename) + } + } +}; diff --git a/test/test-title-format.js b/test/test-title-format.js new file mode 100644 index 00000000..3b7fa96e --- /dev/null +++ b/test/test-title-format.js @@ -0,0 +1,59 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import rule from '../rules/test-title-format'; + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + } +}); + +const errors = [{ruleId: 'test-title-format'}]; +const header = 'const test = require(\'ava\');\n'; + +ruleTester.run('test-title-format', rule, { + valid: [ + header + 'test("Foo", t => { t.pass(); });', + { + code: header + 'test("Foo", t => { t.pass(); });', + options: [{format: '.'}] + }, + { + code: header + 'test("Should pass tests.", t => { t.pass(); });', + options: [{format: '^Should .+\\.$'}] + }, + { + code: header + 'test.todo("Should pass tests.");', + options: [{format: '^Should .+\\.$'}] + }, + { + code: header + 'test(t => { t.pass(); });', + options: [{format: '^Should'}] + }, + { + code: header + 'notTest("Foo", t => { t.pass(); });', + options: [{format: '^Should'}] + }, + { + code: header + 'test(macro, t => { t.pass(); });', + options: [{format: '^Should'}] + }, + // Shouldn't be triggered since it's not a test file + { + code: 'test("Test", t => { t.pass(); });', + options: [{format: '^Should'}] + } + ], + invalid: [ + { + code: header + 'test("Test something", t => { t.pass(); });', + options: [{format: '^Should'}], + errors + }, + { + code: header + 'test.todo("Test something");', + options: [{format: '^Should'}], + errors + } + ] +});