Skip to content

Commit 1b2d24b

Browse files
suganya-sangithG-Rath
authored andcommitted
feat(rules): prefer valid-title (#273) (#433)
1 parent 5bd8f61 commit 1b2d24b

File tree

4 files changed

+291
-1
lines changed

4 files changed

+291
-1
lines changed

docs/rules/valid-title.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Disallow duplicate setup and teardown hooks (no-duplicate-hooks)
2+
3+
A describe/ test block should not contain accidentalSpace or duplicatePrefix.
4+
5+
## Rule Details
6+
7+
**duplicatePrefix**
8+
9+
A describe/ test block should not start with duplicatePrefix
10+
11+
Examples of **incorrect** code for this rule
12+
13+
```js
14+
test('test foo', () => {});
15+
it('it foo', () => {});
16+
17+
describe('foo', () => {
18+
test('test bar', () => {});
19+
});
20+
21+
describe('describe foo', () => {
22+
test('bar', () => {});
23+
});
24+
```
25+
26+
Examples of **correct** code for this rule
27+
28+
```js
29+
test('foo', () => {});
30+
it('foo', () => {});
31+
32+
describe('foo', () => {
33+
test('bar', () => {});
34+
});
35+
```
36+
37+
**accidentalSpace**
38+
39+
A describe/ test block should not contain accidentalSpace
40+
41+
Examples of **incorrect** code for this rule
42+
43+
```js
44+
test(' foo', () => {});
45+
it(' foo', () => {});
46+
47+
describe('foo', () => {
48+
test(' bar', () => {});
49+
});
50+
51+
describe(' foo', () => {
52+
test('bar', () => {});
53+
});
54+
```
55+
56+
Examples of **correct** code for this rule
57+
58+
```js
59+
test('foo', () => {});
60+
it('foo', () => {});
61+
62+
describe('foo', () => {
63+
test('bar', () => {});
64+
});
65+
```

src/__tests__/rules.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resolve } from 'path';
33
import plugin from '../';
44

55
const ruleNames = Object.keys(plugin.rules);
6-
const numberOfRules = 40;
6+
const numberOfRules = 41;
77

88
describe('rules', () => {
99
it('should have a corresponding doc for each rule', () => {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { TSESLint } from '@typescript-eslint/experimental-utils';
2+
import rule from '../valid-title';
3+
4+
const ruleTester = new TSESLint.RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 8,
7+
},
8+
});
9+
10+
ruleTester.run('no-accidental-space', rule, {
11+
valid: [
12+
'describe("foo", function () {})',
13+
'describe(6, function () {})',
14+
'fdescribe("foo", function () {})',
15+
'xdescribe("foo", function () {})',
16+
'it("foo", function () {})',
17+
'fit("foo", function () {})',
18+
'xit("foo", function () {})',
19+
'test("foo", function () {})',
20+
'xtest("foo", function () {})',
21+
'someFn("foo", function () {})',
22+
`
23+
describe('foo', () => {
24+
it('bar', () => {})
25+
})
26+
`,
27+
],
28+
invalid: [
29+
{
30+
code: 'describe(" foo", function () {})',
31+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
32+
},
33+
{
34+
code: 'fdescribe(" foo", function () {})',
35+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
36+
},
37+
{
38+
code: 'xdescribe(" foo", function () {})',
39+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
40+
},
41+
{
42+
code: 'it(" foo", function () {})',
43+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
44+
},
45+
{
46+
code: 'fit(" foo", function () {})',
47+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
48+
},
49+
{
50+
code: 'xit(" foo", function () {})',
51+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
52+
},
53+
{
54+
code: 'test(" foo", function () {})',
55+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
56+
},
57+
{
58+
code: 'xtest(" foo", function () {})',
59+
errors: [{ messageId: 'accidentalSpace', column: 1, line: 1 }],
60+
},
61+
{
62+
code: `
63+
describe(' foo', () => {
64+
it('bar', () => {})
65+
})
66+
`,
67+
errors: [{ messageId: 'accidentalSpace', column: 7, line: 2 }],
68+
},
69+
{
70+
code: `
71+
describe('foo', () => {
72+
it(' bar', () => {})
73+
})
74+
`,
75+
errors: [{ messageId: 'accidentalSpace', column: 9, line: 3 }],
76+
},
77+
],
78+
});
79+
80+
ruleTester.run('no-duplicate-prefix ft describe', rule, {
81+
valid: [
82+
'describe("foo", function () {})',
83+
'fdescribe("foo", function () {})',
84+
'xdescribe("foo", function () {})',
85+
],
86+
invalid: [
87+
{
88+
code: 'describe("describe foo", function () {})',
89+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
90+
},
91+
{
92+
code: 'fdescribe("describe foo", function () {})',
93+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
94+
},
95+
{
96+
code: 'xdescribe("describe foo", function () {})',
97+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
98+
},
99+
],
100+
});
101+
102+
ruleTester.run('no-duplicate-prefix ft test', rule, {
103+
valid: ['test("foo", function () {})', 'xtest("foo", function () {})'],
104+
invalid: [
105+
{
106+
code: 'test("test foo", function () {})',
107+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
108+
},
109+
{
110+
code: 'xtest("test foo", function () {})',
111+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
112+
},
113+
],
114+
});
115+
116+
ruleTester.run('no-duplicate-prefix ft it', rule, {
117+
valid: [
118+
'it("foo", function () {})',
119+
'fit("foo", function () {})',
120+
'xit("foo", function () {})',
121+
],
122+
invalid: [
123+
{
124+
code: 'it("it foo", function () {})',
125+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
126+
},
127+
{
128+
code: 'fit("it foo", function () {})',
129+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
130+
},
131+
{
132+
code: 'xit("it foo", function () {})',
133+
errors: [{ messageId: 'duplicatePrefix', column: 1, line: 1 }],
134+
},
135+
],
136+
});
137+
138+
ruleTester.run('no-duplicate-prefix ft nested', rule, {
139+
valid: [
140+
`
141+
describe('foo', () => {
142+
it('bar', () => {})
143+
})`,
144+
],
145+
invalid: [
146+
{
147+
code: `
148+
describe('describe foo', () => {
149+
it('bar', () => {})
150+
})`,
151+
errors: [{ messageId: 'duplicatePrefix', column: 7, line: 2 }],
152+
},
153+
{
154+
code: `
155+
describe('foo', () => {
156+
it('it bar', () => {})
157+
})`,
158+
errors: [{ messageId: 'duplicatePrefix', column: 9, line: 3 }],
159+
},
160+
],
161+
});

src/rules/valid-title.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {
2+
createRule,
3+
getNodeName,
4+
getStringValue,
5+
isDescribe,
6+
isStringNode,
7+
isTestCase,
8+
} from './utils';
9+
10+
import { TSESTree } from '@typescript-eslint/experimental-utils';
11+
12+
const trimFXprefix = (word: string) =>
13+
['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word;
14+
15+
const getNodeTitle = (node: TSESTree.CallExpression): string | null => {
16+
const [argument] = node.arguments;
17+
18+
return isStringNode(argument) ? getStringValue(argument) : null;
19+
};
20+
21+
export default createRule({
22+
name: __filename,
23+
meta: {
24+
docs: {
25+
category: 'Best Practices',
26+
description: 'Enforce valid titles',
27+
recommended: false,
28+
},
29+
messages: {
30+
duplicatePrefix: 'should not have duplicate prefix',
31+
accidentalSpace: 'should not have space in the beginning',
32+
},
33+
type: 'suggestion',
34+
schema: [],
35+
},
36+
defaultOptions: [],
37+
create(context) {
38+
return {
39+
CallExpression(node) {
40+
if (!isDescribe(node) && !isTestCase(node)) return;
41+
42+
const title = getNodeTitle(node);
43+
if (!title) return;
44+
45+
if (title.trimLeft().length !== title.length) {
46+
context.report({
47+
messageId: 'accidentalSpace',
48+
node,
49+
});
50+
}
51+
52+
const nodeName = trimFXprefix(getNodeName(node.callee));
53+
const [firstWord] = title.split(' ');
54+
55+
if (firstWord.toLowerCase() === nodeName) {
56+
context.report({
57+
messageId: 'duplicatePrefix',
58+
node,
59+
});
60+
}
61+
},
62+
};
63+
},
64+
});

0 commit comments

Comments
 (0)