Skip to content

Commit 38bbe93

Browse files
authored
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
1 parent 1449675 commit 38bbe93

File tree

3 files changed

+160
-6
lines changed

3 files changed

+160
-6
lines changed

docs/rules/valid-title.md

+45-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ xtest('foo', () => {});
4545

4646
Titles for test blocks should always be a string literal or expression.
4747

48-
This is also applied to describe blocks by default, but can be turned off via
48+
This is also applied to `describe` blocks by default, but can be turned off via
4949
the `ignoreTypeOfDescribeName` option:
5050

5151
Examples of **incorrect** code for this rule:
@@ -87,7 +87,7 @@ describe(6, function() {});
8787

8888
**duplicatePrefix**
8989

90-
A describe/ test block should not start with duplicatePrefix
90+
A `describe` / `test` block should not start with `duplicatePrefix`
9191

9292
Examples of **incorrect** code for this rule
9393

@@ -117,7 +117,7 @@ describe('foo', () => {
117117

118118
**accidentalSpace**
119119

120-
A describe/ test block should not contain accidentalSpace
120+
A `describe` / `test` block should not contain accidentalSpace
121121

122122
Examples of **incorrect** code for this rule
123123

@@ -148,3 +148,45 @@ describe('foo', () => {
148148
test('bar', () => {});
149149
});
150150
```
151+
152+
## Options
153+
154+
```ts
155+
interface {
156+
ignoreTypeOfDescribeName?: boolean;
157+
disallowedWords?: string[];
158+
}
159+
```
160+
161+
#### `ignoreTypeOfDescribeName`
162+
163+
Default: `false`
164+
165+
When enabled, the type of the first argument to `describe` blocks won't be
166+
checked.
167+
168+
#### `disallowedWords`
169+
170+
Default: `[]`
171+
172+
A string array of words that are not allowed to be used in test titles. Matching
173+
is not case-sensitive, and looks for complete words:
174+
175+
Examples of **incorrect** code using `disallowedWords`:
176+
177+
```js
178+
// with disallowedWords: ['correct', 'all', 'every', 'properly']
179+
describe('the correct way to do things', () => {});
180+
it('has ALL the things', () => {});
181+
xdescribe('every single one of them', () => {});
182+
test(`that the value is set properly`, () => {});
183+
```
184+
185+
Examples of **correct** code when using `disallowedWords`:
186+
187+
```js
188+
// with disallowedWords: ['correct', 'all', 'every', 'properly']
189+
it('correctly sets the value', () => {});
190+
test('that everything is as it should be', () => {});
191+
describe('the proper way to handle things', () => {});
192+
```

src/rules/__tests__/valid-title.test.ts

+88-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,93 @@ const ruleTester = new TSESLint.RuleTester({
99
},
1010
});
1111

12+
ruleTester.run('disallowedWords option', rule, {
13+
valid: [
14+
'describe("the correct way to properly handle all the things", () => {});',
15+
'test("that all is as it should be", () => {});',
16+
{
17+
code: 'it("correctly sets the value", () => {});',
18+
options: [
19+
{ ignoreTypeOfDescribeName: false, disallowedWords: ['correct'] },
20+
],
21+
},
22+
],
23+
invalid: [
24+
{
25+
code: 'test("the correct way to properly handle all things", () => {});',
26+
options: [{ disallowedWords: ['correct', 'properly', 'all'] }],
27+
errors: [
28+
{
29+
messageId: 'disallowedWord',
30+
data: { word: 'correct' },
31+
column: 6,
32+
line: 1,
33+
},
34+
],
35+
},
36+
{
37+
code: 'describe("the correct way to do things", function () {})',
38+
options: [{ disallowedWords: ['correct'] }],
39+
errors: [
40+
{
41+
messageId: 'disallowedWord',
42+
data: { word: 'correct' },
43+
column: 10,
44+
line: 1,
45+
},
46+
],
47+
},
48+
{
49+
code: 'it("has ALL the things", () => {})',
50+
options: [{ disallowedWords: ['all'] }],
51+
errors: [
52+
{
53+
messageId: 'disallowedWord',
54+
data: { word: 'ALL' },
55+
column: 4,
56+
line: 1,
57+
},
58+
],
59+
},
60+
{
61+
code: 'xdescribe("every single one of them", function () {})',
62+
options: [{ disallowedWords: ['every'] }],
63+
errors: [
64+
{
65+
messageId: 'disallowedWord',
66+
data: { word: 'every' },
67+
column: 11,
68+
line: 1,
69+
},
70+
],
71+
},
72+
{
73+
code: "describe('Very Descriptive Title Goes Here', function () {})",
74+
options: [{ disallowedWords: ['descriptive'] }],
75+
errors: [
76+
{
77+
messageId: 'disallowedWord',
78+
data: { word: 'Descriptive' },
79+
column: 10,
80+
line: 1,
81+
},
82+
],
83+
},
84+
{
85+
code: 'test(`that the value is set properly`, function () {})',
86+
options: [{ disallowedWords: ['properly'] }],
87+
errors: [
88+
{
89+
messageId: 'disallowedWord',
90+
data: { word: 'properly' },
91+
column: 6,
92+
line: 1,
93+
},
94+
],
95+
},
96+
],
97+
});
98+
1299
ruleTester.run('title-must-be-string', rule, {
13100
valid: [
14101
'it("is a string", () => {});',
@@ -31,7 +118,7 @@ ruleTester.run('title-must-be-string', rule, {
31118
},
32119
{
33120
code: 'xdescribe(skipFunction, () => {});',
34-
options: [{ ignoreTypeOfDescribeName: true }],
121+
options: [{ ignoreTypeOfDescribeName: true, disallowedWords: [] }],
35122
},
36123
],
37124
invalid: [

src/rules/valid-title.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default createRule({
5050
emptyTitle: '{{ jestFunctionName }} should not have an empty title',
5151
duplicatePrefix: 'should not have duplicate prefix',
5252
accidentalSpace: 'should not have leading or trailing spaces',
53+
disallowedWord: '"{{ word }}" is not allowed in test titles.',
5354
},
5455
type: 'suggestion',
5556
schema: [
@@ -60,14 +61,24 @@ export default createRule({
6061
type: 'boolean',
6162
default: false,
6263
},
64+
disallowedWords: {
65+
type: 'array',
66+
items: { type: 'string' },
67+
default: [],
68+
},
6369
},
6470
additionalProperties: false,
6571
},
6672
],
6773
fixable: 'code',
6874
},
69-
defaultOptions: [{ ignoreTypeOfDescribeName: false }],
70-
create(context, [{ ignoreTypeOfDescribeName }]) {
75+
defaultOptions: [{ ignoreTypeOfDescribeName: false, disallowedWords: [] }],
76+
create(context, [{ ignoreTypeOfDescribeName, disallowedWords }]) {
77+
const disallowedWordsRegexp = new RegExp(
78+
`\\b(${disallowedWords.join('|')})\\b`,
79+
'iu',
80+
);
81+
7182
return {
7283
CallExpression(node: TSESTree.CallExpression) {
7384
if (!(isDescribe(node) || isTestCase(node)) || !node.arguments.length) {
@@ -113,6 +124,20 @@ export default createRule({
113124
return;
114125
}
115126

127+
if (disallowedWords.length > 0) {
128+
const disallowedMatch = disallowedWordsRegexp.exec(title);
129+
130+
if (disallowedMatch) {
131+
context.report({
132+
data: { word: disallowedMatch[1] },
133+
messageId: 'disallowedWord',
134+
node: argument,
135+
});
136+
137+
return;
138+
}
139+
}
140+
116141
if (title.trim().length !== title.length) {
117142
context.report({
118143
messageId: 'accidentalSpace',

0 commit comments

Comments
 (0)