From c793b7aa5d0436f05b4c345cf677d343be0776c8 Mon Sep 17 00:00:00 2001 From: Dave Lunny Date: Fri, 15 Mar 2019 09:27:22 -0700 Subject: [PATCH] feat(rules): Add no-empty-title rule (#238) Fixes #226 --- README.md | 2 + docs/rules/no-empty-title.md | 36 +++++++ rules/__tests__/no-empty-title.js | 108 +++++++++++++++++++++ rules/__tests__/no-identical-title.test.js | 6 ++ rules/no-empty-title.js | 54 +++++++++++ rules/no-identical-title.js | 11 +-- rules/util.js | 9 +- 7 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 docs/rules/no-empty-title.md create mode 100644 rules/__tests__/no-empty-title.js create mode 100644 rules/no-empty-title.js diff --git a/README.md b/README.md index f96692702..e619ae66e 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ for more information about extending configuration files. | [lowercase-name][] | Disallow capitalized test names | | ![fixable-green][] | | [no-alias-methods][] | Disallow alias methods | ![recommended][] | ![fixable-green][] | | [no-disabled-tests][] | Disallow disabled tests | ![recommended][] | | +| [no-empty-title][] | Disallow empty titles | | | | [no-focused-tests][] | Disallow focused tests | ![recommended][] | | | [no-hooks][] | Disallow setup and teardown hooks | | | | [no-identical-title][] | Disallow identical titles | ![recommended][] | | @@ -131,6 +132,7 @@ for more information about extending configuration files. [lowercase-name]: docs/rules/lowercase-name.md [no-alias-methods]: docs/rules/no-alias-methods.md [no-disabled-tests]: docs/rules/no-disabled-tests.md +[no-empty-title]: docs/rules/no-empty-title.md [no-focused-tests]: docs/rules/no-focused-tests.md [no-hooks]: docs/rules/no-hooks.md [no-identical-title]: docs/rules/no-identical-title.md diff --git a/docs/rules/no-empty-title.md b/docs/rules/no-empty-title.md new file mode 100644 index 000000000..02552810c --- /dev/null +++ b/docs/rules/no-empty-title.md @@ -0,0 +1,36 @@ +# Disallow empty titles + +Having an empty string as your test title is pretty useless. This rule reports +an error if it finds an empty string as s test title. + +This rule is not auto-fixable. + +## Rule Details + +The following patterns are considered warnings: + +```js +describe('', () => {}); +describe('foo', () => { + it('', () => {}); +}); +it('', () => {}); +test('', () => {}); +xdescribe('', () => {}); +xit('', () => {}); +xtest('', () => {}); +``` + +These patterns would not be considered warnings: + +```js +describe('foo', () => {}); +describe('foo', () => { + it('bar', () => {}); +}); +test('foo', () => {}); +it('foo', () => {}); +xdescribe('foo', () => {}); +xit('foo', () => {}); +xtest('foo', () => {}); +``` diff --git a/rules/__tests__/no-empty-title.js b/rules/__tests__/no-empty-title.js new file mode 100644 index 000000000..758a3cd21 --- /dev/null +++ b/rules/__tests__/no-empty-title.js @@ -0,0 +1,108 @@ +'use strict'; + +const { RuleTester } = require('eslint'); +const rule = require('../no-empty-title'); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + }, +}); + +ruleTester.run('no-empty-title', rule, { + valid: [ + 'someFn("", function () {})', + 'describe(1, function () {})', + 'describe("foo", function () {})', + 'describe("foo", function () { it("bar", function () {}) })', + 'test("foo", function () {})', + 'test(`foo`, function () {})', + 'test(`${foo}`, function () {})', + "it('foo', function () {})", + "xdescribe('foo', function () {})", + "xit('foo', function () {})", + "xtest('foo', function () {})", + ], + invalid: [ + { + code: 'describe("", function () {})', + errors: [ + { + message: rule.errorMessages.describe, + column: 1, + line: 1, + }, + ], + }, + { + code: ["describe('foo', () => {", "it('', () => {})", '})'].join('\n'), + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 2, + }, + ], + }, + { + code: 'it("", function () {})', + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 1, + }, + ], + }, + { + code: 'test("", function () {})', + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 1, + }, + ], + }, + { + code: 'test(``, function () {})', + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 1, + }, + ], + }, + { + code: "xdescribe('', () => {})", + errors: [ + { + message: rule.errorMessages.describe, + column: 1, + line: 1, + }, + ], + }, + { + code: "xit('', () => {})", + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 1, + }, + ], + }, + { + code: "xtest('', () => {})", + errors: [ + { + message: rule.errorMessages.test, + column: 1, + line: 1, + }, + ], + }, + ], +}); diff --git a/rules/__tests__/no-identical-title.test.js b/rules/__tests__/no-identical-title.test.js index 364410bf3..9099ad683 100644 --- a/rules/__tests__/no-identical-title.test.js +++ b/rules/__tests__/no-identical-title.test.js @@ -69,6 +69,12 @@ ruleTester.run('no-identical-title', rule, { es6: true, }, }, + { + code: 'it(`${n}`, function() {});', + env: { + es6: true, + }, + }, [ 'describe("title " + foo, function() {', ' describe("describe1", function() {});', diff --git a/rules/no-empty-title.js b/rules/no-empty-title.js new file mode 100644 index 000000000..9efc1e810 --- /dev/null +++ b/rules/no-empty-title.js @@ -0,0 +1,54 @@ +'use strict'; + +const { + getDocsUrl, + hasExpressions, + isDescribe, + isTestCase, + isTemplateLiteral, + isString, + getStringValue, +} = require('./util'); + +const errorMessages = { + describe: 'describe should not have an empty title', + test: 'test should not have an empty title', +}; + +module.exports = { + meta: { + docs: { + url: getDocsUrl(__filename), + }, + }, + create(context) { + return { + CallExpression(node) { + const is = { + describe: isDescribe(node), + testCase: isTestCase(node), + }; + if (!is.describe && !is.testCase) { + return; + } + const [firstArgument] = node.arguments; + if (!isString(firstArgument)) { + return; + } + if (isTemplateLiteral(firstArgument) && hasExpressions(firstArgument)) { + return; + } + if (getStringValue(firstArgument) === '') { + const message = is.describe + ? errorMessages.describe + : errorMessages.test; + context.report({ + message, + node, + }); + } + }, + }; + }, + errorMessages, +}; diff --git a/rules/no-identical-title.js b/rules/no-identical-title.js index 7e3da6f17..001a0fdb9 100644 --- a/rules/no-identical-title.js +++ b/rules/no-identical-title.js @@ -6,6 +6,7 @@ const { isTestCase, isString, hasExpressions, + getStringValue, } = require('./util'); const newDescribeContext = () => ({ @@ -50,14 +51,6 @@ const isFirstArgValid = arg => { return true; }; -const getArgValue = arg => { - if (arg.type === 'TemplateLiteral') { - return arg.quasis[0].value.raw; - } - - return arg.value; -}; - module.exports = { meta: { docs: { @@ -76,7 +69,7 @@ module.exports = { if (!isFirstArgValid(firstArgument)) { return; } - const title = getArgValue(firstArgument); + const title = getStringValue(firstArgument); handleTestCaseTitles(context, currentLayer.testTitles, node, title); handleDescribeBlockTitles( context, diff --git a/rules/util.js b/rules/util.js index 505d5f1f8..b7bf6469a 100644 --- a/rules/util.js +++ b/rules/util.js @@ -132,10 +132,15 @@ const isFunction = node => const isString = node => (node.type === 'Literal' && typeof node.value === 'string') || - node.type === 'TemplateLiteral'; + isTemplateLiteral(node); + +const isTemplateLiteral = node => node.type === 'TemplateLiteral'; const hasExpressions = node => node.expressions && node.expressions.length > 0; +const getStringValue = arg => + isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value; + /** * Generates the URL to documentation for the given rule name. It uses the * package version to build the link to a tagged version of the @@ -210,8 +215,10 @@ module.exports = { expectToEqualCase, expectNotToEqualCase, getNodeName, + getStringValue, isDescribe, isFunction, + isTemplateLiteral, isTestCase, isString, hasExpressions,