eslint-plugin-jest 29.2.3 → 29.4.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/README.md +3 -1
- package/docs/rules/prefer-called-with.md +10 -7
- package/docs/rules/prefer-to-have-been-called-times.md +41 -0
- package/docs/rules/prefer-to-have-been-called.md +33 -0
- package/lib/rules/prefer-called-with.js +1 -1
- package/lib/rules/prefer-expect-assertions.js +38 -1
- package/lib/rules/prefer-to-have-been-called-times.js +67 -0
- package/lib/rules/prefer-to-have-been-called.js +59 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -360,7 +360,7 @@ Manually fixable by
|
|
|
360
360
|
| [padding-around-describe-blocks](docs/rules/padding-around-describe-blocks.md) | Enforce padding around `describe` blocks | | | 🔧 | |
|
|
361
361
|
| [padding-around-expect-groups](docs/rules/padding-around-expect-groups.md) | Enforce padding around `expect` groups | | | 🔧 | |
|
|
362
362
|
| [padding-around-test-blocks](docs/rules/padding-around-test-blocks.md) | Enforce padding around `test` and `it` blocks | | | 🔧 | |
|
|
363
|
-
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `
|
|
363
|
+
| [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toHaveBeenCalledWith()` | | | | |
|
|
364
364
|
| [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | | 🔧 | |
|
|
365
365
|
| [prefer-each](docs/rules/prefer-each.md) | Prefer using `.each` rather than manual loops | | | | |
|
|
366
366
|
| [prefer-ending-with-an-expect](docs/rules/prefer-ending-with-an-expect.md) | Prefer having the last statement in a test be an assertion | | | | |
|
|
@@ -378,6 +378,8 @@ Manually fixable by
|
|
|
378
378
|
| [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | | | 💡 |
|
|
379
379
|
| [prefer-to-be](docs/rules/prefer-to-be.md) | Suggest using `toBe()` for primitive literals | 🎨 | | 🔧 | |
|
|
380
380
|
| [prefer-to-contain](docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | 🎨 | | 🔧 | |
|
|
381
|
+
| [prefer-to-have-been-called](docs/rules/prefer-to-have-been-called.md) | Suggest using `toHaveBeenCalled` | | | 🔧 | |
|
|
382
|
+
| [prefer-to-have-been-called-times](docs/rules/prefer-to-have-been-called-times.md) | Suggest using `toHaveBeenCalledTimes()` | | | 🔧 | |
|
|
381
383
|
| [prefer-to-have-length](docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` | 🎨 | | 🔧 | |
|
|
382
384
|
| [prefer-todo](docs/rules/prefer-todo.md) | Suggest using `test.todo` | | | 🔧 | |
|
|
383
385
|
| [require-hook](docs/rules/require-hook.md) | Require setup and teardown code to be within a hook | | | | |
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Suggest using `
|
|
1
|
+
# Suggest using `toHaveBeenCalledWith()` (`prefer-called-with`)
|
|
2
2
|
|
|
3
3
|
<!-- end auto-generated rule header -->
|
|
4
4
|
|
|
5
|
-
The `
|
|
5
|
+
The `toHaveBeenCalled()` matcher is used to assert that a mock function has been
|
|
6
6
|
called one or more times, without checking the arguments passed. The assertion
|
|
7
|
-
is stronger when arguments are also validated using the `
|
|
7
|
+
is stronger when arguments are also validated using the `toHaveBeenCalledWith()`
|
|
8
8
|
matcher. When some arguments are difficult to check, using generic match like
|
|
9
9
|
`expect.anything()` at least enforces number and position of arguments.
|
|
10
10
|
|
|
@@ -24,11 +24,14 @@ expect(someFunction).toHaveBeenCalled();
|
|
|
24
24
|
The following patterns are not warnings:
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
|
-
expect(noArgsFunction).
|
|
27
|
+
expect(noArgsFunction).toHaveBeenCalledWith();
|
|
28
28
|
|
|
29
|
-
expect(roughArgsFunction).
|
|
29
|
+
expect(roughArgsFunction).toHaveBeenCalledWith(
|
|
30
|
+
expect.anything(),
|
|
31
|
+
expect.any(Date),
|
|
32
|
+
);
|
|
30
33
|
|
|
31
|
-
expect(anyArgsFunction).
|
|
34
|
+
expect(anyArgsFunction).toHaveBeenCalledTimes(1);
|
|
32
35
|
|
|
33
|
-
expect(uncalledFunction).not.
|
|
36
|
+
expect(uncalledFunction).not.toHaveBeenCalled();
|
|
34
37
|
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Suggest using `toHaveBeenCalledTimes()` (`prefer-to-have-been-called-times`)
|
|
2
|
+
|
|
3
|
+
🔧 This rule is automatically fixable by the
|
|
4
|
+
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
|
5
|
+
|
|
6
|
+
<!-- end auto-generated rule header -->
|
|
7
|
+
|
|
8
|
+
In order to have a better failure message, `toHaveBeenCalledTimes` should be
|
|
9
|
+
used instead of directly checking the length of `mock.calls`.
|
|
10
|
+
|
|
11
|
+
## Rule details
|
|
12
|
+
|
|
13
|
+
This rule triggers a warning if `toHaveLength` is used to assert the number of
|
|
14
|
+
times a mock is called.
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
>
|
|
18
|
+
> This rule should ideally be paired with
|
|
19
|
+
> [`prefer-to-have-length`](./prefer-to-have-length.md)
|
|
20
|
+
|
|
21
|
+
The following patterns are considered warnings:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
expect(someFunction.mock.calls).toHaveLength(1);
|
|
25
|
+
expect(someFunction.mock.calls).toHaveLength(0);
|
|
26
|
+
|
|
27
|
+
expect(someFunction.mock.calls).not.toHaveLength(1);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The following patterns are not warnings:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
expect(someFunction).toHaveBeenCalledTimes(1);
|
|
34
|
+
expect(someFunction).toHaveBeenCalledTimes(0);
|
|
35
|
+
|
|
36
|
+
expect(someFunction).not.toHaveBeenCalledTimes(0);
|
|
37
|
+
|
|
38
|
+
expect(uncalledFunction).not.toBeCalled();
|
|
39
|
+
|
|
40
|
+
expect(method.mock.calls[0][0]).toStrictEqual(value);
|
|
41
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Suggest using `toHaveBeenCalled` (`prefer-to-have-been-called`)
|
|
2
|
+
|
|
3
|
+
🔧 This rule is automatically fixable by the
|
|
4
|
+
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
|
5
|
+
|
|
6
|
+
<!-- end auto-generated rule header -->
|
|
7
|
+
|
|
8
|
+
In order to have a better failure message, `toHaveBeenCalled` should be used
|
|
9
|
+
instead of checking the number of times a mock has been called.
|
|
10
|
+
|
|
11
|
+
## Rule details
|
|
12
|
+
|
|
13
|
+
This rule triggers a warning if `toHaveBeenCalledTimes` is used to assert that a
|
|
14
|
+
mock has or has not been called zero times
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
>
|
|
18
|
+
> This rule should ideally be paired with
|
|
19
|
+
> [`prefer-to-have-been-called-times`](./prefer-to-have-been-called-times.md)
|
|
20
|
+
|
|
21
|
+
The following patterns are considered warnings:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
expect(someFunction).toHaveBeenCalledTimes(0);
|
|
25
|
+
expect(someFunction).not.toHaveBeenCalledTimes(0);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The following patterns are not warnings:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
expect(someFunction).not.toHaveBeenCalled();
|
|
32
|
+
expect(someFunction).toHaveBeenCalled();
|
|
33
|
+
```
|
|
@@ -9,7 +9,7 @@ var _default = exports.default = (0, _utils.createRule)({
|
|
|
9
9
|
name: __filename,
|
|
10
10
|
meta: {
|
|
11
11
|
docs: {
|
|
12
|
-
description: 'Suggest using `
|
|
12
|
+
description: 'Suggest using `toHaveBeenCalledWith()`'
|
|
13
13
|
},
|
|
14
14
|
messages: {
|
|
15
15
|
preferCalledWith: 'Prefer {{ matcherName }}With(/* expected args */)'
|
|
@@ -68,6 +68,7 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
68
68
|
}],
|
|
69
69
|
create(context, [options]) {
|
|
70
70
|
let expressionDepth = 0;
|
|
71
|
+
let describeDepth = 0;
|
|
71
72
|
let hasExpectInCallback = false;
|
|
72
73
|
let hasExpectInLoop = false;
|
|
73
74
|
let hasExpectAssertionsAsFirstStatement = false;
|
|
@@ -134,6 +135,14 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
134
135
|
const exitExpression = () => inTestCaseCall && expressionDepth--;
|
|
135
136
|
const enterForLoop = () => inForLoop = true;
|
|
136
137
|
const exitForLoop = () => inForLoop = false;
|
|
138
|
+
let inEachHook = false;
|
|
139
|
+
|
|
140
|
+
// when set to a non-negative value, all expect calls within describes whose depth
|
|
141
|
+
// are equal to or higher than this value are covered by an expect.hasAssertions set
|
|
142
|
+
// up within a beforeEach or afterEach hook
|
|
143
|
+
//
|
|
144
|
+
// when the describe depth is lower than the current value, it gets reset to -1
|
|
145
|
+
let coveredByHookAtDepth = -1;
|
|
137
146
|
return {
|
|
138
147
|
FunctionExpression: enterExpression,
|
|
139
148
|
'FunctionExpression:exit': exitExpression,
|
|
@@ -147,10 +156,22 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
147
156
|
'ForOfStatement:exit': exitForLoop,
|
|
148
157
|
CallExpression(node) {
|
|
149
158
|
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
159
|
+
if (jestFnCall?.type === 'describe') {
|
|
160
|
+
describeDepth += 1;
|
|
161
|
+
}
|
|
162
|
+
if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) {
|
|
163
|
+
inEachHook = true;
|
|
164
|
+
}
|
|
150
165
|
if (jestFnCall?.type === 'test') {
|
|
151
166
|
inTestCaseCall = true;
|
|
152
167
|
return;
|
|
153
168
|
}
|
|
169
|
+
if (jestFnCall?.type === 'expect' && inEachHook && (0, _utils2.getAccessorValue)(jestFnCall.members[0]) === 'hasAssertions') {
|
|
170
|
+
checkExpectHasAssertions(jestFnCall, node);
|
|
171
|
+
if (coveredByHookAtDepth < 0) {
|
|
172
|
+
coveredByHookAtDepth = describeDepth;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
154
175
|
if (jestFnCall?.type === 'expect' && inTestCaseCall) {
|
|
155
176
|
if (expressionDepth === 1 && isFirstStatement(node) && ['assertions', 'hasAssertions'].includes((0, _utils2.getAccessorValue)(jestFnCall.members[0]))) {
|
|
156
177
|
checkExpectHasAssertions(jestFnCall, node);
|
|
@@ -165,7 +186,20 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
165
186
|
}
|
|
166
187
|
},
|
|
167
188
|
'CallExpression:exit'(node) {
|
|
168
|
-
|
|
189
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
190
|
+
if (jestFnCall?.type === 'describe') {
|
|
191
|
+
describeDepth -= 1;
|
|
192
|
+
|
|
193
|
+
// clear the "covered by each hook" flag if we have left the describe
|
|
194
|
+
// depth that it applies to
|
|
195
|
+
if (coveredByHookAtDepth > describeDepth) {
|
|
196
|
+
coveredByHookAtDepth = -1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (jestFnCall?.type === 'hook' && jestFnCall.name.endsWith('Each')) {
|
|
200
|
+
inEachHook = false;
|
|
201
|
+
}
|
|
202
|
+
if (jestFnCall?.type !== 'test') {
|
|
169
203
|
return;
|
|
170
204
|
}
|
|
171
205
|
inTestCaseCall = false;
|
|
@@ -182,6 +216,9 @@ var _default = exports.default = (0, _utils2.createRule)({
|
|
|
182
216
|
hasExpectAssertionsAsFirstStatement = false;
|
|
183
217
|
return;
|
|
184
218
|
}
|
|
219
|
+
if (coveredByHookAtDepth >= 0 && coveredByHookAtDepth <= describeDepth) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
185
222
|
const suggestions = [];
|
|
186
223
|
if (testFn.body.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
187
224
|
suggestions.push(['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var _utils2 = require("./utils");
|
|
9
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
10
|
+
name: __filename,
|
|
11
|
+
meta: {
|
|
12
|
+
fixable: 'code',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Suggest using `toHaveBeenCalledTimes()`'
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
preferMatcher: 'Prefer `toHaveBeenCalledTimes`'
|
|
18
|
+
},
|
|
19
|
+
type: 'suggestion',
|
|
20
|
+
schema: []
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
27
|
+
if (jestFnCall?.type !== 'expect') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const {
|
|
31
|
+
parent: expect
|
|
32
|
+
} = jestFnCall.head.node;
|
|
33
|
+
if (expect?.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const [argument] = expect.arguments;
|
|
37
|
+
|
|
38
|
+
// check if the last property in the chain is `calls`
|
|
39
|
+
if (argument?.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'calls')) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const {
|
|
43
|
+
object
|
|
44
|
+
} = argument;
|
|
45
|
+
|
|
46
|
+
// check if the second-to-last property in the chain is `mock`
|
|
47
|
+
if (object.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(object.property, 'mock')) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const {
|
|
51
|
+
matcher
|
|
52
|
+
} = jestFnCall;
|
|
53
|
+
context.report({
|
|
54
|
+
messageId: 'preferMatcher',
|
|
55
|
+
node: matcher,
|
|
56
|
+
fix(fixer) {
|
|
57
|
+
return [
|
|
58
|
+
// remove the "mock.calls" accessor chain
|
|
59
|
+
fixer.removeRange([object.property.range[0] - 1, argument.range[1]]),
|
|
60
|
+
// replace the current matcher with "toHaveBeenCalledTimes"
|
|
61
|
+
fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], '.toHaveBeenCalledTimes')];
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var _utils2 = require("./utils");
|
|
9
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
10
|
+
name: __filename,
|
|
11
|
+
meta: {
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Suggest using `toHaveBeenCalled`'
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
preferMatcher: 'Use `toHaveBeenCalled`'
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
type: 'suggestion',
|
|
20
|
+
schema: []
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
27
|
+
if (jestFnCall?.type !== 'expect') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const {
|
|
31
|
+
matcher
|
|
32
|
+
} = jestFnCall;
|
|
33
|
+
if (!['toBeCalledTimes', 'toHaveBeenCalledTimes'].includes((0, _utils2.getAccessorValue)(matcher))) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const arg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
|
|
37
|
+
if (arg.type !== _utils.AST_NODE_TYPES.Literal || arg.value !== 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const notModifier = jestFnCall.modifiers.find(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
|
|
41
|
+
context.report({
|
|
42
|
+
messageId: 'preferMatcher',
|
|
43
|
+
node: matcher,
|
|
44
|
+
fix(fixer) {
|
|
45
|
+
let replacementMatcher = '.not.toHaveBeenCalled';
|
|
46
|
+
if (notModifier) {
|
|
47
|
+
replacementMatcher = '.toHaveBeenCalled';
|
|
48
|
+
}
|
|
49
|
+
return [
|
|
50
|
+
// remove all the arguments to the matcher
|
|
51
|
+
fixer.removeRange([jestFnCall.args[0].range[0], jestFnCall.args[jestFnCall.args.length - 1].range[1]]),
|
|
52
|
+
// replace the current matcher with "(.not).toHaveBeenCalled"
|
|
53
|
+
fixer.replaceTextRange([(notModifier || matcher).parent.object.range[1], jestFnCall.matcher.parent.range[1]], replacementMatcher)];
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|