eslint-plugin-jest 21.26.1 → 21.27.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
@@ -83,7 +83,7 @@ for more information about extending configuration files.
83
83
  | [consistent-test-it][] | Enforce consistent test or it keyword | | ![fixable-green][] |
84
84
  | [expect-expect][] | Enforce assertion to be made in a test body | | |
85
85
  | [lowercase-name][] | Disallow capitalized test names | | ![fixable-green][] |
86
- | [no-alias-methods][] | Disallow alias methods | | |
86
+ | [no-alias-methods][] | Disallow alias methods | | ![fixable-green][] |
87
87
  | [no-disabled-tests][] | Disallow disabled tests | ![recommended][] | |
88
88
  | [no-focused-tests][] | Disallow focused tests | ![recommended][] | |
89
89
  | [no-hooks][] | Disallow setup and teardown hooks | | |
@@ -95,6 +95,7 @@ for more information about extending configuration files.
95
95
  | [no-test-prefixes][] | Disallow using `f` & `x` prefixes to define focused/skipped tests | | ![fixable-green][] |
96
96
  | [no-test-return-statement][] | Disallow explicitly returning from tests | | |
97
97
  | [prefer-expect-assertions][] | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | |
98
+ | [prefer-spy-on][] | Suggest using `jest.spyOn()` | | ![fixable-green][] |
98
99
  | [prefer-strict-equal][] | Suggest using `toStrictEqual()` | | ![fixable-green][] |
99
100
  | [prefer-to-be-null][] | Suggest using `toBeNull()` | | ![fixable-green][] |
100
101
  | [prefer-to-be-undefined][] | Suggest using `toBeUndefined()` | | ![fixable-green][] |
@@ -126,6 +127,7 @@ for more information about extending configuration files.
126
127
  [no-test-prefixes]: docs/rules/no-test-prefixes.md
127
128
  [no-test-return-statement]: docs/rules/no-test-return-statement.md
128
129
  [prefer-expect-assertions]: docs/rules/prefer-expect-assertions.md
130
+ [prefer-spy-on]: docs/rules/prefer-spy-on.md
129
131
  [prefer-strict-equal]: docs/rules/prefer-strict-equal.md
130
132
  [prefer-to-be-null]: docs/rules/prefer-to-be-null.md
131
133
  [prefer-to-be-undefined]: docs/rules/prefer-to-be-undefined.md
@@ -0,0 +1,41 @@
1
+ # Suggest using `jest.spyOn()` (prefer-spy-on)
2
+
3
+ When mocking a function by overwriting a property you have to manually restore
4
+ the original implementation when cleaning up. When using `jest.spyOn()` Jest
5
+ keeps track of changes, and they can be restored with `jest.restoreAllMocks()`,
6
+ `mockFn.mockRestore()` or by setting `restoreMocks` to `true` in the Jest
7
+ config.
8
+
9
+ Note: The mock created by `jest.spyOn()` still behaves the same as the original
10
+ function. The original function can be overwritten with
11
+ `mockFn.mockImplementation()` or by some of the
12
+ [other mock functions](https://jestjs.io/docs/en/mock-function-api).
13
+
14
+ ```js
15
+ Date.now = jest.fn(); // Original behaviour lost, returns undefined
16
+
17
+ jest.spyOn(Date, 'now'); // Turned into a mock function but behaviour hasn't changed
18
+ jest.spyOn(Date, 'now').mockImplementation(() => 10); // Will always return 10
19
+ jest.spyOn(Date, 'now').mockReturnValue(10); // Will always return 10
20
+ ```
21
+
22
+ ## Rule details
23
+
24
+ This rule triggers a warning if an object's property is overwritten with a jest
25
+ mock.
26
+
27
+ ### Default configuration
28
+
29
+ The following patterns are considered warnings:
30
+
31
+ ```js
32
+ Date.now = jest.fn();
33
+ Date.now = jest.fn(() => 10);
34
+ ```
35
+
36
+ These patterns would not be considered warnings:
37
+
38
+ ```js
39
+ jest.spyOn(Date, 'now');
40
+ jest.spyOn(Date, 'now').mockImplementation(() => 10);
41
+ ```
package/index.js CHANGED
@@ -12,6 +12,7 @@ const noJestImport = require('./rules/no-jest-import');
12
12
  const noLargeSnapshots = require('./rules/no-large-snapshots');
13
13
  const noTestPrefixes = require('./rules/no-test-prefixes');
14
14
  const noTestReturnStatement = require('./rules/no-test-return-statement');
15
+ const preferSpyOn = require('./rules/prefer-spy-on');
15
16
  const preferToBeNull = require('./rules/prefer-to-be-null');
16
17
  const preferToBeUndefined = require('./rules/prefer-to-be-undefined');
17
18
  const preferToContain = require('./rules/prefer-to-contain');
@@ -84,6 +85,7 @@ module.exports = {
84
85
  'no-large-snapshots': noLargeSnapshots,
85
86
  'no-test-prefixes': noTestPrefixes,
86
87
  'no-test-return-statement': noTestReturnStatement,
88
+ 'prefer-spy-on': preferSpyOn,
87
89
  'prefer-to-be-null': preferToBeNull,
88
90
  'prefer-to-be-undefined': preferToBeUndefined,
89
91
  'prefer-to-contain': preferToContain,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "21.26.1",
3
+ "version": "21.27.2",
4
4
  "description": "Eslint rules for Jest",
5
5
  "repository": "jest-community/eslint-plugin-jest",
6
6
  "license": "MIT",
@@ -63,5 +63,9 @@ ruleTester.run('no-focused-tests', rule, {
63
63
  code: 'fit()',
64
64
  errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
65
65
  },
66
+ {
67
+ code: 'fit.each()',
68
+ errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
69
+ },
66
70
  ],
67
71
  });
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const RuleTester = require('eslint').RuleTester;
4
+ const rule = require('../prefer-spy-on');
5
+
6
+ const ruleTester = new RuleTester({
7
+ parserOptions: {
8
+ ecmaVersion: 6,
9
+ },
10
+ });
11
+
12
+ ruleTester.run('prefer-spy-on', rule, {
13
+ valid: [
14
+ 'Date.now = () => 10',
15
+ 'window.fetch = jest.fn',
16
+ 'Date.now = fn()',
17
+ 'obj.mock = jest.something()',
18
+ 'const mock = jest.fn()',
19
+ 'mock = jest.fn()',
20
+ 'const mockObj = { mock: jest.fn() }',
21
+ 'mockObj = { mock: jest.fn() }',
22
+ 'window[`${name}`] = jest[`fn${expression}`]()',
23
+ ],
24
+ invalid: [
25
+ {
26
+ code: 'obj.a = jest.fn(); const test = 10;',
27
+ errors: [
28
+ {
29
+ message: 'Use jest.spyOn() instead.',
30
+ type: 'AssignmentExpression',
31
+ },
32
+ ],
33
+ output: "jest.spyOn(obj, 'a'); const test = 10;",
34
+ },
35
+ {
36
+ code: "Date['now'] = jest['fn']()",
37
+ errors: [
38
+ {
39
+ message: 'Use jest.spyOn() instead.',
40
+ type: 'AssignmentExpression',
41
+ },
42
+ ],
43
+ output: "jest.spyOn(Date, 'now')",
44
+ },
45
+ {
46
+ code: 'window[`${name}`] = jest[`fn`]()',
47
+ errors: [
48
+ {
49
+ message: 'Use jest.spyOn() instead.',
50
+ type: 'AssignmentExpression',
51
+ },
52
+ ],
53
+ output: 'jest.spyOn(window, `${name}`)',
54
+ },
55
+ {
56
+ code: "obj['prop' + 1] = jest['fn']()",
57
+ errors: [
58
+ {
59
+ message: 'Use jest.spyOn() instead.',
60
+ type: 'AssignmentExpression',
61
+ },
62
+ ],
63
+ output: "jest.spyOn(obj, 'prop' + 1)",
64
+ },
65
+ {
66
+ code: 'obj.one.two = jest.fn(); const test = 10;',
67
+ errors: [
68
+ {
69
+ message: 'Use jest.spyOn() instead.',
70
+ type: 'AssignmentExpression',
71
+ },
72
+ ],
73
+ output: "jest.spyOn(obj.one, 'two'); const test = 10;",
74
+ },
75
+ {
76
+ code: 'obj.a = jest.fn(() => 10)',
77
+ errors: [
78
+ {
79
+ message: 'Use jest.spyOn() instead.',
80
+ type: 'AssignmentExpression',
81
+ },
82
+ ],
83
+ output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)",
84
+ },
85
+ {
86
+ code:
87
+ "obj.a.b = jest.fn(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
88
+ errors: [
89
+ {
90
+ message: 'Use jest.spyOn() instead.',
91
+ type: 'AssignmentExpression',
92
+ },
93
+ ],
94
+ output:
95
+ "jest.spyOn(obj.a, 'b').mockImplementation(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
96
+ },
97
+ {
98
+ code: 'window.fetch = jest.fn(() => ({})).one.two().three().four',
99
+ errors: [
100
+ {
101
+ message: 'Use jest.spyOn() instead.',
102
+ type: 'AssignmentExpression',
103
+ },
104
+ ],
105
+ output:
106
+ "jest.spyOn(window, 'fetch').mockImplementation(() => ({})).one.two().three().four",
107
+ },
108
+ ],
109
+ });
@@ -182,6 +182,15 @@ ruleTester.run('valid-expect-in-promise', rule, {
182
182
  ],
183
183
 
184
184
  valid: [
185
+ `
186
+ it('it1', () => new Promise((done) => {
187
+ test()
188
+ .then(() => {
189
+ expect(someThing).toEqual(true);
190
+ done();
191
+ });
192
+ }));
193
+ `,
185
194
  `
186
195
  it('it1', () => {
187
196
  return somePromise.then(() => {
@@ -30,6 +30,17 @@ module.exports = {
30
30
  const callee = node.callee;
31
31
 
32
32
  if (callee.type === 'MemberExpression') {
33
+ if (
34
+ callee.object.type === 'Identifier' &&
35
+ isCallToFocusedTestFunction(callee.object)
36
+ ) {
37
+ context.report({
38
+ message: 'Unexpected focused test.',
39
+ node: callee.object,
40
+ });
41
+ return;
42
+ }
43
+
33
44
  if (
34
45
  callee.object.type === 'MemberExpression' &&
35
46
  isCallToTestOnlyFunction(callee.object)
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const getDocsUrl = require('./util').getDocsUrl;
4
+ const getNodeName = require('./util').getNodeName;
5
+
6
+ const getJestFnCall = node => {
7
+ if (
8
+ (node.type !== 'CallExpression' && node.type !== 'MemberExpression') ||
9
+ (node.callee && node.callee.type !== 'MemberExpression')
10
+ ) {
11
+ return null;
12
+ }
13
+
14
+ const obj = node.callee ? node.callee.object : node.object;
15
+
16
+ if (obj.type === 'Identifier') {
17
+ return node.type === 'CallExpression' &&
18
+ getNodeName(node.callee) === 'jest.fn'
19
+ ? node
20
+ : null;
21
+ }
22
+
23
+ return getJestFnCall(obj);
24
+ };
25
+
26
+ module.exports = {
27
+ meta: {
28
+ docs: {
29
+ url: getDocsUrl(__filename),
30
+ },
31
+ fixable: 'code',
32
+ },
33
+ create(context) {
34
+ return {
35
+ AssignmentExpression(node) {
36
+ if (node.left.type !== 'MemberExpression') return;
37
+
38
+ const jestFnCall = getJestFnCall(node.right);
39
+
40
+ if (!jestFnCall) return;
41
+
42
+ context.report({
43
+ node,
44
+ message: 'Use jest.spyOn() instead.',
45
+ fix(fixer) {
46
+ const leftPropQuote =
47
+ node.left.property.type === 'Identifier' ? "'" : '';
48
+ const arg = jestFnCall.arguments[0];
49
+ const argSource = arg && context.getSourceCode().getText(arg);
50
+ const mockImplementation = argSource
51
+ ? `.mockImplementation(${argSource})`
52
+ : '';
53
+
54
+ return [
55
+ fixer.insertTextBefore(node.left, `jest.spyOn(`),
56
+ fixer.replaceTextRange(
57
+ [node.left.object.end, node.left.property.start],
58
+ `, ${leftPropQuote}`
59
+ ),
60
+ fixer.replaceTextRange(
61
+ [node.left.property.end, jestFnCall.end],
62
+ `${leftPropQuote})${mockImplementation}`
63
+ ),
64
+ ];
65
+ },
66
+ });
67
+ },
68
+ };
69
+ },
70
+ };
package/rules/util.js CHANGED
@@ -107,6 +107,9 @@ const getNodeName = node => {
107
107
  return node.name;
108
108
  case 'Literal':
109
109
  return node.value;
110
+ case 'TemplateLiteral':
111
+ if (node.expressions.length === 0) return node.quasis[0].value.cooked;
112
+ break;
110
113
  case 'MemberExpression':
111
114
  return joinNames(getNodeName(node.object), getNodeName(node.property));
112
115
  }
@@ -88,6 +88,7 @@ const getTestFunction = node => {
88
88
  const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
89
89
  return (
90
90
  testFunctionBody.type === 'CallExpression' ||
91
+ testFunctionBody.type === 'NewExpression' ||
91
92
  node.parent.parent.type === 'ReturnStatement' ||
92
93
  isPromiseReturnedLater(node, testFunctionBody) ||
93
94
  isThenOrCatch(node.parent.parent)