eslint-plugin-jest 25.3.2 → 25.5.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,32 @@
1
+ # [25.5.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.4.0...v25.5.0) (2022-01-15)
2
+
3
+
4
+ ### Features
5
+
6
+ * **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))
7
+
8
+ # [25.4.0](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.4...v25.4.0) (2022-01-15)
9
+
10
+
11
+ ### Features
12
+
13
+ * **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))
14
+
15
+ ## [25.3.4](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.3...v25.3.4) (2022-01-01)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **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))
21
+
22
+ ## [25.3.3](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.2...v25.3.3) (2021-12-30)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **prefer-to-contain:** support square bracket accessors ([#1009](https://github.com/jest-community/eslint-plugin-jest/issues/1009)) ([73984a7](https://github.com/jest-community/eslint-plugin-jest/commit/73984a79f790986a17116589a587506bcc10efc0))
28
+ * **prefer-to-have-length:** support square bracket accessors ([#1010](https://github.com/jest-community/eslint-plugin-jest/issues/1010)) ([9e70f55](https://github.com/jest-community/eslint-plugin-jest/commit/9e70f550e341432f69a1cd334c19df87513ea906))
29
+
1
30
  ## [25.3.2](https://github.com/jest-community/eslint-plugin-jest/compare/v25.3.1...v25.3.2) (2021-12-27)
2
31
 
3
32
 
@@ -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
+ ```
@@ -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;
@@ -32,52 +32,8 @@ const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCa
32
32
  * @param {CallExpression} node
33
33
  *
34
34
  * @return {node is FixableIncludesCallExpression}
35
- *
36
- * @todo support `['includes']()` syntax (remove last property.type check to begin)
37
- * @todo break out into `isMethodCall<Name extends string>(node: TSESTree.Node, method: Name)` util-fn
38
- */
39
- const isFixableIncludesCallExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'includes') && node.callee.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (0, _utils.hasOnlyOneArgument)(node);
40
-
41
- const buildToContainFuncExpectation = negated => negated ? `${_utils.ModifierName.not}.toContain` : 'toContain';
42
- /**
43
- * Finds the first `.` character token between the `object` & `property` of the given `member` expression.
44
- *
45
- * @param {TSESTree.MemberExpression} member
46
- * @param {SourceCode} sourceCode
47
- *
48
- * @return {Token | null}
49
35
  */
50
-
51
-
52
- const findPropertyDotToken = (member, sourceCode) => sourceCode.getFirstTokenBetween(member.object, member.property, token => token.value === '.');
53
-
54
- const getNegationFixes = (node, modifier, matcher, sourceCode, fixer, fileName) => {
55
- const [containArg] = node.arguments;
56
- const negationPropertyDot = findPropertyDotToken(modifier.node, sourceCode);
57
- const toContainFunc = buildToContainFuncExpectation((0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value);
58
- /* istanbul ignore if */
59
-
60
- if (negationPropertyDot === null) {
61
- throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
62
- }
63
-
64
- return [fixer.remove(negationPropertyDot), fixer.remove(modifier.node.property), fixer.replaceText(matcher.node.property, toContainFunc), fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg))];
65
- };
66
-
67
- const getCommonFixes = (node, sourceCode, fileName) => {
68
- const [containArg] = node.arguments;
69
- const includesCallee = node.callee;
70
- const propertyDot = findPropertyDotToken(includesCallee, sourceCode);
71
- const closingParenthesis = sourceCode.getTokenAfter(containArg);
72
- const openParenthesis = sourceCode.getTokenBefore(containArg);
73
- /* istanbul ignore if */
74
-
75
- if (propertyDot === null || closingParenthesis === null || openParenthesis === null) {
76
- throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
77
- }
78
-
79
- return [containArg, includesCallee.property, propertyDot, closingParenthesis, openParenthesis];
80
- }; // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
36
+ const isFixableIncludesCallExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils.hasOnlyOneArgument)(node); // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
81
37
 
82
38
 
83
39
  var _default = (0, _utils.createRule)({
@@ -106,7 +62,8 @@ var _default = (0, _utils.createRule)({
106
62
 
107
63
  const {
108
64
  expect: {
109
- arguments: [includesCall]
65
+ arguments: [includesCall],
66
+ range: [, expectCallEnd]
110
67
  },
111
68
  matcher,
112
69
  modifier
@@ -118,19 +75,14 @@ var _default = (0, _utils.createRule)({
118
75
 
119
76
  context.report({
120
77
  fix(fixer) {
121
- const sourceCode = context.getSourceCode();
122
- const fileName = context.getFilename();
123
- const fixArr = getCommonFixes(includesCall, sourceCode, fileName).map(target => fixer.remove(target));
124
-
125
- if (modifier) {
126
- return getNegationFixes(includesCall, modifier, matcher, sourceCode, fixer, fileName).concat(fixArr);
127
- }
128
-
129
- const toContainFunc = buildToContainFuncExpectation(!(0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value);
130
- const [containArg] = includesCall.arguments;
131
- fixArr.push(fixer.replaceText(matcher.node.property, toContainFunc));
132
- fixArr.push(fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg)));
133
- return fixArr;
78
+ const sourceCode = context.getSourceCode(); // we need to negate the expectation if the current expected
79
+ // value is itself negated by the "not" modifier
80
+
81
+ const addNotModifier = (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value === !!modifier;
82
+ return [// remove the "includes" call entirely
83
+ fixer.removeRange([includesCall.callee.property.range[0] - 1, includesCall.range[1]]), // replace the current matcher with "toContain", adding "not" if needed
84
+ fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], addNotModifier ? `.${_utils.ModifierName.not}.toContain` : '.toContain'), // replace the matcher argument with the value from the "includes"
85
+ fixer.replaceText(matcher.arguments[0], sourceCode.getText(includesCall.arguments[0]))];
134
86
  },
135
87
 
136
88
  messageId: 'useToContain',
@@ -40,20 +40,15 @@ var _default = (0, _utils.createRule)({
40
40
  matcher
41
41
  } = (0, _utils.parseExpectCall)(node);
42
42
 
43
- if (!matcher || !(0, _utils.isParsedEqualityMatcherCall)(matcher) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || !(0, _utils.isSupportedAccessor)(argument.property, 'length') || argument.property.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) {
43
+ if (!matcher || !(0, _utils.isParsedEqualityMatcherCall)(matcher) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || !(0, _utils.isSupportedAccessor)(argument.property, 'length')) {
44
44
  return;
45
45
  }
46
46
 
47
47
  context.report({
48
48
  fix(fixer) {
49
- const propertyDot = context.getSourceCode().getFirstTokenBetween(argument.object, argument.property, token => token.value === '.');
50
- /* istanbul ignore if */
51
-
52
- if (propertyDot === null) {
53
- throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
54
- }
55
-
56
- return [fixer.remove(propertyDot), fixer.remove(argument.property), fixer.replaceText(matcher.node.property, 'toHaveLength')];
49
+ return [// remove the "length" property accessor
50
+ fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]), // replace the current matcher with "toHaveLength"
51
+ fixer.replaceTextRange([matcher.node.object.range[1], matcher.node.range[1]], '.toHaveLength')];
57
52
  },
58
53
 
59
54
  messageId: 'useToHaveLength',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "25.3.2",
3
+ "version": "25.5.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",