Skip to content

Commit 8067405

Browse files
authored
feat: create max-nested-describe rule (#845)
* feat: create max-nested-describe rule * feat: support only object schema * refactor: simplify call expression type check * test: max 0 option * test: update number of rules * test: update snapshot * test: describe modifiers * docs: fix examples syntax * docs: fix syntax consistency * test: fix missing brackets * refactor: add spacing around message placeholders * feat: increase default max to 5 * docs: update with new default max of 5 * test: add cases for 5 max
1 parent 19e3a6e commit 8067405

File tree

6 files changed

+437
-1
lines changed

6 files changed

+437
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ installations requiring long-term consistency.
135135
| [consistent-test-it](docs/rules/consistent-test-it.md) | Have control over `test` and `it` usages | | ![fixable][] |
136136
| [expect-expect](docs/rules/expect-expect.md) | Enforce assertion to be made in a test body | ![recommended][] | |
137137
| [lowercase-name](docs/rules/lowercase-name.md) | Enforce lowercase test names | | ![fixable][] |
138+
| [max-nested-describe](docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | |
138139
| [no-alias-methods](docs/rules/no-alias-methods.md) | Disallow alias methods | ![style][] | ![fixable][] |
139140
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | ![recommended][] | |
140141
| [no-conditional-expect](docs/rules/no-conditional-expect.md) | Prevent calling `expect` conditionally | ![recommended][] | |

docs/rules/max-nested-describe.md

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Enforces a maximum depth to nested describe calls (`max-nested-describe`)
2+
3+
While it's useful to be able to group your tests together within the same file
4+
using `describe()`, having too many levels of nesting throughout your tests make
5+
them difficult to read.
6+
7+
## Rule Details
8+
9+
This rule enforces a maximum depth to nested `describe()` calls to improve code
10+
clarity in your tests.
11+
12+
The following patterns are considered warnings (with the default option of
13+
`{ "max": 5 } `):
14+
15+
```js
16+
describe('foo', () => {
17+
describe('bar', () => {
18+
describe('baz', () => {
19+
describe('qux', () => {
20+
describe('quxx', () => {
21+
describe('too many', () => {
22+
it('should get something', () => {
23+
expect(getSomething()).toBe('Something');
24+
});
25+
});
26+
});
27+
});
28+
});
29+
});
30+
});
31+
32+
describe('foo', function () {
33+
describe('bar', function () {
34+
describe('baz', function () {
35+
describe('qux', function () {
36+
describe('quxx', function () {
37+
describe('too many', function () {
38+
it('should get something', () => {
39+
expect(getSomething()).toBe('Something');
40+
});
41+
});
42+
});
43+
});
44+
});
45+
});
46+
});
47+
```
48+
49+
The following patterns are **not** considered warnings (with the default option
50+
of `{ "max": 5 } `):
51+
52+
```js
53+
describe('foo', () => {
54+
describe('bar', () => {
55+
it('should get something', () => {
56+
expect(getSomething()).toBe('Something');
57+
});
58+
});
59+
60+
describe('qux', () => {
61+
it('should get something', () => {
62+
expect(getSomething()).toBe('Something');
63+
});
64+
});
65+
});
66+
67+
describe('foo2', function () {
68+
it('should get something', () => {
69+
expect(getSomething()).toBe('Something');
70+
});
71+
});
72+
73+
describe('foo', function () {
74+
describe('bar', function () {
75+
describe('baz', function () {
76+
describe('qux', function () {
77+
describe('this is the limit', function () {
78+
it('should get something', () => {
79+
expect(getSomething()).toBe('Something');
80+
});
81+
});
82+
});
83+
});
84+
});
85+
});
86+
```
87+
88+
## Options
89+
90+
```json
91+
{
92+
"jest/max-nested-describe": [
93+
"error",
94+
{
95+
"max": 5
96+
}
97+
]
98+
}
99+
```
100+
101+
### `max`
102+
103+
Enforces a maximum depth for nested `describe()`.
104+
105+
This has a default value of `5`.
106+
107+
Examples of patterns **not** considered warnings with options set to
108+
`{ "max": 2 }`:
109+
110+
```js
111+
describe('foo', () => {
112+
describe('bar', () => {
113+
it('should get something', () => {
114+
expect(getSomething()).toBe('Something');
115+
});
116+
});
117+
});
118+
119+
describe('foo2', function()) {
120+
describe('bar2', function() {
121+
it('should get something', function() {
122+
expect(getSomething()).toBe('Something');
123+
});
124+
125+
it('should get else', function() {
126+
expect(getSomething()).toBe('Something');
127+
});
128+
});
129+
});
130+
131+
```

src/__tests__/__snapshots__/rules.test.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Object {
1313
"jest/consistent-test-it": "error",
1414
"jest/expect-expect": "error",
1515
"jest/lowercase-name": "error",
16+
"jest/max-nested-describe": "error",
1617
"jest/no-alias-methods": "error",
1718
"jest/no-commented-out-tests": "error",
1819
"jest/no-conditional-expect": "error",

src/__tests__/rules.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { existsSync } from 'fs';
22
import { resolve } from 'path';
33
import plugin from '../';
44

5-
const numberOfRules = 45;
5+
const numberOfRules = 46;
66
const ruleNames = Object.keys(plugin.rules);
77
const deprecatedRules = Object.entries(plugin.rules)
88
.filter(([, rule]) => rule.meta.deprecated)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { TSESLint } from '@typescript-eslint/experimental-utils';
2+
import dedent from 'dedent';
3+
import resolveFrom from 'resolve-from';
4+
import rule from '../max-nested-describe';
5+
6+
const ruleTester = new TSESLint.RuleTester({
7+
parser: resolveFrom(require.resolve('eslint'), 'espree'),
8+
parserOptions: {
9+
ecmaVersion: 2017,
10+
},
11+
});
12+
13+
ruleTester.run('max-nested-describe', rule, {
14+
valid: [
15+
dedent`
16+
describe('foo', function() {
17+
describe('bar', function () {
18+
describe('baz', function () {
19+
describe('qux', function () {
20+
describe('qux', function () {
21+
it('should get something', () => {
22+
expect(getSomething()).toBe('Something');
23+
});
24+
})
25+
})
26+
})
27+
})
28+
});
29+
`,
30+
dedent`
31+
describe('foo', function() {
32+
describe('bar', function () {
33+
describe('baz', function () {
34+
describe('qux', function () {
35+
describe('qux', function () {
36+
it('should get something', () => {
37+
expect(getSomething()).toBe('Something');
38+
});
39+
});
40+
41+
fdescribe('qux', () => {
42+
it('something', async () => {
43+
expect('something').toBe('something');
44+
});
45+
});
46+
})
47+
})
48+
})
49+
});
50+
`,
51+
dedent`
52+
describe('foo', () => {
53+
describe('bar', () => {
54+
it('hello', async () => {
55+
expect('hello').toBe('hello');
56+
});
57+
});
58+
});
59+
60+
xdescribe('foo', function() {
61+
describe('bar', function() {
62+
it('something', async () => {
63+
expect('something').toBe('something');
64+
});
65+
});
66+
});
67+
`,
68+
{
69+
code: dedent`
70+
describe('foo', () => {
71+
describe.only('bar', () => {
72+
describe.skip('baz', () => {
73+
it('something', async () => {
74+
expect('something').toBe('something');
75+
});
76+
});
77+
});
78+
});
79+
`,
80+
options: [{ max: 3 }],
81+
},
82+
{
83+
code: dedent`
84+
it('something', async () => {
85+
expect('something').toBe('something');
86+
});
87+
`,
88+
options: [{ max: 0 }],
89+
},
90+
dedent`
91+
describe('foo', () => {
92+
describe.each(['hello', 'world'])("%s", (a) => {});
93+
});
94+
`,
95+
dedent`
96+
describe('foo', () => {
97+
describe.each\`
98+
foo | bar
99+
${1} | ${2}
100+
\`('$foo $bar', ({ foo, bar }) => {});
101+
});
102+
`,
103+
],
104+
invalid: [
105+
{
106+
code: dedent`
107+
describe('foo', function() {
108+
describe('bar', function () {
109+
describe('baz', function () {
110+
describe('qux', function () {
111+
describe('quxx', function () {
112+
describe('over limit', function () {
113+
it('should get something', () => {
114+
expect(getSomething()).toBe('Something');
115+
});
116+
});
117+
});
118+
});
119+
});
120+
});
121+
});
122+
`,
123+
errors: [{ messageId: 'exceededMaxDepth', line: 6, column: 11 }],
124+
},
125+
{
126+
code: dedent`
127+
describe('foo', () => {
128+
describe('bar', () => {
129+
describe('baz', () => {
130+
describe('baz1', () => {
131+
describe('baz2', () => {
132+
describe('baz3', () => {
133+
it('should get something', () => {
134+
expect(getSomething()).toBe('Something');
135+
});
136+
});
137+
138+
describe('baz4', () => {
139+
it('should get something', () => {
140+
expect(getSomething()).toBe('Something');
141+
});
142+
});
143+
});
144+
});
145+
});
146+
147+
describe('qux', function () {
148+
it('should get something', () => {
149+
expect(getSomething()).toBe('Something');
150+
});
151+
});
152+
})
153+
});
154+
`,
155+
errors: [
156+
{ messageId: 'exceededMaxDepth', line: 6, column: 11 },
157+
{ messageId: 'exceededMaxDepth', line: 12, column: 11 },
158+
],
159+
},
160+
{
161+
code: dedent`
162+
fdescribe('foo', () => {
163+
describe.only('bar', () => {
164+
describe.skip('baz', () => {
165+
it('should get something', () => {
166+
expect(getSomething()).toBe('Something');
167+
});
168+
});
169+
170+
describe('baz', () => {
171+
it('should get something', () => {
172+
expect(getSomething()).toBe('Something');
173+
});
174+
});
175+
});
176+
});
177+
178+
xdescribe('qux', () => {
179+
it('should get something', () => {
180+
expect(getSomething()).toBe('Something');
181+
});
182+
});
183+
`,
184+
options: [{ max: 2 }],
185+
errors: [
186+
{ messageId: 'exceededMaxDepth', line: 3, column: 5 },
187+
{ messageId: 'exceededMaxDepth', line: 9, column: 5 },
188+
],
189+
},
190+
{
191+
code: dedent`
192+
describe('qux', () => {
193+
it('should get something', () => {
194+
expect(getSomething()).toBe('Something');
195+
});
196+
});
197+
`,
198+
options: [{ max: 0 }],
199+
errors: [{ messageId: 'exceededMaxDepth', line: 1, column: 1 }],
200+
},
201+
{
202+
code: dedent`
203+
describe('foo', () => {
204+
describe.each(['hello', 'world'])("%s", (a) => {});
205+
});
206+
`,
207+
options: [{ max: 1 }],
208+
errors: [{ messageId: 'exceededMaxDepth', line: 2, column: 3 }],
209+
},
210+
{
211+
code: dedent`
212+
describe('foo', () => {
213+
describe.each\`
214+
foo | bar
215+
${1} | ${2}
216+
\`('$foo $bar', ({ foo, bar }) => {});
217+
});
218+
`,
219+
options: [{ max: 1 }],
220+
errors: [{ messageId: 'exceededMaxDepth', line: 2, column: 3 }],
221+
},
222+
],
223+
});

0 commit comments

Comments
 (0)