Skip to content

Commit 018a533

Browse files
committed
feat: create prefer-each rule
1 parent 5508c95 commit 018a533

File tree

7 files changed

+462
-2
lines changed

7 files changed

+462
-2
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ installations requiring long-term consistency.
226226
| [no-test-return-statement](docs/rules/no-test-return-statement.md) | Disallow explicitly returning from tests | | |
227227
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` | | |
228228
| [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | ![fixable][] |
229+
| [prefer-each](docs/rules/prefer-each.md) | Prefer using `.each` rather than manual loops | | |
229230
| [prefer-equality-matcher](docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | ![suggest][] |
230231
| [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | ![suggest][] |
231232
| [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | ![fixable][] |

docs/rules/prefer-each.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Prefer using `.each` rather than manual loops (`prefer-each`)
2+
3+
Reports where you might be able to use `.each` instead of native loops.
4+
5+
## Rule details
6+
7+
This rule triggers a warning if you use test case functions like `describe`,
8+
`test`, and `it`, in a native loop - generally you should be able to use `.each`
9+
instead which gives better output and makes it easier to run specific cases.
10+
11+
Examples of **incorrect** code for this rule:
12+
13+
```js
14+
for (const number of getNumbers()) {
15+
it('is greater than five', function () {
16+
expect(number).toBeGreaterThan(5);
17+
});
18+
}
19+
20+
for (const [input, expected] of data) {
21+
beforeEach(() => setupSomething(input));
22+
23+
test(`results in ${expected}`, () => {
24+
expect(doSomething()).toBe(expected);
25+
});
26+
}
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```js
32+
it.each(getNumbers())(
33+
'only returns numbers that are greater than seven',
34+
number => {
35+
expect(number).toBeGreaterThan(7);
36+
},
37+
);
38+
39+
describe.each(data)('when input is %s', ([input, expected]) => {
40+
beforeEach(() => setupSomething(input));
41+
42+
test(`results in ${expected}`, () => {
43+
expect(doSomething()).toBe(expected);
44+
});
45+
});
46+
47+
// we don't warn on loops _in_ test functions because those typically involve
48+
// complex setup that is better done in the test function itself
49+
it('returns numbers that are greater than five', () => {
50+
for (const number of getNumbers()) {
51+
expect(number).toBeGreaterThan(5);
52+
}
53+
});
54+
```

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

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Object {
3737
"jest/no-test-return-statement": "error",
3838
"jest/prefer-called-with": "error",
3939
"jest/prefer-comparison-matcher": "error",
40+
"jest/prefer-each": "error",
4041
"jest/prefer-equality-matcher": "error",
4142
"jest/prefer-expect-assertions": "error",
4243
"jest/prefer-expect-resolves": "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 = 50;
5+
const numberOfRules = 51;
66
const ruleNames = Object.keys(plugin.rules);
77
const deprecatedRules = Object.entries(plugin.rules)
88
.filter(([, rule]) => rule.meta.deprecated)
+310
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import { TSESLint } from '@typescript-eslint/utils';
2+
import dedent from 'dedent';
3+
import rule from '../prefer-each';
4+
import { espreeParser } from './test-utils';
5+
6+
const ruleTester = new TSESLint.RuleTester({
7+
parser: espreeParser,
8+
parserOptions: {
9+
ecmaVersion: 2015,
10+
},
11+
});
12+
13+
ruleTester.run('prefer-each', rule, {
14+
valid: [
15+
'it("is true", () => { expect(true).toBe(false) });',
16+
dedent`
17+
it.each(getNumbers())("only returns numbers that are greater than seven", number => {
18+
expect(number).toBeGreaterThan(7);
19+
});
20+
`,
21+
// while these cases could be done with .each, it's reasonable to have more
22+
// complex cases that would not look good in .each, so we consider this valid
23+
dedent`
24+
it("returns numbers that are greater than five", function () {
25+
for (const number of getNumbers()) {
26+
expect(number).toBeGreaterThan(5);
27+
}
28+
});
29+
`,
30+
dedent`
31+
it("returns things that are less than ten", function () {
32+
for (const thing in things) {
33+
expect(thing).toBeLessThan(10);
34+
}
35+
});
36+
`,
37+
dedent`
38+
it("only returns numbers that are greater than seven", function () {
39+
const numbers = getNumbers();
40+
41+
for (let i = 0; i < numbers.length; i++) {
42+
expect(numbers[i]).toBeGreaterThan(7);
43+
}
44+
});
45+
`,
46+
],
47+
invalid: [
48+
{
49+
code: dedent`
50+
for (const [input, expected] of data) {
51+
it(\`results in $\{expected}\`, () => {
52+
expect(fn(input)).toBe(expected)
53+
});
54+
}
55+
`,
56+
errors: [
57+
{
58+
data: { fn: 'it' },
59+
messageId: 'preferEach',
60+
},
61+
],
62+
},
63+
{
64+
code: dedent`
65+
for (const [input, expected] of data) {
66+
describe(\`when the input is $\{input}\`, () => {
67+
it(\`results in $\{expected}\`, () => {
68+
expect(fn(input)).toBe(expected)
69+
});
70+
});
71+
}
72+
`,
73+
errors: [
74+
{
75+
data: { fn: 'describe' },
76+
messageId: 'preferEach',
77+
},
78+
],
79+
},
80+
{
81+
code: dedent`
82+
for (const [input, expected] of data) {
83+
describe(\`when the input is $\{input}\`, () => {
84+
it(\`results in $\{expected}\`, () => {
85+
expect(fn(input)).toBe(expected)
86+
});
87+
});
88+
}
89+
90+
for (const [input, expected] of data) {
91+
it.skip(\`results in $\{expected}\`, () => {
92+
expect(fn(input)).toBe(expected)
93+
});
94+
}
95+
`,
96+
errors: [
97+
{
98+
data: { fn: 'describe' },
99+
messageId: 'preferEach',
100+
},
101+
{
102+
data: { fn: 'it' },
103+
messageId: 'preferEach',
104+
},
105+
],
106+
},
107+
{
108+
code: dedent`
109+
for (const [input, expected] of data) {
110+
it.skip(\`results in $\{expected}\`, () => {
111+
expect(fn(input)).toBe(expected)
112+
});
113+
}
114+
`,
115+
errors: [
116+
{
117+
data: { fn: 'it' },
118+
messageId: 'preferEach',
119+
},
120+
],
121+
},
122+
{
123+
code: dedent`
124+
it('is true', () => {
125+
expect(true).toBe(false);
126+
});
127+
128+
for (const [input, expected] of data) {
129+
it.skip(\`results in $\{expected}\`, () => {
130+
expect(fn(input)).toBe(expected)
131+
});
132+
}
133+
`,
134+
errors: [
135+
{
136+
data: { fn: 'it' },
137+
messageId: 'preferEach',
138+
},
139+
],
140+
},
141+
{
142+
code: dedent`
143+
for (const [input, expected] of data) {
144+
it.skip(\`results in $\{expected}\`, () => {
145+
expect(fn(input)).toBe(expected)
146+
});
147+
}
148+
149+
it('is true', () => {
150+
expect(true).toBe(false);
151+
});
152+
`,
153+
errors: [
154+
{
155+
data: { fn: 'it' },
156+
messageId: 'preferEach',
157+
},
158+
],
159+
},
160+
{
161+
code: dedent`
162+
it('is true', () => {
163+
expect(true).toBe(false);
164+
});
165+
166+
for (const [input, expected] of data) {
167+
it.skip(\`results in $\{expected}\`, () => {
168+
expect(fn(input)).toBe(expected)
169+
});
170+
}
171+
172+
it('is true', () => {
173+
expect(true).toBe(false);
174+
});
175+
`,
176+
errors: [
177+
{
178+
data: { fn: 'it' },
179+
messageId: 'preferEach',
180+
},
181+
],
182+
},
183+
{
184+
code: dedent`
185+
for (const [input, expected] of data) {
186+
it(\`results in $\{expected}\`, () => {
187+
expect(fn(input)).toBe(expected)
188+
});
189+
190+
it(\`results in $\{expected}\`, () => {
191+
expect(fn(input)).toBe(expected)
192+
});
193+
}
194+
`,
195+
errors: [
196+
{
197+
data: { fn: 'describe' },
198+
messageId: 'preferEach',
199+
},
200+
],
201+
},
202+
{
203+
code: dedent`
204+
for (const [input, expected] of data) {
205+
it(\`results in $\{expected}\`, () => {
206+
expect(fn(input)).toBe(expected)
207+
});
208+
}
209+
210+
for (const [input, expected] of data) {
211+
it(\`results in $\{expected}\`, () => {
212+
expect(fn(input)).toBe(expected)
213+
});
214+
}
215+
`,
216+
errors: [
217+
{
218+
data: { fn: 'it' },
219+
messageId: 'preferEach',
220+
},
221+
{
222+
data: { fn: 'it' },
223+
messageId: 'preferEach',
224+
},
225+
],
226+
},
227+
{
228+
code: dedent`
229+
for (const [input, expected] of data) {
230+
it(\`results in $\{expected}\`, () => {
231+
expect(fn(input)).toBe(expected)
232+
});
233+
}
234+
235+
for (const [input, expected] of data) {
236+
test(\`results in $\{expected}\`, () => {
237+
expect(fn(input)).toBe(expected)
238+
});
239+
}
240+
`,
241+
errors: [
242+
{
243+
data: { fn: 'it' },
244+
messageId: 'preferEach',
245+
},
246+
{
247+
data: { fn: 'it' },
248+
messageId: 'preferEach',
249+
},
250+
],
251+
},
252+
{
253+
code: dedent`
254+
for (const [input, expected] of data) {
255+
beforeEach(() => setupSomething(input));
256+
257+
test(\`results in $\{expected}\`, () => {
258+
expect(doSomething()).toBe(expected)
259+
});
260+
}
261+
`,
262+
errors: [
263+
{
264+
data: { fn: 'describe' },
265+
messageId: 'preferEach',
266+
},
267+
],
268+
},
269+
{
270+
code: dedent`
271+
for (const [input, expected] of data) {
272+
it("only returns numbers that are greater than seven", function () {
273+
const numbers = getNumbers(input);
274+
275+
for (let i = 0; i < numbers.length; i++) {
276+
expect(numbers[i]).toBeGreaterThan(7);
277+
}
278+
});
279+
}
280+
`,
281+
errors: [
282+
{
283+
data: { fn: 'it' },
284+
messageId: 'preferEach',
285+
},
286+
],
287+
},
288+
{
289+
code: dedent`
290+
for (const [input, expected] of data) {
291+
beforeEach(() => setupSomething(input));
292+
293+
it("only returns numbers that are greater than seven", function () {
294+
const numbers = getNumbers();
295+
296+
for (let i = 0; i < numbers.length; i++) {
297+
expect(numbers[i]).toBeGreaterThan(7);
298+
}
299+
});
300+
}
301+
`,
302+
errors: [
303+
{
304+
data: { fn: 'describe' },
305+
messageId: 'preferEach',
306+
},
307+
],
308+
},
309+
],
310+
});

0 commit comments

Comments
 (0)