From 38bbe93794ed456c6e9e5d7be848b2aeb55ce0ba Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 8 Feb 2020 09:39:34 +1300 Subject: [PATCH] feat(valid-title): support `disallowedWords` option (#522) * feat(valid-title): support `disallowedWords` option * test(valid-title): include `data` in `disallowedWord` tests * test(valid-title): add multi-disallowedWords test * docs(valid-title): use code quotes & arrow functions * chore(valid-title): use "greater than 0" in check * chore(valid-title): reword `disallowedWord` message --- docs/rules/valid-title.md | 48 ++++++++++++- src/rules/__tests__/valid-title.test.ts | 89 ++++++++++++++++++++++++- src/rules/valid-title.ts | 29 +++++++- 3 files changed, 160 insertions(+), 6 deletions(-) diff --git a/docs/rules/valid-title.md b/docs/rules/valid-title.md index 579fdb48e..38fef1bb7 100644 --- a/docs/rules/valid-title.md +++ b/docs/rules/valid-title.md @@ -45,7 +45,7 @@ xtest('foo', () => {}); Titles for test blocks should always be a string literal or expression. -This is also applied to describe blocks by default, but can be turned off via +This is also applied to `describe` blocks by default, but can be turned off via the `ignoreTypeOfDescribeName` option: Examples of **incorrect** code for this rule: @@ -87,7 +87,7 @@ describe(6, function() {}); **duplicatePrefix** -A describe/ test block should not start with duplicatePrefix +A `describe` / `test` block should not start with `duplicatePrefix` Examples of **incorrect** code for this rule @@ -117,7 +117,7 @@ describe('foo', () => { **accidentalSpace** -A describe/ test block should not contain accidentalSpace +A `describe` / `test` block should not contain accidentalSpace Examples of **incorrect** code for this rule @@ -148,3 +148,45 @@ describe('foo', () => { test('bar', () => {}); }); ``` + +## Options + +```ts +interface { + ignoreTypeOfDescribeName?: boolean; + disallowedWords?: string[]; +} +``` + +#### `ignoreTypeOfDescribeName` + +Default: `false` + +When enabled, the type of the first argument to `describe` blocks won't be +checked. + +#### `disallowedWords` + +Default: `[]` + +A string array of words that are not allowed to be used in test titles. Matching +is not case-sensitive, and looks for complete words: + +Examples of **incorrect** code using `disallowedWords`: + +```js +// with disallowedWords: ['correct', 'all', 'every', 'properly'] +describe('the correct way to do things', () => {}); +it('has ALL the things', () => {}); +xdescribe('every single one of them', () => {}); +test(`that the value is set properly`, () => {}); +``` + +Examples of **correct** code when using `disallowedWords`: + +```js +// with disallowedWords: ['correct', 'all', 'every', 'properly'] +it('correctly sets the value', () => {}); +test('that everything is as it should be', () => {}); +describe('the proper way to handle things', () => {}); +``` diff --git a/src/rules/__tests__/valid-title.test.ts b/src/rules/__tests__/valid-title.test.ts index 7c293c49e..3584b4909 100644 --- a/src/rules/__tests__/valid-title.test.ts +++ b/src/rules/__tests__/valid-title.test.ts @@ -9,6 +9,93 @@ const ruleTester = new TSESLint.RuleTester({ }, }); +ruleTester.run('disallowedWords option', rule, { + valid: [ + 'describe("the correct way to properly handle all the things", () => {});', + 'test("that all is as it should be", () => {});', + { + code: 'it("correctly sets the value", () => {});', + options: [ + { ignoreTypeOfDescribeName: false, disallowedWords: ['correct'] }, + ], + }, + ], + invalid: [ + { + code: 'test("the correct way to properly handle all things", () => {});', + options: [{ disallowedWords: ['correct', 'properly', 'all'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'correct' }, + column: 6, + line: 1, + }, + ], + }, + { + code: 'describe("the correct way to do things", function () {})', + options: [{ disallowedWords: ['correct'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'correct' }, + column: 10, + line: 1, + }, + ], + }, + { + code: 'it("has ALL the things", () => {})', + options: [{ disallowedWords: ['all'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'ALL' }, + column: 4, + line: 1, + }, + ], + }, + { + code: 'xdescribe("every single one of them", function () {})', + options: [{ disallowedWords: ['every'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'every' }, + column: 11, + line: 1, + }, + ], + }, + { + code: "describe('Very Descriptive Title Goes Here', function () {})", + options: [{ disallowedWords: ['descriptive'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'Descriptive' }, + column: 10, + line: 1, + }, + ], + }, + { + code: 'test(`that the value is set properly`, function () {})', + options: [{ disallowedWords: ['properly'] }], + errors: [ + { + messageId: 'disallowedWord', + data: { word: 'properly' }, + column: 6, + line: 1, + }, + ], + }, + ], +}); + ruleTester.run('title-must-be-string', rule, { valid: [ 'it("is a string", () => {});', @@ -31,7 +118,7 @@ ruleTester.run('title-must-be-string', rule, { }, { code: 'xdescribe(skipFunction, () => {});', - options: [{ ignoreTypeOfDescribeName: true }], + options: [{ ignoreTypeOfDescribeName: true, disallowedWords: [] }], }, ], invalid: [ diff --git a/src/rules/valid-title.ts b/src/rules/valid-title.ts index a98e70342..43e7ffd21 100644 --- a/src/rules/valid-title.ts +++ b/src/rules/valid-title.ts @@ -50,6 +50,7 @@ export default createRule({ emptyTitle: '{{ jestFunctionName }} should not have an empty title', duplicatePrefix: 'should not have duplicate prefix', accidentalSpace: 'should not have leading or trailing spaces', + disallowedWord: '"{{ word }}" is not allowed in test titles.', }, type: 'suggestion', schema: [ @@ -60,14 +61,24 @@ export default createRule({ type: 'boolean', default: false, }, + disallowedWords: { + type: 'array', + items: { type: 'string' }, + default: [], + }, }, additionalProperties: false, }, ], fixable: 'code', }, - defaultOptions: [{ ignoreTypeOfDescribeName: false }], - create(context, [{ ignoreTypeOfDescribeName }]) { + defaultOptions: [{ ignoreTypeOfDescribeName: false, disallowedWords: [] }], + create(context, [{ ignoreTypeOfDescribeName, disallowedWords }]) { + const disallowedWordsRegexp = new RegExp( + `\\b(${disallowedWords.join('|')})\\b`, + 'iu', + ); + return { CallExpression(node: TSESTree.CallExpression) { if (!(isDescribe(node) || isTestCase(node)) || !node.arguments.length) { @@ -113,6 +124,20 @@ export default createRule({ return; } + if (disallowedWords.length > 0) { + const disallowedMatch = disallowedWordsRegexp.exec(title); + + if (disallowedMatch) { + context.report({ + data: { word: disallowedMatch[1] }, + messageId: 'disallowedWord', + node: argument, + }); + + return; + } + } + if (title.trim().length !== title.length) { context.report({ messageId: 'accidentalSpace',