Skip to content

Commit 3a64aea

Browse files
authored
feat: create prefer-to-be rule (#864)
1 parent 6940488 commit 3a64aea

File tree

7 files changed

+499
-3
lines changed

7 files changed

+499
-3
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ installations requiring long-term consistency.
182182
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | |
183183
| [prefer-spy-on](docs/rules/prefer-spy-on.md) | Suggest using `jest.spyOn()` | | ![fixable][] |
184184
| [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | ![suggest][] |
185+
| [prefer-to-be](docs/rules/prefer-to-be.md) | Suggest using `toBe()` for primitive literals | | ![fixable][] |
185186
| [prefer-to-be-null](docs/rules/prefer-to-be-null.md) | Suggest using `toBeNull()` | ![style][] | ![fixable][] |
186187
| [prefer-to-be-undefined](docs/rules/prefer-to-be-undefined.md) | Suggest using `toBeUndefined()` | ![style][] | ![fixable][] |
187188
| [prefer-to-contain](docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | ![style][] | ![fixable][] |

docs/rules/prefer-to-be.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Suggest using `toBe()` for primitive literals (`prefer-to-be`)
2+
3+
When asserting against primitive literals such as numbers and strings, the
4+
equality matchers all operate the same, but read slightly differently in code.
5+
6+
This rule recommends using the `toBe` matcher in these situations, as it forms
7+
the most grammatically natural sentence. For `null`, `undefined`, and `NaN` this
8+
rule recommends using their specific `toBe` matchers, as they give better error
9+
messages as well.
10+
11+
## Rule details
12+
13+
This rule triggers a warning if `toEqual()` or `toStrictEqual()` are used to
14+
assert a primitive literal value such as numbers, strings, and booleans.
15+
16+
The following patterns are considered warnings:
17+
18+
```js
19+
expect(value).not.toEqual(5);
20+
expect(getMessage()).toStrictEqual('hello world');
21+
expect(loadMessage()).resolves.toEqual('hello world');
22+
```
23+
24+
The following pattern is not warning:
25+
26+
```js
27+
expect(value).not.toBe(5);
28+
expect(getMessage()).toBe('hello world');
29+
expect(loadMessage()).resolves.toBe('hello world');
30+
expect(didError).not.toBe(true);
31+
32+
expect(catchError()).toStrictEqual({ message: 'oh noes!' });
33+
```
34+
35+
For `null`, `undefined`, and `NaN`, this rule triggers a warning if `toBe` is
36+
used to assert against those literal values instead of their more specific
37+
`toBe` counterparts:
38+
39+
```js
40+
expect(value).not.toBe(undefined);
41+
expect(getMessage()).toBe(null);
42+
expect(countMessages()).resolves.not.toBe(NaN);
43+
```
44+
45+
The following pattern is not warning:
46+
47+
```js
48+
expect(value).toBeDefined();
49+
expect(getMessage()).toBeNull();
50+
expect(countMessages()).resolves.not.toBeNaN();
51+
52+
expect(catchError()).toStrictEqual({ message: undefined });
53+
```

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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Object {
4040
"jest/prefer-hooks-on-top": "error",
4141
"jest/prefer-spy-on": "error",
4242
"jest/prefer-strict-equal": "error",
43+
"jest/prefer-to-be": "error",
4344
"jest/prefer-to-be-null": "error",
4445
"jest/prefer-to-be-undefined": "error",
4546
"jest/prefer-to-contain": "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 = 46;
5+
const numberOfRules = 47;
66
const ruleNames = Object.keys(plugin.rules);
77
const deprecatedRules = Object.entries(plugin.rules)
88
.filter(([, rule]) => rule.meta.deprecated)
+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { TSESLint } from '@typescript-eslint/experimental-utils';
2+
import rule from '../prefer-to-be';
3+
4+
const ruleTester = new TSESLint.RuleTester();
5+
6+
ruleTester.run('prefer-to-be', rule, {
7+
valid: [
8+
'expect(null).toBeNull();',
9+
'expect(null).not.toBeNull();',
10+
'expect(null).toBe(1);',
11+
'expect(obj).toStrictEqual([ x, 1 ]);',
12+
'expect(obj).toStrictEqual({ x: 1 });',
13+
'expect(obj).not.toStrictEqual({ x: 1 });',
14+
'expect(value).toMatchSnapshot();',
15+
"expect(catchError()).toStrictEqual({ message: 'oh noes!' })",
16+
'expect("something");',
17+
],
18+
invalid: [
19+
{
20+
code: 'expect(value).toEqual("my string");',
21+
output: 'expect(value).toBe("my string");',
22+
errors: [{ messageId: 'useToBe', column: 15, line: 1 }],
23+
},
24+
{
25+
code: 'expect(value).toStrictEqual("my string");',
26+
output: 'expect(value).toBe("my string");',
27+
errors: [{ messageId: 'useToBe', column: 15, line: 1 }],
28+
},
29+
{
30+
code: 'expect(value).toStrictEqual(1);',
31+
output: 'expect(value).toBe(1);',
32+
errors: [{ messageId: 'useToBe', column: 15, line: 1 }],
33+
},
34+
{
35+
code: 'expect(loadMessage()).resolves.toStrictEqual("hello world");',
36+
output: 'expect(loadMessage()).resolves.toBe("hello world");',
37+
errors: [{ messageId: 'useToBe', column: 32, line: 1 }],
38+
},
39+
{
40+
code: 'expect(loadMessage()).resolves.toStrictEqual(false);',
41+
output: 'expect(loadMessage()).resolves.toBe(false);',
42+
errors: [{ messageId: 'useToBe', column: 32, line: 1 }],
43+
},
44+
],
45+
});
46+
47+
ruleTester.run('prefer-to-be: null', rule, {
48+
valid: [
49+
'expect(null).toBeNull();',
50+
'expect(null).not.toBeNull();',
51+
'expect(null).toBe(1);',
52+
'expect(obj).toStrictEqual([ x, 1 ]);',
53+
'expect(obj).toStrictEqual({ x: 1 });',
54+
'expect(obj).not.toStrictEqual({ x: 1 });',
55+
'expect(value).toMatchSnapshot();',
56+
"expect(catchError()).toStrictEqual({ message: 'oh noes!' })",
57+
'expect("something");',
58+
//
59+
'expect(null).not.toEqual();',
60+
'expect(null).toBe();',
61+
'expect(null).toMatchSnapshot();',
62+
'expect("a string").toMatchSnapshot(null);',
63+
'expect("a string").not.toMatchSnapshot();',
64+
'expect(null).toBe',
65+
],
66+
invalid: [
67+
{
68+
code: 'expect(null).toBe(null);',
69+
output: 'expect(null).toBeNull();',
70+
errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }],
71+
},
72+
{
73+
code: 'expect(null).toEqual(null);',
74+
output: 'expect(null).toBeNull();',
75+
errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }],
76+
},
77+
{
78+
code: 'expect(null).toStrictEqual(null);',
79+
output: 'expect(null).toBeNull();',
80+
errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }],
81+
},
82+
{
83+
code: 'expect("a string").not.toBe(null);',
84+
output: 'expect("a string").not.toBeNull();',
85+
errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }],
86+
},
87+
{
88+
code: 'expect("a string").not.toEqual(null);',
89+
output: 'expect("a string").not.toBeNull();',
90+
errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }],
91+
},
92+
{
93+
code: 'expect("a string").not.toStrictEqual(null);',
94+
output: 'expect("a string").not.toBeNull();',
95+
errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }],
96+
},
97+
],
98+
});
99+
100+
ruleTester.run('prefer-to-be: undefined', rule, {
101+
valid: [
102+
'expect(undefined).toBeUndefined();',
103+
'expect(true).toBeDefined();',
104+
'expect({}).toEqual({});',
105+
'expect(something).toBe()',
106+
'expect(something).toBe(somethingElse)',
107+
'expect(something).toEqual(somethingElse)',
108+
'expect(something).not.toBe(somethingElse)',
109+
'expect(something).not.toEqual(somethingElse)',
110+
'expect(undefined).toBe',
111+
'expect("something");',
112+
],
113+
114+
invalid: [
115+
{
116+
code: 'expect(undefined).toBe(undefined);',
117+
output: 'expect(undefined).toBeUndefined();',
118+
errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }],
119+
},
120+
{
121+
code: 'expect(undefined).toEqual(undefined);',
122+
output: 'expect(undefined).toBeUndefined();',
123+
errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }],
124+
},
125+
{
126+
code: 'expect(undefined).toStrictEqual(undefined);',
127+
output: 'expect(undefined).toBeUndefined();',
128+
errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }],
129+
},
130+
{
131+
code: 'expect("a string").not.toBe(undefined);',
132+
output: 'expect("a string").toBeDefined();',
133+
errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }],
134+
},
135+
{
136+
code: 'expect("a string").not.toEqual(undefined);',
137+
output: 'expect("a string").toBeDefined();',
138+
errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }],
139+
},
140+
{
141+
code: 'expect("a string").not.toStrictEqual(undefined);',
142+
output: 'expect("a string").toBeDefined();',
143+
errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }],
144+
},
145+
],
146+
});
147+
148+
ruleTester.run('prefer-to-be: NaN', rule, {
149+
valid: [
150+
'expect(NaN).toBeNaN();',
151+
'expect(true).not.toBeNaN();',
152+
'expect({}).toEqual({});',
153+
'expect(something).toBe()',
154+
'expect(something).toBe(somethingElse)',
155+
'expect(something).toEqual(somethingElse)',
156+
'expect(something).not.toBe(somethingElse)',
157+
'expect(something).not.toEqual(somethingElse)',
158+
'expect(undefined).toBe',
159+
'expect("something");',
160+
],
161+
invalid: [
162+
{
163+
code: 'expect(NaN).toBe(NaN);',
164+
output: 'expect(NaN).toBeNaN();',
165+
errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }],
166+
},
167+
{
168+
code: 'expect(NaN).toEqual(NaN);',
169+
output: 'expect(NaN).toBeNaN();',
170+
errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }],
171+
},
172+
{
173+
code: 'expect(NaN).toStrictEqual(NaN);',
174+
output: 'expect(NaN).toBeNaN();',
175+
errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }],
176+
},
177+
{
178+
code: 'expect("a string").not.toBe(NaN);',
179+
output: 'expect("a string").not.toBeNaN();',
180+
errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }],
181+
},
182+
{
183+
code: 'expect("a string").not.toEqual(NaN);',
184+
output: 'expect("a string").not.toBeNaN();',
185+
errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }],
186+
},
187+
{
188+
code: 'expect("a string").not.toStrictEqual(NaN);',
189+
output: 'expect("a string").not.toBeNaN();',
190+
errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }],
191+
},
192+
],
193+
});
194+
195+
ruleTester.run('prefer-to-be: undefined vs defined', rule, {
196+
valid: [
197+
'expect(NaN).toBeNaN();',
198+
'expect(true).not.toBeNaN();',
199+
'expect({}).toEqual({});',
200+
'expect(something).toBe()',
201+
'expect(something).toBe(somethingElse)',
202+
'expect(something).toEqual(somethingElse)',
203+
'expect(something).not.toBe(somethingElse)',
204+
'expect(something).not.toEqual(somethingElse)',
205+
'expect(undefined).toBe',
206+
'expect("something");',
207+
],
208+
invalid: [
209+
{
210+
code: 'expect(undefined).not.toBeDefined();',
211+
output: 'expect(undefined).toBeUndefined();',
212+
errors: [{ messageId: 'useToBeUndefined', column: 23, line: 1 }],
213+
},
214+
{
215+
code: 'expect(undefined).resolves.not.toBeDefined();',
216+
output: 'expect(undefined).resolves.toBeUndefined();',
217+
errors: [{ messageId: 'useToBeUndefined', column: 32, line: 1 }],
218+
},
219+
{
220+
code: 'expect("a string").not.toBeUndefined();',
221+
output: 'expect("a string").toBeDefined();',
222+
errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }],
223+
},
224+
{
225+
code: 'expect("a string").rejects.not.toBeUndefined();',
226+
output: 'expect("a string").rejects.toBeDefined();',
227+
errors: [{ messageId: 'useToBeDefined', column: 32, line: 1 }],
228+
},
229+
],
230+
});
231+
232+
new TSESLint.RuleTester({
233+
parser: require.resolve('@typescript-eslint/parser'),
234+
}).run('prefer-to-be: typescript edition', rule, {
235+
valid: [
236+
"(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()",
237+
],
238+
invalid: [
239+
{
240+
code: 'expect(null).toEqual(1 as unknown as string as unknown as any);',
241+
output: 'expect(null).toBe(1 as unknown as string as unknown as any);',
242+
errors: [{ messageId: 'useToBe', column: 14, line: 1 }],
243+
},
244+
{
245+
code: 'expect("a string").not.toStrictEqual("string" as number);',
246+
output: 'expect("a string").not.toBe("string" as number);',
247+
errors: [{ messageId: 'useToBe', column: 24, line: 1 }],
248+
},
249+
{
250+
code: 'expect(null).toBe(null as unknown as string as unknown as any);',
251+
output: 'expect(null).toBeNull();',
252+
errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }],
253+
},
254+
{
255+
code: 'expect("a string").not.toEqual(null as number);',
256+
output: 'expect("a string").not.toBeNull();',
257+
errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }],
258+
},
259+
{
260+
code: 'expect(undefined).toBe(undefined as unknown as string as any);',
261+
output: 'expect(undefined).toBeUndefined();',
262+
errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }],
263+
},
264+
{
265+
code: 'expect("a string").toEqual(undefined as number);',
266+
output: 'expect("a string").toBeUndefined();',
267+
errors: [{ messageId: 'useToBeUndefined', column: 20, line: 1 }],
268+
},
269+
],
270+
});

0 commit comments

Comments
 (0)