eslint-plugin-jest 25.3.4 → 25.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +2 -0
- package/docs/rules/prefer-comparison-matcher.md +55 -0
- package/docs/rules/prefer-equality-matcher.md +29 -0
- package/docs/rules/prefer-expect-assertions.md +126 -0
- package/docs/rules/valid-expect.md +13 -0
- package/lib/rules/prefer-comparison-matcher.js +139 -0
- package/lib/rules/prefer-equality-matcher.js +98 -0
- package/lib/rules/prefer-expect-assertions.js +84 -2
- package/lib/rules/valid-expect.js +12 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# [25.7.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.6.0...v25.7.0) (2022-01-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* create `prefer-equality-matcher` rule ([#1016](https://github.com/jest-community/eslint-plugin-jest/issues/1016)) ([341353b](https://github.com/jest-community/eslint-plugin-jest/commit/341353bc7d57685cc5e0b31501d6ca336a0dbaf0))
|
|
7
|
+
* **valid-expect:** support `asyncMatchers` option and default to `jest-extended` matchers ([#1018](https://github.com/jest-community/eslint-plugin-jest/issues/1018)) ([c82205a](https://github.com/jest-community/eslint-plugin-jest/commit/c82205a73a4e8de315a2ad4d413b146e27c14a34))
|
|
8
|
+
|
|
9
|
+
# [25.6.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.5.0...v25.6.0) (2022-01-15)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* create `prefer-comparison-matcher` rule ([#1015](https://github.com/jest-community/eslint-plugin-jest/issues/1015)) ([eb11876](https://github.com/jest-community/eslint-plugin-jest/commit/eb118761a422b3589311113cd827a6be437f5bb5))
|
|
15
|
+
|
|
16
|
+
# [25.5.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.4.0...v25.5.0) (2022-01-15)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* **prefer-expect-assertions:** support requiring only if `expect` is used in a callback ([#1028](https://github.com/jest-community/eslint-plugin-jest/issues/1028)) ([8d5fd33](https://github.com/jest-community/eslint-plugin-jest/commit/8d5fd33eed633f0c0bbdcb9e86bd2d8d7de79c4b))
|
|
22
|
+
|
|
23
|
+
# [25.4.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.4...v25.4.0) (2022-01-15)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* **prefer-expect-assertions:** support requiring only if `expect` is used in a loop ([#1013](https://github.com/jest-community/eslint-plugin-jest/issues/1013)) ([e6f4f8a](https://github.com/jest-community/eslint-plugin-jest/commit/e6f4f8aaf7664bcf9d9d5549c3c43b1b09f49461))
|
|
29
|
+
|
|
1
30
|
## [25.3.4](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.3...v25.3.4) (2022-01-01)
|
|
2
31
|
|
|
3
32
|
|
package/README.md
CHANGED
|
@@ -177,6 +177,8 @@ installations requiring long-term consistency.
|
|
|
177
177
|
| [no-test-prefixes](docs/rules/no-test-prefixes.md) | Use `.only` and `.skip` over `f` and `x` | ![recommended][] | ![fixable][] |
|
|
178
178
|
| [no-test-return-statement](docs/rules/no-test-return-statement.md) | Disallow explicitly returning from tests | | |
|
|
179
179
|
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` | | |
|
|
180
|
+
| [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | ![fixable][] |
|
|
181
|
+
| [prefer-equality-matcher](docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | ![suggest][] |
|
|
180
182
|
| [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | ![suggest][] |
|
|
181
183
|
| [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | ![fixable][] |
|
|
182
184
|
| [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Suggest using the built-in comparison matchers (`prefer-comparison-matcher`)
|
|
2
|
+
|
|
3
|
+
Jest has a number of built-in matchers for comparing numbers which allow for
|
|
4
|
+
more readable tests and error messages if an expectation fails.
|
|
5
|
+
|
|
6
|
+
## Rule details
|
|
7
|
+
|
|
8
|
+
This rule checks for comparisons in tests that could be replaced with one of the
|
|
9
|
+
following built-in comparison matchers:
|
|
10
|
+
|
|
11
|
+
- `toBeGreaterThan`
|
|
12
|
+
- `toBeGreaterThanOrEqual`
|
|
13
|
+
- `toBeLessThan`
|
|
14
|
+
- `toBeLessThanOrEqual`
|
|
15
|
+
|
|
16
|
+
Examples of **incorrect** code for this rule:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
expect(x > 5).toBe(true);
|
|
20
|
+
expect(x < 7).not.toEqual(true);
|
|
21
|
+
expect(x <= y).toStrictEqual(true);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Examples of **correct** code for this rule:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
expect(x).toBeGreaterThan(5);
|
|
28
|
+
expect(x).not.toBeLessThanOrEqual(7);
|
|
29
|
+
expect(x).toBeLessThanOrEqual(y);
|
|
30
|
+
|
|
31
|
+
// special case - see below
|
|
32
|
+
expect(x < 'Carl').toBe(true);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Note that these matchers only work with numbers and bigints, and that the rule
|
|
36
|
+
assumes that any variables on either side of the comparison operator are of one
|
|
37
|
+
of those types - this means if you're using the comparison operator with
|
|
38
|
+
strings, the fix applied by this rule will result in an error.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
expect(myName).toBeGreaterThanOrEqual(theirName); // Matcher error: received value must be a number or bigint
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The reason for this is that comparing strings with these operators is expected
|
|
45
|
+
to be very rare and would mean not being able to have an automatic fixer for
|
|
46
|
+
this rule.
|
|
47
|
+
|
|
48
|
+
If for some reason you are using these operators to compare strings, you can
|
|
49
|
+
disable this rule using an inline
|
|
50
|
+
[configuration comment](https://eslint.org/docs/user-guide/configuring/rules#disabling-rules):
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
// eslint-disable-next-line jest/prefer-comparison-matcher
|
|
54
|
+
expect(myName > theirName).toBe(true);
|
|
55
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Suggest using the built-in equality matchers (`prefer-equality-matcher`)
|
|
2
|
+
|
|
3
|
+
Jest has built-in matchers for expecting equality which allow for more readable
|
|
4
|
+
tests and error messages if an expectation fails.
|
|
5
|
+
|
|
6
|
+
## Rule details
|
|
7
|
+
|
|
8
|
+
This rule checks for _strict_ equality checks (`===` & `!==`) in tests that
|
|
9
|
+
could be replaced with one of the following built-in equality matchers:
|
|
10
|
+
|
|
11
|
+
- `toBe`
|
|
12
|
+
- `toEqual`
|
|
13
|
+
- `toStrictEqual`
|
|
14
|
+
|
|
15
|
+
Examples of **incorrect** code for this rule:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
expect(x === 5).toBe(true);
|
|
19
|
+
expect(name === 'Carl').not.toEqual(true);
|
|
20
|
+
expect(myObj !== thatObj).toStrictEqual(true);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Examples of **correct** code for this rule:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
expect(x).toBe(5);
|
|
27
|
+
expect(name).not.toEqual('Carl');
|
|
28
|
+
expect(myObj).toStrictEqual(thatObj);
|
|
29
|
+
```
|
|
@@ -58,6 +58,16 @@ test('my test', () => {
|
|
|
58
58
|
|
|
59
59
|
## Options
|
|
60
60
|
|
|
61
|
+
This rule can be configured to only check tests that match certain patterns that
|
|
62
|
+
typically look like `expect` calls might be missed, such as in promises or
|
|
63
|
+
loops.
|
|
64
|
+
|
|
65
|
+
By default, none of these options are enabled meaning the rule checks _every_
|
|
66
|
+
test for a call to either `expect.hasAssertions` or `expect.assertions`. If any
|
|
67
|
+
of the options are enabled the rule checks any test that matches _at least one_
|
|
68
|
+
of the patterns represented by the enabled options (think "OR" rather than
|
|
69
|
+
"AND").
|
|
70
|
+
|
|
61
71
|
#### `onlyFunctionsWithAsyncKeyword`
|
|
62
72
|
|
|
63
73
|
When `true`, this rule will only warn for tests that use the `async` keyword.
|
|
@@ -97,3 +107,119 @@ test('my test', async () => {
|
|
|
97
107
|
expect(result).toBe('foo');
|
|
98
108
|
});
|
|
99
109
|
```
|
|
110
|
+
|
|
111
|
+
#### `onlyFunctionsWithExpectInLoop`
|
|
112
|
+
|
|
113
|
+
When `true`, this rule will only warn for tests that have `expect` calls within
|
|
114
|
+
a native loop.
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"rules": {
|
|
119
|
+
"jest/prefer-expect-assertions": [
|
|
120
|
+
"warn",
|
|
121
|
+
{ "onlyFunctionsWithAsyncKeyword": true }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Examples of **incorrect** code when `'onlyFunctionsWithExpectInLoop'` is `true`:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
describe('getNumbers', () => {
|
|
131
|
+
it('only returns numbers that are greater than zero', () => {
|
|
132
|
+
const numbers = getNumbers();
|
|
133
|
+
|
|
134
|
+
for (const number in numbers) {
|
|
135
|
+
expect(number).toBeGreaterThan(0);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Examples of **correct** code when `'onlyFunctionsWithExpectInLoop'` is `true`:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
describe('getNumbers', () => {
|
|
145
|
+
it('only returns numbers that are greater than zero', () => {
|
|
146
|
+
expect.hasAssertions();
|
|
147
|
+
|
|
148
|
+
const numbers = getNumbers();
|
|
149
|
+
|
|
150
|
+
for (const number in numbers) {
|
|
151
|
+
expect(number).toBeGreaterThan(0);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns more than one number', () => {
|
|
156
|
+
expect(getNumbers().length).toBeGreaterThan(1);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `onlyFunctionsWithExpectInCallback`
|
|
162
|
+
|
|
163
|
+
When `true`, this rule will only warn for tests that have `expect` calls within
|
|
164
|
+
a callback.
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"rules": {
|
|
169
|
+
"jest/prefer-expect-assertions": [
|
|
170
|
+
"warn",
|
|
171
|
+
{ "onlyFunctionsWithExpectInCallback": true }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Examples of **incorrect** code when `'onlyFunctionsWithExpectInCallback'` is
|
|
178
|
+
`true`:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
describe('getNumbers', () => {
|
|
182
|
+
it('only returns numbers that are greater than zero', () => {
|
|
183
|
+
const numbers = getNumbers();
|
|
184
|
+
|
|
185
|
+
getNumbers().forEach(number => {
|
|
186
|
+
expect(number).toBeGreaterThan(0);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('/users', () => {
|
|
192
|
+
it.each([1, 2, 3])('returns ok', id => {
|
|
193
|
+
client.get(`/users/${id}`, response => {
|
|
194
|
+
expect(response.status).toBe(200);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Examples of **correct** code when `'onlyFunctionsWithExpectInCallback'` is
|
|
201
|
+
`true`:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
describe('getNumbers', () => {
|
|
205
|
+
it('only returns numbers that are greater than zero', () => {
|
|
206
|
+
expect.hasAssertions();
|
|
207
|
+
|
|
208
|
+
const numbers = getNumbers();
|
|
209
|
+
|
|
210
|
+
getNumbers().forEach(number => {
|
|
211
|
+
expect(number).toBeGreaterThan(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('/users', () => {
|
|
217
|
+
it.each([1, 2, 3])('returns ok', id => {
|
|
218
|
+
expect.assertions(3);
|
|
219
|
+
|
|
220
|
+
client.get(`/users/${id}`, response => {
|
|
221
|
+
expect(response.status).toBe(200);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
```
|
|
@@ -38,6 +38,11 @@ This rule is enabled by default.
|
|
|
38
38
|
type: 'boolean',
|
|
39
39
|
default: false,
|
|
40
40
|
},
|
|
41
|
+
asyncMatchers: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: { type: 'string' },
|
|
44
|
+
default: ['toResolve', 'toReject'],
|
|
45
|
+
},
|
|
41
46
|
minArgs: {
|
|
42
47
|
type: 'number',
|
|
43
48
|
minimum: 1,
|
|
@@ -78,6 +83,14 @@ test('test1', async () => {
|
|
|
78
83
|
test('test2', () => expect(Promise.resolve(2)).resolves.toBe(2));
|
|
79
84
|
```
|
|
80
85
|
|
|
86
|
+
### `asyncMatchers`
|
|
87
|
+
|
|
88
|
+
Allows specifying which matchers return promises, and so should be considered
|
|
89
|
+
async when checking if an `expect` should be returned or awaited.
|
|
90
|
+
|
|
91
|
+
By default, this has a list of all the async matchers provided by
|
|
92
|
+
`jest-extended` (namely, `toResolve` and `toReject`).
|
|
93
|
+
|
|
81
94
|
### `minArgs` & `maxArgs`
|
|
82
95
|
|
|
83
96
|
Enforces the minimum and maximum number of arguments that `expect` can take, and
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
|
|
9
|
+
|
|
10
|
+
var _utils = require("./utils");
|
|
11
|
+
|
|
12
|
+
const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
|
|
16
|
+
* with a boolean literal as the sole argument.
|
|
17
|
+
*
|
|
18
|
+
* @example javascript
|
|
19
|
+
* toBe(true);
|
|
20
|
+
* toEqual(false);
|
|
21
|
+
*
|
|
22
|
+
* @param {ParsedExpectMatcher} matcher
|
|
23
|
+
*
|
|
24
|
+
* @return {matcher is ParsedBooleanEqualityMatcher}
|
|
25
|
+
*/
|
|
26
|
+
const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
|
|
27
|
+
|
|
28
|
+
const isString = node => {
|
|
29
|
+
return (0, _utils.isStringNode)(node) || node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const isComparingToString = expression => {
|
|
33
|
+
return isString(expression.left) || isString(expression.right);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const invertOperator = operator => {
|
|
37
|
+
switch (operator) {
|
|
38
|
+
case '>':
|
|
39
|
+
return '<=';
|
|
40
|
+
|
|
41
|
+
case '<':
|
|
42
|
+
return '>=';
|
|
43
|
+
|
|
44
|
+
case '>=':
|
|
45
|
+
return '<';
|
|
46
|
+
|
|
47
|
+
case '<=':
|
|
48
|
+
return '>';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const determineMatcher = (operator, negated) => {
|
|
55
|
+
const op = negated ? invertOperator(operator) : operator;
|
|
56
|
+
|
|
57
|
+
switch (op) {
|
|
58
|
+
case '>':
|
|
59
|
+
return 'toBeGreaterThan';
|
|
60
|
+
|
|
61
|
+
case '<':
|
|
62
|
+
return 'toBeLessThan';
|
|
63
|
+
|
|
64
|
+
case '>=':
|
|
65
|
+
return 'toBeGreaterThanOrEqual';
|
|
66
|
+
|
|
67
|
+
case '<=':
|
|
68
|
+
return 'toBeLessThanOrEqual';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
var _default = (0, _utils.createRule)({
|
|
75
|
+
name: __filename,
|
|
76
|
+
meta: {
|
|
77
|
+
docs: {
|
|
78
|
+
category: 'Best Practices',
|
|
79
|
+
description: 'Suggest using the built-in comparison matchers',
|
|
80
|
+
recommended: false
|
|
81
|
+
},
|
|
82
|
+
messages: {
|
|
83
|
+
useToBeComparison: 'Prefer using `{{ preferredMatcher }}` instead'
|
|
84
|
+
},
|
|
85
|
+
fixable: 'code',
|
|
86
|
+
type: 'suggestion',
|
|
87
|
+
schema: []
|
|
88
|
+
},
|
|
89
|
+
defaultOptions: [],
|
|
90
|
+
|
|
91
|
+
create(context) {
|
|
92
|
+
return {
|
|
93
|
+
CallExpression(node) {
|
|
94
|
+
if (!(0, _utils.isExpectCall)(node)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
expect: {
|
|
100
|
+
arguments: [comparison],
|
|
101
|
+
range: [, expectCallEnd]
|
|
102
|
+
},
|
|
103
|
+
matcher,
|
|
104
|
+
modifier
|
|
105
|
+
} = (0, _utils.parseExpectCall)(node);
|
|
106
|
+
|
|
107
|
+
if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _experimentalUtils.AST_NODE_TYPES.BinaryExpression || isComparingToString(comparison) || !isBooleanEqualityMatcher(matcher)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const preferredMatcher = determineMatcher(comparison.operator, (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value === !!modifier);
|
|
112
|
+
|
|
113
|
+
if (!preferredMatcher) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
context.report({
|
|
118
|
+
fix(fixer) {
|
|
119
|
+
const sourceCode = context.getSourceCode();
|
|
120
|
+
return [// replace the comparison argument with the left-hand side of the comparison
|
|
121
|
+
fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
|
|
122
|
+
fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], `.${preferredMatcher}`), // replace the matcher argument with the right-hand side of the comparison
|
|
123
|
+
fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
messageId: 'useToBeComparison',
|
|
127
|
+
data: {
|
|
128
|
+
preferredMatcher
|
|
129
|
+
},
|
|
130
|
+
node: (modifier || matcher).node.property
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
exports.default = _default;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
|
|
9
|
+
|
|
10
|
+
var _utils = require("./utils");
|
|
11
|
+
|
|
12
|
+
const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
|
|
16
|
+
* with a boolean literal as the sole argument.
|
|
17
|
+
*
|
|
18
|
+
* @example javascript
|
|
19
|
+
* toBe(true);
|
|
20
|
+
* toEqual(false);
|
|
21
|
+
*
|
|
22
|
+
* @param {ParsedExpectMatcher} matcher
|
|
23
|
+
*
|
|
24
|
+
* @return {matcher is ParsedBooleanEqualityMatcher}
|
|
25
|
+
*/
|
|
26
|
+
const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
|
|
27
|
+
|
|
28
|
+
var _default = (0, _utils.createRule)({
|
|
29
|
+
name: __filename,
|
|
30
|
+
meta: {
|
|
31
|
+
docs: {
|
|
32
|
+
category: 'Best Practices',
|
|
33
|
+
description: 'Suggest using the built-in equality matchers',
|
|
34
|
+
recommended: false,
|
|
35
|
+
suggestion: true
|
|
36
|
+
},
|
|
37
|
+
messages: {
|
|
38
|
+
useEqualityMatcher: 'Prefer using one of the equality matchers instead',
|
|
39
|
+
suggestEqualityMatcher: 'Use `{{ equalityMatcher }}`'
|
|
40
|
+
},
|
|
41
|
+
hasSuggestions: true,
|
|
42
|
+
type: 'suggestion',
|
|
43
|
+
schema: []
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
|
|
47
|
+
create(context) {
|
|
48
|
+
return {
|
|
49
|
+
CallExpression(node) {
|
|
50
|
+
if (!(0, _utils.isExpectCall)(node)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
expect: {
|
|
56
|
+
arguments: [comparison],
|
|
57
|
+
range: [, expectCallEnd]
|
|
58
|
+
},
|
|
59
|
+
matcher,
|
|
60
|
+
modifier
|
|
61
|
+
} = (0, _utils.parseExpectCall)(node);
|
|
62
|
+
|
|
63
|
+
if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _experimentalUtils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== '===' && comparison.operator !== '!==' || !isBooleanEqualityMatcher(matcher)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const matcherValue = (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value; // we need to negate the expectation if the current expected
|
|
68
|
+
// value is itself negated by the "not" modifier
|
|
69
|
+
|
|
70
|
+
const addNotModifier = (comparison.operator === '!==' ? !matcherValue : matcherValue) === !!modifier;
|
|
71
|
+
|
|
72
|
+
const buildFixer = equalityMatcher => fixer => {
|
|
73
|
+
const sourceCode = context.getSourceCode();
|
|
74
|
+
return [// replace the comparison argument with the left-hand side of the comparison
|
|
75
|
+
fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
|
|
76
|
+
fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], addNotModifier ? `.${_utils.ModifierName.not}.${equalityMatcher}` : `.${equalityMatcher}`), // replace the matcher argument with the right-hand side of the comparison
|
|
77
|
+
fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
context.report({
|
|
81
|
+
messageId: 'useEqualityMatcher',
|
|
82
|
+
suggest: ['toBe', 'toEqual', 'toStrictEqual'].map(equalityMatcher => ({
|
|
83
|
+
messageId: 'suggestEqualityMatcher',
|
|
84
|
+
data: {
|
|
85
|
+
equalityMatcher
|
|
86
|
+
},
|
|
87
|
+
fix: buildFixer(equalityMatcher)
|
|
88
|
+
})),
|
|
89
|
+
node: (modifier || matcher).node.property
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
exports.default = _default;
|
|
@@ -45,18 +45,94 @@ var _default = (0, _utils.createRule)({
|
|
|
45
45
|
properties: {
|
|
46
46
|
onlyFunctionsWithAsyncKeyword: {
|
|
47
47
|
type: 'boolean'
|
|
48
|
+
},
|
|
49
|
+
onlyFunctionsWithExpectInLoop: {
|
|
50
|
+
type: 'boolean'
|
|
51
|
+
},
|
|
52
|
+
onlyFunctionsWithExpectInCallback: {
|
|
53
|
+
type: 'boolean'
|
|
48
54
|
}
|
|
49
55
|
},
|
|
50
56
|
additionalProperties: false
|
|
51
57
|
}]
|
|
52
58
|
},
|
|
53
59
|
defaultOptions: [{
|
|
54
|
-
onlyFunctionsWithAsyncKeyword: false
|
|
60
|
+
onlyFunctionsWithAsyncKeyword: false,
|
|
61
|
+
onlyFunctionsWithExpectInLoop: false,
|
|
62
|
+
onlyFunctionsWithExpectInCallback: false
|
|
55
63
|
}],
|
|
56
64
|
|
|
57
65
|
create(context, [options]) {
|
|
66
|
+
let expressionDepth = 0;
|
|
67
|
+
let hasExpectInCallback = false;
|
|
68
|
+
let hasExpectInLoop = false;
|
|
69
|
+
let inTestCaseCall = false;
|
|
70
|
+
let inForLoop = false;
|
|
71
|
+
|
|
72
|
+
const shouldCheckFunction = testFunction => {
|
|
73
|
+
if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInLoop && !options.onlyFunctionsWithExpectInCallback) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (options.onlyFunctionsWithAsyncKeyword) {
|
|
78
|
+
if (testFunction.async) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (options.onlyFunctionsWithExpectInLoop) {
|
|
84
|
+
if (hasExpectInLoop) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (options.onlyFunctionsWithExpectInCallback) {
|
|
90
|
+
if (hasExpectInCallback) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const enterExpression = () => inTestCaseCall && expressionDepth++;
|
|
99
|
+
|
|
100
|
+
const exitExpression = () => inTestCaseCall && expressionDepth--;
|
|
101
|
+
|
|
102
|
+
const enterForLoop = () => inForLoop = true;
|
|
103
|
+
|
|
104
|
+
const exitForLoop = () => inForLoop = false;
|
|
105
|
+
|
|
58
106
|
return {
|
|
107
|
+
FunctionExpression: enterExpression,
|
|
108
|
+
'FunctionExpression:exit': exitExpression,
|
|
109
|
+
ArrowFunctionExpression: enterExpression,
|
|
110
|
+
'ArrowFunctionExpression:exit': exitExpression,
|
|
111
|
+
ForStatement: enterForLoop,
|
|
112
|
+
'ForStatement:exit': exitForLoop,
|
|
113
|
+
ForInStatement: enterForLoop,
|
|
114
|
+
'ForInStatement:exit': exitForLoop,
|
|
115
|
+
ForOfStatement: enterForLoop,
|
|
116
|
+
'ForOfStatement:exit': exitForLoop,
|
|
117
|
+
|
|
59
118
|
CallExpression(node) {
|
|
119
|
+
if ((0, _utils.isTestCaseCall)(node)) {
|
|
120
|
+
inTestCaseCall = true;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if ((0, _utils.isExpectCall)(node) && inTestCaseCall) {
|
|
125
|
+
if (inForLoop) {
|
|
126
|
+
hasExpectInLoop = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (expressionDepth > 1) {
|
|
130
|
+
hasExpectInCallback = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
'CallExpression:exit'(node) {
|
|
60
136
|
if (!(0, _utils.isTestCaseCall)(node)) {
|
|
61
137
|
return;
|
|
62
138
|
}
|
|
@@ -67,10 +143,16 @@ var _default = (0, _utils.createRule)({
|
|
|
67
143
|
|
|
68
144
|
const [, testFn] = node.arguments;
|
|
69
145
|
|
|
70
|
-
if (!(0, _utils.isFunction)(testFn) || testFn.body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement
|
|
146
|
+
if (!(0, _utils.isFunction)(testFn) || testFn.body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!shouldCheckFunction(testFn)) {
|
|
71
151
|
return;
|
|
72
152
|
}
|
|
73
153
|
|
|
154
|
+
hasExpectInLoop = false;
|
|
155
|
+
hasExpectInCallback = false;
|
|
74
156
|
const testFuncBody = testFn.body.body;
|
|
75
157
|
|
|
76
158
|
if (!isFirstLineExprStmt(testFuncBody)) {
|
|
@@ -71,6 +71,8 @@ const promiseArrayExceptionKey = ({
|
|
|
71
71
|
end
|
|
72
72
|
}) => `${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
73
73
|
|
|
74
|
+
const defaultAsyncMatchers = ['toReject', 'toResolve'];
|
|
75
|
+
|
|
74
76
|
var _default = (0, _utils.createRule)({
|
|
75
77
|
name: __filename,
|
|
76
78
|
meta: {
|
|
@@ -96,6 +98,12 @@ var _default = (0, _utils.createRule)({
|
|
|
96
98
|
type: 'boolean',
|
|
97
99
|
default: false
|
|
98
100
|
},
|
|
101
|
+
asyncMatchers: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: {
|
|
104
|
+
type: 'string'
|
|
105
|
+
}
|
|
106
|
+
},
|
|
99
107
|
minArgs: {
|
|
100
108
|
type: 'number',
|
|
101
109
|
minimum: 1
|
|
@@ -110,12 +118,14 @@ var _default = (0, _utils.createRule)({
|
|
|
110
118
|
},
|
|
111
119
|
defaultOptions: [{
|
|
112
120
|
alwaysAwait: false,
|
|
121
|
+
asyncMatchers: defaultAsyncMatchers,
|
|
113
122
|
minArgs: 1,
|
|
114
123
|
maxArgs: 1
|
|
115
124
|
}],
|
|
116
125
|
|
|
117
126
|
create(context, [{
|
|
118
127
|
alwaysAwait,
|
|
128
|
+
asyncMatchers = defaultAsyncMatchers,
|
|
119
129
|
minArgs = 1,
|
|
120
130
|
maxArgs = 1
|
|
121
131
|
}]) {
|
|
@@ -225,8 +235,9 @@ var _default = (0, _utils.createRule)({
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
const parentNode = matcher.node.parent;
|
|
238
|
+
const shouldBeAwaited = modifier && modifier.name !== _utils.ModifierName.not || asyncMatchers.includes(matcher.name);
|
|
228
239
|
|
|
229
|
-
if (!parentNode.parent || !
|
|
240
|
+
if (!parentNode.parent || !shouldBeAwaited) {
|
|
230
241
|
return;
|
|
231
242
|
}
|
|
232
243
|
/**
|