eslint-plugin-jest 25.3.3 → 25.6.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 CHANGED
@@ -1,3 +1,31 @@
1
+ # [25.6.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.5.0...v25.6.0) (2022-01-15)
2
+
3
+
4
+ ### Features
5
+
6
+ * 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))
7
+
8
+ # [25.5.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.4.0...v25.5.0) (2022-01-15)
9
+
10
+
11
+ ### Features
12
+
13
+ * **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))
14
+
15
+ # [25.4.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.4...v25.4.0) (2022-01-15)
16
+
17
+
18
+ ### Features
19
+
20
+ * **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))
21
+
22
+ ## [25.3.4](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.3...v25.3.4) (2022-01-01)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **prefer-lowercase-title:** ignore `it` and `test` separately ([#1011](https://github.com/jest-community/eslint-plugin-jest/issues/1011)) ([f1a7674](https://github.com/jest-community/eslint-plugin-jest/commit/f1a767400967bd923512f79e80f283b3b2afa772))
28
+
1
29
  ## [25.3.3](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.2...v25.3.3) (2021-12-30)
2
30
 
3
31
 
package/README.md CHANGED
@@ -177,6 +177,7 @@ 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][] |
180
181
  | [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | ![suggest][] |
181
182
  | [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | ![fixable][] |
182
183
  | [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
+ ```
@@ -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
+ ```
@@ -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;
@@ -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 || options.onlyFunctionsWithAsyncKeyword && !testFn.async) {
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)) {
@@ -29,11 +29,11 @@ const populateIgnores = ignore => {
29
29
  }
30
30
 
31
31
  if (ignore.includes(_utils.TestCaseName.test)) {
32
- ignores.push(...Object.keys(_utils.TestCaseName));
32
+ ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.test)));
33
33
  }
34
34
 
35
35
  if (ignore.includes(_utils.TestCaseName.it)) {
36
- ignores.push(...Object.keys(_utils.TestCaseName));
36
+ ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.it)));
37
37
  }
38
38
 
39
39
  return ignores;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "25.3.3",
3
+ "version": "25.6.0",
4
4
  "description": "Eslint rules for Jest",
5
5
  "keywords": [
6
6
  "eslint",
@@ -111,6 +111,8 @@
111
111
  "eslint-plugin-import": "^2.25.1",
112
112
  "eslint-plugin-node": "^11.0.0",
113
113
  "eslint-plugin-prettier": "^3.4.1",
114
+ "eslint-remote-tester": "^2.1.0",
115
+ "eslint-remote-tester-repositories": "^0.0.3",
114
116
  "husky": "^7.0.2",
115
117
  "is-ci": "^3.0.0",
116
118
  "jest": "^27.0.0",