eslint-plugin-jest 22.0.0 → 22.1.2

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 CHANGED
@@ -105,6 +105,7 @@ for more information about extending configuration files.
105
105
  | [no-test-callback][] | Using a callback in asynchronous tests | | ![fixable-green][] |
106
106
  | [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | ![recommended][] | ![fixable-green][] |
107
107
  | [no-test-return-statement][] | Disallow explicitly returning from tests | | |
108
+ | [no-truthy-falsy][] | Disallow using `toBeTruthy()` & `toBeFalsy()` | | |
108
109
  | [prefer-expect-assertions][] | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | |
109
110
  | [prefer-spy-on][] | Suggest using `jest.spyOn()` | | ![fixable-green][] |
110
111
  | [prefer-strict-equal][] | Suggest using `toStrictEqual()` | | ![fixable-green][] |
@@ -137,6 +138,7 @@ for more information about extending configuration files.
137
138
  [no-test-callback]: docs/rules/no-test-callback.md
138
139
  [no-test-prefixes]: docs/rules/no-test-prefixes.md
139
140
  [no-test-return-statement]: docs/rules/no-test-return-statement.md
141
+ [no-truthy-falsy]: docs/rules/no-truthy-falsy.md
140
142
  [prefer-expect-assertions]: docs/rules/prefer-expect-assertions.md
141
143
  [prefer-spy-on]: docs/rules/prefer-spy-on.md
142
144
  [prefer-strict-equal]: docs/rules/prefer-strict-equal.md
@@ -3,7 +3,7 @@
3
3
  `jest` uses `jasmine` as a test runner. A side effect of this is that both a
4
4
  `jasmine` object, and some jasmine-specific globals, are exposed to the test
5
5
  environment. Most functionality offered by Jasmine has been ported to Jest, and
6
- the Jasmine globals will stop working in the future. Developers should therefor
6
+ the Jasmine globals will stop working in the future. Developers should therefore
7
7
  migrate to Jest's documented API instead of relying on the undocumented Jasmine
8
8
  API.
9
9
 
@@ -0,0 +1,32 @@
1
+ # Disallow using `toBeTruthy()` & `toBeFalsy()` (no-truthy-falsy)
2
+
3
+ Tests against boolean values should assert true or false. Asserting `toBeTruthy`
4
+ or `toBeFalsy` matches non-boolean values as well and encourages weaker tests.
5
+
6
+ For example, `expect(someBoolean).toBeFalsy()` passes when
7
+ `someBoolean === null`, and when `someBoolean === false`.
8
+
9
+ Similarly, `expect(someBoolean).toBeTruthy()` passes when `someBoolean === []`,
10
+ and when `someBoolean === 'false'` (note that `'false'` is a string).
11
+
12
+ ## Rule details
13
+
14
+ This rule triggers a warning if `toBeTruthy()` or `toBeFalsy()` are used.
15
+
16
+ This rule is disabled by default.
17
+
18
+ ### Default configuration
19
+
20
+ The following patterns are considered warnings:
21
+
22
+ ```js
23
+ expect(someValue).toBeTruthy();
24
+ expect(someValue).toBeFalsy();
25
+ ```
26
+
27
+ The following patterns are not considered warnings:
28
+
29
+ ```js
30
+ expect(someValue).toBe(true);
31
+ expect(someValue).toBe(false);
32
+ ```
package/index.js CHANGED
@@ -26,6 +26,7 @@ const preferStrictEqual = require('./rules/prefer-strict-equal');
26
26
  const requireTothrowMessage = require('./rules/require-tothrow-message');
27
27
  const noAliasMethods = require('./rules/no-alias-methods');
28
28
  const noTestCallback = require('./rules/no-test-callback');
29
+ const noTruthyFalsy = require('./rules/no-truthy-falsy');
29
30
 
30
31
  const snapshotProcessor = require('./processors/snapshot-processor');
31
32
 
@@ -112,5 +113,6 @@ module.exports = {
112
113
  'require-tothrow-message': requireTothrowMessage,
113
114
  'no-alias-methods': noAliasMethods,
114
115
  'no-test-callback': noTestCallback,
116
+ 'no-truthy-falsy': noTruthyFalsy,
115
117
  },
116
118
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "22.0.0",
3
+ "version": "22.1.2",
4
4
  "description": "Eslint rules for Jest",
5
5
  "repository": "jest-community/eslint-plugin-jest",
6
6
  "license": "MIT",
@@ -153,5 +153,41 @@ ruleTester.run('no-alias-methods', rule, {
153
153
  ],
154
154
  output: 'expect(a).toThrow()',
155
155
  },
156
+ {
157
+ code: 'expect(a).resolves.toThrowError()',
158
+ errors: [
159
+ {
160
+ message:
161
+ 'Replace toThrowError() with its canonical name of toThrow()',
162
+ column: 20,
163
+ line: 1,
164
+ },
165
+ ],
166
+ output: 'expect(a).resolves.toThrow()',
167
+ },
168
+ {
169
+ code: 'expect(a).rejects.toThrowError()',
170
+ errors: [
171
+ {
172
+ message:
173
+ 'Replace toThrowError() with its canonical name of toThrow()',
174
+ column: 19,
175
+ line: 1,
176
+ },
177
+ ],
178
+ output: 'expect(a).rejects.toThrow()',
179
+ },
180
+ {
181
+ code: 'expect(a).not.toThrowError()',
182
+ errors: [
183
+ {
184
+ message:
185
+ 'Replace toThrowError() with its canonical name of toThrow()',
186
+ column: 15,
187
+ line: 1,
188
+ },
189
+ ],
190
+ output: 'expect(a).not.toThrow()',
191
+ },
156
192
  ],
157
193
  });
@@ -15,6 +15,9 @@ ruleTester.run('no-jasmine-globals', rule, {
15
15
  'test("foo", function () {})',
16
16
  'foo()',
17
17
  `require('foo')('bar')`,
18
+ 'function callback(fail) { fail() }',
19
+ 'var spyOn = require("actions"); spyOn("foo")',
20
+ 'function callback(pending) { pending() }',
18
21
  ],
19
22
  invalid: [
20
23
  {
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const { RuleTester } = require('eslint');
4
+ const rule = require('../no-truthy-falsy');
5
+
6
+ const ruleTester = new RuleTester();
7
+
8
+ ruleTester.run('no-truthy-falsy', rule, {
9
+ valid: [
10
+ 'expect(true).toBe(true);',
11
+ 'expect(false).toBe(false);',
12
+ 'expect("anything").toBe(true);',
13
+ 'expect("anything").toEqual(false);',
14
+ 'expect("anything").not.toBe(true);',
15
+ 'expect("anything").not.toEqual(true);',
16
+ 'expect(Promise.resolve({})).resolves.toBe(true);',
17
+ 'expect(Promise.reject({})).rejects.toBe(true);',
18
+ ],
19
+
20
+ invalid: [
21
+ {
22
+ code: 'expect(true).toBeTruthy();',
23
+ errors: [
24
+ {
25
+ message: 'Avoid toBeTruthy',
26
+ column: 14,
27
+ line: 1,
28
+ },
29
+ ],
30
+ },
31
+ {
32
+ code: 'expect(false).not.toBeTruthy();',
33
+ errors: [
34
+ {
35
+ message: 'Avoid toBeTruthy',
36
+ column: 19,
37
+ line: 1,
38
+ },
39
+ ],
40
+ },
41
+ {
42
+ code: 'expect(Promise.resolve({})).resolves.toBeTruthy()',
43
+ errors: [
44
+ {
45
+ message: 'Avoid toBeTruthy',
46
+ column: 38,
47
+ line: 1,
48
+ },
49
+ ],
50
+ },
51
+ {
52
+ code: 'expect(Promise.resolve({})).rejects.toBeTruthy()',
53
+ errors: [
54
+ {
55
+ message: 'Avoid toBeTruthy',
56
+ column: 37,
57
+ line: 1,
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ code: 'expect(false).toBeFalsy();',
63
+ errors: [
64
+ {
65
+ message: 'Avoid toBeFalsy',
66
+ column: 15,
67
+ line: 1,
68
+ },
69
+ ],
70
+ },
71
+ {
72
+ code: 'expect(true).not.toBeFalsy();',
73
+ errors: [
74
+ {
75
+ message: 'Avoid toBeFalsy',
76
+ column: 18,
77
+ line: 1,
78
+ },
79
+ ],
80
+ },
81
+ {
82
+ code: 'expect(Promise.resolve({})).resolves.toBeFalsy()',
83
+ errors: [
84
+ {
85
+ message: 'Avoid toBeFalsy',
86
+ column: 38,
87
+ line: 1,
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ code: 'expect(Promise.resolve({})).rejects.toBeFalsy()',
93
+ errors: [
94
+ {
95
+ message: 'Avoid toBeFalsy',
96
+ column: 37,
97
+ line: 1,
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ });
@@ -18,6 +18,7 @@ ruleTester.run('prefer-to-be-null', rule, {
18
18
  'expect("a string").toMatchSnapshot(null);',
19
19
  'expect("a string").not.toMatchSnapshot();',
20
20
  "expect(something).toEqual('a string');",
21
+ 'expect(null).toBe',
21
22
  ],
22
23
 
23
24
  invalid: [
@@ -15,6 +15,7 @@ ruleTester.run('prefer-to-be-undefined', rule, {
15
15
  'expect(something).toEqual(somethingElse)',
16
16
  'expect(something).not.toBe(somethingElse)',
17
17
  'expect(something).not.toEqual(somethingElse)',
18
+ 'expect(undefined).toBe',
18
19
  ],
19
20
 
20
21
  invalid: [
@@ -32,9 +32,19 @@ module.exports = {
32
32
  return;
33
33
  }
34
34
 
35
- // Check if the method used matches any of ours.
36
- const propertyName = method(node) && method(node).name;
37
- const methodItem = methodNames.find(item => item[1] === propertyName);
35
+ let targetNode = method(node);
36
+ if (
37
+ targetNode.name === 'resolves' ||
38
+ targetNode.name === 'rejects' ||
39
+ targetNode.name === 'not'
40
+ ) {
41
+ targetNode = method(node.parent);
42
+ }
43
+
44
+ // Check if the method used matches any of ours
45
+ const methodItem = methodNames.find(
46
+ item => item[1] === targetNode.name
47
+ );
38
48
 
39
49
  if (methodItem) {
40
50
  context.report({
@@ -43,9 +53,9 @@ module.exports = {
43
53
  replace: methodItem[1],
44
54
  canonical: methodItem[0],
45
55
  },
46
- node: method(node),
56
+ node: targetNode,
47
57
  fix(fixer) {
48
- return [fixer.replaceText(method(node), methodItem[0])];
58
+ return [fixer.replaceText(targetNode, methodItem[0])];
49
59
  },
50
60
  });
51
61
  }
@@ -1,33 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getDocsUrl, getNodeName } = require('./util');
4
-
5
- function collectReferences(scope) {
6
- const locals = new Set();
7
- const unresolved = new Set();
8
-
9
- let currentScope = scope;
10
-
11
- while (currentScope !== null) {
12
- for (const ref of currentScope.variables) {
13
- const isReferenceDefined = ref.defs.some(def => {
14
- return def.type !== 'ImplicitGlobalVariable';
15
- });
16
-
17
- if (isReferenceDefined) {
18
- locals.add(ref.name);
19
- }
20
- }
21
-
22
- for (const ref of currentScope.through) {
23
- unresolved.add(ref.identifier.name);
24
- }
25
-
26
- currentScope = currentScope.upper;
27
- }
28
-
29
- return { locals, unresolved };
30
- }
3
+ const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
31
4
 
32
5
  module.exports = {
33
6
  meta: {
@@ -67,15 +40,7 @@ module.exports = {
67
40
  }
68
41
  },
69
42
  'CallExpression[callee.name="pending"]'(node) {
70
- const references = collectReferences(context.getScope());
71
-
72
- if (
73
- // `pending` was found as a local variable or function declaration.
74
- references.locals.has('pending') ||
75
- // `pending` was not found as an unresolved reference,
76
- // meaning it is likely not an implicit global reference.
77
- !references.unresolved.has('pending')
78
- ) {
43
+ if (scopeHasLocalReference(context.getScope(), 'pending')) {
79
44
  return;
80
45
  }
81
46
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { getDocsUrl, getNodeName } = require('./util');
3
+ const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
4
4
 
5
5
  module.exports = {
6
6
  meta: {
@@ -8,6 +8,17 @@ module.exports = {
8
8
  url: getDocsUrl(__filename),
9
9
  },
10
10
  fixable: 'code',
11
+ messages: {
12
+ illegalGlobal:
13
+ 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
14
+ illegalMethod:
15
+ 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
16
+ illegalFail:
17
+ 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
18
+ illegalPending:
19
+ 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
20
+ illegalJasmine: 'Illegal usage of jasmine global',
21
+ },
11
22
  },
12
23
  create(context) {
13
24
  return {
@@ -17,30 +28,39 @@ module.exports = {
17
28
  if (!calleeName) {
18
29
  return;
19
30
  }
31
+ if (
32
+ calleeName === 'spyOn' ||
33
+ calleeName === 'spyOnProperty' ||
34
+ calleeName === 'fail' ||
35
+ calleeName === 'pending'
36
+ ) {
37
+ if (scopeHasLocalReference(context.getScope(), calleeName)) {
38
+ // It's a local variable, not a jasmine global.
39
+ return;
40
+ }
20
41
 
21
- if (calleeName === 'spyOn' || calleeName === 'spyOnProperty') {
22
- context.report({
23
- node,
24
- message: `Illegal usage of global \`${calleeName}\`, prefer \`jest.spyOn\``,
25
- });
26
- return;
27
- }
28
-
29
- if (calleeName === 'fail') {
30
- context.report({
31
- node,
32
- message:
33
- 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
34
- });
35
- return;
36
- }
37
-
38
- if (calleeName === 'pending') {
39
- context.report({
40
- node,
41
- message:
42
- 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
43
- });
42
+ switch (calleeName) {
43
+ case 'spyOn':
44
+ case 'spyOnProperty':
45
+ context.report({
46
+ node,
47
+ messageId: 'illegalGlobal',
48
+ data: { global: calleeName, replacement: 'jest.spyOn' },
49
+ });
50
+ break;
51
+ case 'fail':
52
+ context.report({
53
+ node,
54
+ messageId: 'illegalFail',
55
+ });
56
+ break;
57
+ case 'pending':
58
+ context.report({
59
+ node,
60
+ messageId: 'illegalPending',
61
+ });
62
+ break;
63
+ }
44
64
  return;
45
65
  }
46
66
 
@@ -59,7 +79,11 @@ module.exports = {
59
79
  return [fixer.replaceText(node.callee.object, 'expect')];
60
80
  },
61
81
  node,
62
- message: `Illegal usage of \`${calleeName}\`, prefer \`expect.${functionName}\``,
82
+ messageId: 'illegalMethod',
83
+ data: {
84
+ method: calleeName,
85
+ replacement: `expect.${functionName}`,
86
+ },
63
87
  });
64
88
  return;
65
89
  }
@@ -67,7 +91,11 @@ module.exports = {
67
91
  if (functionName === 'addMatchers') {
68
92
  context.report({
69
93
  node,
70
- message: `Illegal usage of \`${calleeName}\`, prefer \`expect.extend\``,
94
+ messageId: 'illegalMethod',
95
+ data: {
96
+ method: calleeName,
97
+ replacement: `expect.extend`,
98
+ },
71
99
  });
72
100
  return;
73
101
  }
@@ -75,14 +103,18 @@ module.exports = {
75
103
  if (functionName === 'createSpy') {
76
104
  context.report({
77
105
  node,
78
- message: `Illegal usage of \`${calleeName}\`, prefer \`jest.fn\``,
106
+ messageId: 'illegalMethod',
107
+ data: {
108
+ method: calleeName,
109
+ replacement: 'jest.fn',
110
+ },
79
111
  });
80
112
  return;
81
113
  }
82
114
 
83
115
  context.report({
84
116
  node,
85
- message: 'Illegal usage of jasmine global',
117
+ messageId: 'illegalJasmine',
86
118
  });
87
119
  }
88
120
  },
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ getDocsUrl,
5
+ expectCase,
6
+ expectNotCase,
7
+ expectResolveCase,
8
+ expectRejectCase,
9
+ method,
10
+ } = require('./util');
11
+
12
+ module.exports = {
13
+ meta: {
14
+ docs: {
15
+ url: getDocsUrl(__filename),
16
+ },
17
+ },
18
+ create(context) {
19
+ return {
20
+ CallExpression(node) {
21
+ if (
22
+ expectCase(node) ||
23
+ expectNotCase(node) ||
24
+ expectResolveCase(node) ||
25
+ expectRejectCase(node)
26
+ ) {
27
+ const targetNode =
28
+ node.parent.parent.type === 'MemberExpression' ? node.parent : node;
29
+
30
+ const methodNode = method(targetNode);
31
+ const { name: methodName } = methodNode;
32
+
33
+ if (methodName === 'toBeTruthy' || methodName === 'toBeFalsy') {
34
+ context.report({
35
+ data: { methodName },
36
+ message: 'Avoid {{methodName}}',
37
+ node: methodNode,
38
+ });
39
+ }
40
+ }
41
+ },
42
+ };
43
+ },
44
+ };
package/rules/util.js CHANGED
@@ -73,9 +73,11 @@ const methodName = node => method(node).name;
73
73
 
74
74
  const methodName2 = node => method2(node).name;
75
75
 
76
- const argument = node => node.parent.parent.arguments[0];
76
+ const argument = node =>
77
+ node.parent.parent.arguments && node.parent.parent.arguments[0];
77
78
 
78
- const argument2 = node => node.parent.parent.parent.arguments[0];
79
+ const argument2 = node =>
80
+ node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
79
81
 
80
82
  const describeAliases = Object.assign(Object.create(null), {
81
83
  describe: true,
@@ -142,6 +144,44 @@ const getDocsUrl = filename => {
142
144
  return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
143
145
  };
144
146
 
147
+ const collectReferences = scope => {
148
+ const locals = new Set();
149
+ const unresolved = new Set();
150
+
151
+ let currentScope = scope;
152
+
153
+ while (currentScope !== null) {
154
+ for (const ref of currentScope.variables) {
155
+ const isReferenceDefined = ref.defs.some(def => {
156
+ return def.type !== 'ImplicitGlobalVariable';
157
+ });
158
+
159
+ if (isReferenceDefined) {
160
+ locals.add(ref.name);
161
+ }
162
+ }
163
+
164
+ for (const ref of currentScope.through) {
165
+ unresolved.add(ref.identifier.name);
166
+ }
167
+
168
+ currentScope = currentScope.upper;
169
+ }
170
+
171
+ return { locals, unresolved };
172
+ };
173
+
174
+ const scopeHasLocalReference = (scope, referenceName) => {
175
+ const references = collectReferences(scope);
176
+ return (
177
+ // referenceName was found as a local variable or function declaration.
178
+ references.locals.has(referenceName) ||
179
+ // referenceName was not found as an unresolved reference,
180
+ // meaning it is likely not an implicit global reference.
181
+ !references.unresolved.has(referenceName)
182
+ );
183
+ };
184
+
145
185
  module.exports = {
146
186
  method,
147
187
  method2,
@@ -160,4 +200,5 @@ module.exports = {
160
200
  isFunction,
161
201
  isTestCase,
162
202
  getDocsUrl,
203
+ scopeHasLocalReference,
163
204
  };