@depup/eslint-plugin-jest 29.15.1-depup.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/LICENSE +21 -0
- package/README.md +31 -0
- package/changes.json +10 -0
- package/docs/rules/consistent-test-it.md +96 -0
- package/docs/rules/expect-expect.md +152 -0
- package/docs/rules/max-expects.md +78 -0
- package/docs/rules/max-nested-describe.md +134 -0
- package/docs/rules/no-alias-methods.md +57 -0
- package/docs/rules/no-commented-out-tests.md +68 -0
- package/docs/rules/no-conditional-expect.md +142 -0
- package/docs/rules/no-conditional-in-test.md +123 -0
- package/docs/rules/no-confusing-set-timeout.md +64 -0
- package/docs/rules/no-deprecated-functions.md +59 -0
- package/docs/rules/no-disabled-tests.md +72 -0
- package/docs/rules/no-done-callback.md +100 -0
- package/docs/rules/no-duplicate-hooks.md +79 -0
- package/docs/rules/no-error-equal.md +37 -0
- package/docs/rules/no-export.md +52 -0
- package/docs/rules/no-focused-tests.md +67 -0
- package/docs/rules/no-hooks.md +178 -0
- package/docs/rules/no-identical-title.md +59 -0
- package/docs/rules/no-interpolation-in-snapshots.md +67 -0
- package/docs/rules/no-jasmine-globals.md +69 -0
- package/docs/rules/no-large-snapshots.md +180 -0
- package/docs/rules/no-mocks-import.md +34 -0
- package/docs/rules/no-restricted-jest-methods.md +53 -0
- package/docs/rules/no-restricted-matchers.md +64 -0
- package/docs/rules/no-standalone-expect.md +106 -0
- package/docs/rules/no-test-prefixes.md +42 -0
- package/docs/rules/no-test-return-statement.md +50 -0
- package/docs/rules/no-unnecessary-assertion.md +41 -0
- package/docs/rules/no-unneeded-async-expect-function.md +41 -0
- package/docs/rules/no-untyped-mock-factory.md +74 -0
- package/docs/rules/padding-around-after-all-blocks.md +34 -0
- package/docs/rules/padding-around-after-each-blocks.md +38 -0
- package/docs/rules/padding-around-all.md +20 -0
- package/docs/rules/padding-around-before-all-blocks.md +37 -0
- package/docs/rules/padding-around-before-each-blocks.md +38 -0
- package/docs/rules/padding-around-describe-blocks.md +42 -0
- package/docs/rules/padding-around-expect-groups.md +44 -0
- package/docs/rules/padding-around-test-blocks.md +48 -0
- package/docs/rules/prefer-called-with.md +39 -0
- package/docs/rules/prefer-comparison-matcher.md +62 -0
- package/docs/rules/prefer-each.md +58 -0
- package/docs/rules/prefer-ending-with-an-expect.md +170 -0
- package/docs/rules/prefer-equality-matcher.md +36 -0
- package/docs/rules/prefer-expect-assertions.md +230 -0
- package/docs/rules/prefer-expect-resolves.md +62 -0
- package/docs/rules/prefer-hooks-in-order.md +137 -0
- package/docs/rules/prefer-hooks-on-top.md +124 -0
- package/docs/rules/prefer-importing-jest-globals.md +85 -0
- package/docs/rules/prefer-jest-mocked.md +40 -0
- package/docs/rules/prefer-lowercase-title.md +128 -0
- package/docs/rules/prefer-mock-promise-shorthand.md +41 -0
- package/docs/rules/prefer-mock-return-shorthand.md +50 -0
- package/docs/rules/prefer-snapshot-hint.md +192 -0
- package/docs/rules/prefer-spy-on.md +46 -0
- package/docs/rules/prefer-strict-equal.md +29 -0
- package/docs/rules/prefer-to-be.md +63 -0
- package/docs/rules/prefer-to-contain.md +53 -0
- package/docs/rules/prefer-to-have-been-called-times.md +43 -0
- package/docs/rules/prefer-to-have-been-called.md +35 -0
- package/docs/rules/prefer-to-have-length.md +41 -0
- package/docs/rules/prefer-todo.md +33 -0
- package/docs/rules/require-hook.md +187 -0
- package/docs/rules/require-to-throw-message.md +43 -0
- package/docs/rules/require-top-level-describe.md +86 -0
- package/docs/rules/unbound-method.md +61 -0
- package/docs/rules/valid-describe-callback.md +69 -0
- package/docs/rules/valid-expect-in-promise.md +79 -0
- package/docs/rules/valid-expect-with-promise.md +43 -0
- package/docs/rules/valid-expect.md +155 -0
- package/docs/rules/valid-mock-module-path.md +72 -0
- package/docs/rules/valid-title.md +319 -0
- package/index.d.ts +28 -0
- package/lib/globals.json +15 -0
- package/lib/index.js +85 -0
- package/lib/rules/consistent-test-it.js +101 -0
- package/lib/rules/expect-expect.js +102 -0
- package/lib/rules/max-expects.js +70 -0
- package/lib/rules/max-nested-describe.js +59 -0
- package/lib/rules/no-alias-methods.js +62 -0
- package/lib/rules/no-commented-out-tests.js +44 -0
- package/lib/rules/no-conditional-expect.js +80 -0
- package/lib/rules/no-conditional-in-test.js +63 -0
- package/lib/rules/no-confusing-set-timeout.js +61 -0
- package/lib/rules/no-deprecated-functions.js +81 -0
- package/lib/rules/no-disabled-tests.js +57 -0
- package/lib/rules/no-done-callback.js +120 -0
- package/lib/rules/no-duplicate-hooks.js +52 -0
- package/lib/rules/no-error-equal.js +41 -0
- package/lib/rules/no-export.js +61 -0
- package/lib/rules/no-focused-tests.js +63 -0
- package/lib/rules/no-hooks.js +51 -0
- package/lib/rules/no-identical-title.js +73 -0
- package/lib/rules/no-interpolation-in-snapshots.js +43 -0
- package/lib/rules/no-jasmine-globals.js +140 -0
- package/lib/rules/no-large-snapshots.js +100 -0
- package/lib/rules/no-mocks-import.js +46 -0
- package/lib/rules/no-restricted-jest-methods.js +53 -0
- package/lib/rules/no-restricted-matchers.js +60 -0
- package/lib/rules/no-standalone-expect.js +114 -0
- package/lib/rules/no-test-prefixes.js +48 -0
- package/lib/rules/no-test-return-statement.js +62 -0
- package/lib/rules/no-unnecessary-assertion.js +87 -0
- package/lib/rules/no-unneeded-async-expect-function.js +57 -0
- package/lib/rules/no-untyped-mock-factory.js +61 -0
- package/lib/rules/padding-around-after-all-blocks.js +17 -0
- package/lib/rules/padding-around-after-each-blocks.js +17 -0
- package/lib/rules/padding-around-all.js +15 -0
- package/lib/rules/padding-around-before-all-blocks.js +17 -0
- package/lib/rules/padding-around-before-each-blocks.js +17 -0
- package/lib/rules/padding-around-describe-blocks.js +17 -0
- package/lib/rules/padding-around-expect-groups.js +21 -0
- package/lib/rules/padding-around-test-blocks.js +17 -0
- package/lib/rules/prefer-called-with.js +47 -0
- package/lib/rules/prefer-comparison-matcher.js +111 -0
- package/lib/rules/prefer-each.js +77 -0
- package/lib/rules/prefer-ending-with-an-expect.js +102 -0
- package/lib/rules/prefer-equality-matcher.js +87 -0
- package/lib/rules/prefer-expect-assertions.js +237 -0
- package/lib/rules/prefer-expect-resolves.js +47 -0
- package/lib/rules/prefer-hooks-in-order.js +67 -0
- package/lib/rules/prefer-hooks-on-top.js +41 -0
- package/lib/rules/prefer-importing-jest-globals.js +126 -0
- package/lib/rules/prefer-jest-mocked.js +66 -0
- package/lib/rules/prefer-lowercase-title.js +128 -0
- package/lib/rules/prefer-mock-promise-shorthand.js +88 -0
- package/lib/rules/prefer-mock-return-shorthand.js +122 -0
- package/lib/rules/prefer-snapshot-hint.js +111 -0
- package/lib/rules/prefer-spy-on.js +80 -0
- package/lib/rules/prefer-strict-equal.js +46 -0
- package/lib/rules/prefer-to-be.js +98 -0
- package/lib/rules/prefer-to-contain.js +82 -0
- package/lib/rules/prefer-to-have-been-called-times.js +70 -0
- package/lib/rules/prefer-to-have-been-called.js +59 -0
- package/lib/rules/prefer-to-have-length.js +57 -0
- package/lib/rules/prefer-todo.js +72 -0
- package/lib/rules/require-hook.js +93 -0
- package/lib/rules/require-to-throw-message.js +45 -0
- package/lib/rules/require-top-level-describe.js +86 -0
- package/lib/rules/unbound-method.js +86 -0
- package/lib/rules/utils/accessors.js +127 -0
- package/lib/rules/utils/ast-utils.js +59 -0
- package/lib/rules/utils/detectJestVersion.js +24 -0
- package/lib/rules/utils/followTypeAssertionChain.js +10 -0
- package/lib/rules/utils/index.js +71 -0
- package/lib/rules/utils/misc.js +168 -0
- package/lib/rules/utils/padding.js +308 -0
- package/lib/rules/utils/parseJestFnCall.js +325 -0
- package/lib/rules/utils/ts.js +73 -0
- package/lib/rules/valid-describe-callback.js +94 -0
- package/lib/rules/valid-expect-in-promise.js +311 -0
- package/lib/rules/valid-expect-with-promise.js +54 -0
- package/lib/rules/valid-expect.js +302 -0
- package/lib/rules/valid-mock-module-path.js +90 -0
- package/lib/rules/valid-title.js +252 -0
- package/package.json +164 -0
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
const findNodeObject = node => {
|
|
10
|
+
if ('object' in node) {
|
|
11
|
+
return node.object;
|
|
12
|
+
}
|
|
13
|
+
if (node.callee.type === _utils.AST_NODE_TYPES.MemberExpression) {
|
|
14
|
+
return node.callee.object;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
const getJestFnCall = node => {
|
|
19
|
+
if (node.type !== _utils.AST_NODE_TYPES.CallExpression && node.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const obj = findNodeObject(node);
|
|
23
|
+
if (!obj) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (obj.type === _utils.AST_NODE_TYPES.Identifier) {
|
|
27
|
+
return node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.getNodeName)(node.callee) === 'jest.fn' ? node : null;
|
|
28
|
+
}
|
|
29
|
+
return getJestFnCall(obj);
|
|
30
|
+
};
|
|
31
|
+
const getAutoFixMockImplementation = (jestFnCall, context) => {
|
|
32
|
+
const hasMockImplementationAlready = jestFnCall.parent.type === _utils.AST_NODE_TYPES.MemberExpression && jestFnCall.parent.property.type === _utils.AST_NODE_TYPES.Identifier && jestFnCall.parent.property.name === 'mockImplementation';
|
|
33
|
+
if (hasMockImplementationAlready) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
const [arg] = jestFnCall.arguments;
|
|
37
|
+
const argSource = arg && context.sourceCode.getText(arg);
|
|
38
|
+
return argSource ? `.mockImplementation(${argSource})` : '.mockImplementation()';
|
|
39
|
+
};
|
|
40
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
41
|
+
name: __filename,
|
|
42
|
+
meta: {
|
|
43
|
+
docs: {
|
|
44
|
+
description: 'Suggest using `jest.spyOn()`'
|
|
45
|
+
},
|
|
46
|
+
messages: {
|
|
47
|
+
useJestSpyOn: 'Use jest.spyOn() instead'
|
|
48
|
+
},
|
|
49
|
+
fixable: 'code',
|
|
50
|
+
schema: [],
|
|
51
|
+
type: 'suggestion'
|
|
52
|
+
},
|
|
53
|
+
defaultOptions: [],
|
|
54
|
+
create(context) {
|
|
55
|
+
return {
|
|
56
|
+
AssignmentExpression(node) {
|
|
57
|
+
const {
|
|
58
|
+
left,
|
|
59
|
+
right
|
|
60
|
+
} = node;
|
|
61
|
+
if (left.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const jestFnCall = getJestFnCall(right);
|
|
65
|
+
if (!jestFnCall) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'useJestSpyOn',
|
|
71
|
+
fix(fixer) {
|
|
72
|
+
const leftPropQuote = left.property.type === _utils.AST_NODE_TYPES.Identifier && !left.computed ? "'" : '';
|
|
73
|
+
const mockImplementation = getAutoFixMockImplementation(jestFnCall, context);
|
|
74
|
+
return [fixer.insertTextBefore(left, `jest.spyOn(`), fixer.replaceTextRange([left.object.range[1], left.property.range[0]], `, ${leftPropQuote}`), fixer.replaceTextRange([left.property.range[1], jestFnCall.range[1]], `${leftPropQuote})${mockImplementation}`)];
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _utils = require("./utils");
|
|
8
|
+
var _default = exports.default = (0, _utils.createRule)({
|
|
9
|
+
name: __filename,
|
|
10
|
+
meta: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Suggest using `toStrictEqual()`'
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
useToStrictEqual: 'Use `toStrictEqual()` instead',
|
|
16
|
+
suggestReplaceWithStrictEqual: 'Replace with `toStrictEqual()`'
|
|
17
|
+
},
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
schema: [],
|
|
20
|
+
hasSuggestions: true
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
CallExpression(node) {
|
|
26
|
+
const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
|
|
27
|
+
if (jestFnCall?.type !== 'expect') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const {
|
|
31
|
+
matcher
|
|
32
|
+
} = jestFnCall;
|
|
33
|
+
if ((0, _utils.isSupportedAccessor)(matcher, 'toEqual')) {
|
|
34
|
+
context.report({
|
|
35
|
+
messageId: 'useToStrictEqual',
|
|
36
|
+
node: matcher,
|
|
37
|
+
suggest: [{
|
|
38
|
+
messageId: 'suggestReplaceWithStrictEqual',
|
|
39
|
+
fix: fixer => [(0, _utils.replaceAccessorFixer)(fixer, matcher, _utils.EqualityMatcher.toStrictEqual)]
|
|
40
|
+
}]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
const isNullLiteral = node => node.type === _utils.AST_NODE_TYPES.Literal && node.value === null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers,
|
|
13
|
+
* with a `null` literal as the sole argument.
|
|
14
|
+
*/
|
|
15
|
+
const isNullEqualityMatcher = expectFnCall => isNullLiteral((0, _utils2.getFirstMatcherArg)(expectFnCall));
|
|
16
|
+
const isFirstArgumentIdentifier = (expectFnCall, name) => (0, _utils2.isIdentifier)((0, _utils2.getFirstMatcherArg)(expectFnCall), name);
|
|
17
|
+
const shouldUseToBe = expectFnCall => {
|
|
18
|
+
let firstArg = (0, _utils2.getFirstMatcherArg)(expectFnCall);
|
|
19
|
+
if (firstArg.type === _utils.AST_NODE_TYPES.UnaryExpression && firstArg.operator === '-') {
|
|
20
|
+
firstArg = firstArg.argument;
|
|
21
|
+
}
|
|
22
|
+
if (firstArg.type === _utils.AST_NODE_TYPES.Literal) {
|
|
23
|
+
// regex literals are classed as literals, but they're actually objects
|
|
24
|
+
// which means "toBe" will give different results than other matchers
|
|
25
|
+
return !('regex' in firstArg);
|
|
26
|
+
}
|
|
27
|
+
return firstArg.type === _utils.AST_NODE_TYPES.TemplateLiteral;
|
|
28
|
+
};
|
|
29
|
+
const reportPreferToBe = (context, whatToBe, expectFnCall, func, modifierNode) => {
|
|
30
|
+
context.report({
|
|
31
|
+
messageId: `useToBe${whatToBe}`,
|
|
32
|
+
fix(fixer) {
|
|
33
|
+
const fixes = [(0, _utils2.replaceAccessorFixer)(fixer, expectFnCall.matcher, `toBe${whatToBe}`)];
|
|
34
|
+
if (expectFnCall.args.length && whatToBe !== '') {
|
|
35
|
+
fixes.push((0, _utils2.removeExtraArgumentsFixer)(fixer, context, func, 0));
|
|
36
|
+
}
|
|
37
|
+
if (modifierNode) {
|
|
38
|
+
fixes.push(fixer.removeRange([modifierNode.range[0] - 1, modifierNode.range[1]]));
|
|
39
|
+
}
|
|
40
|
+
return fixes;
|
|
41
|
+
},
|
|
42
|
+
node: expectFnCall.matcher
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
46
|
+
name: __filename,
|
|
47
|
+
meta: {
|
|
48
|
+
docs: {
|
|
49
|
+
description: 'Suggest using `toBe()` for primitive literals'
|
|
50
|
+
},
|
|
51
|
+
messages: {
|
|
52
|
+
useToBe: 'Use `toBe` when expecting primitive literals',
|
|
53
|
+
useToBeUndefined: 'Use `toBeUndefined` instead',
|
|
54
|
+
useToBeDefined: 'Use `toBeDefined` instead',
|
|
55
|
+
useToBeNull: 'Use `toBeNull` instead',
|
|
56
|
+
useToBeNaN: 'Use `toBeNaN` instead'
|
|
57
|
+
},
|
|
58
|
+
fixable: 'code',
|
|
59
|
+
type: 'suggestion',
|
|
60
|
+
schema: []
|
|
61
|
+
},
|
|
62
|
+
defaultOptions: [],
|
|
63
|
+
create(context) {
|
|
64
|
+
return {
|
|
65
|
+
CallExpression(node) {
|
|
66
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
67
|
+
if (jestFnCall?.type !== 'expect') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const matcherName = (0, _utils2.getAccessorValue)(jestFnCall.matcher);
|
|
71
|
+
const notModifier = jestFnCall.modifiers.find(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
|
|
72
|
+
if (notModifier && ['toBeUndefined', 'toBeDefined'].includes(matcherName)) {
|
|
73
|
+
reportPreferToBe(context, matcherName === 'toBeDefined' ? 'Undefined' : 'Defined', jestFnCall, node, notModifier);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!_utils2.EqualityMatcher.hasOwnProperty(matcherName) || jestFnCall.args.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (isNullEqualityMatcher(jestFnCall)) {
|
|
80
|
+
reportPreferToBe(context, 'Null', jestFnCall, node);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (isFirstArgumentIdentifier(jestFnCall, 'undefined')) {
|
|
84
|
+
const name = notModifier ? 'Defined' : 'Undefined';
|
|
85
|
+
reportPreferToBe(context, name, jestFnCall, node, notModifier);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (isFirstArgumentIdentifier(jestFnCall, 'NaN')) {
|
|
89
|
+
reportPreferToBe(context, 'NaN', jestFnCall, node);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (shouldUseToBe(jestFnCall) && matcherName !== _utils2.EqualityMatcher.toBe) {
|
|
93
|
+
reportPreferToBe(context, '', jestFnCall, node);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* Checks if the given `node` is a `CallExpression` representing the calling
|
|
11
|
+
* of an `includes`-like method that can be 'fixed' (using `toContain`).
|
|
12
|
+
*
|
|
13
|
+
* @param {CallExpression} node
|
|
14
|
+
*
|
|
15
|
+
* @return {node is FixableIncludesCallExpression}
|
|
16
|
+
*/
|
|
17
|
+
const isFixableIncludesCallExpression = node => node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils2.hasOnlyOneArgument)(node) && node.arguments[0].type !== _utils.AST_NODE_TYPES.SpreadElement;
|
|
18
|
+
|
|
19
|
+
// expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
|
|
20
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
21
|
+
name: __filename,
|
|
22
|
+
meta: {
|
|
23
|
+
docs: {
|
|
24
|
+
description: 'Suggest using `toContain()`'
|
|
25
|
+
},
|
|
26
|
+
messages: {
|
|
27
|
+
useToContain: 'Use toContain() instead'
|
|
28
|
+
},
|
|
29
|
+
fixable: 'code',
|
|
30
|
+
type: 'suggestion',
|
|
31
|
+
schema: []
|
|
32
|
+
},
|
|
33
|
+
defaultOptions: [],
|
|
34
|
+
create(context) {
|
|
35
|
+
return {
|
|
36
|
+
CallExpression(node) {
|
|
37
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
38
|
+
if (jestFnCall?.type !== 'expect' || jestFnCall.args.length === 0) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const {
|
|
42
|
+
parent: expect
|
|
43
|
+
} = jestFnCall.head.node;
|
|
44
|
+
if (expect.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const {
|
|
48
|
+
arguments: [includesCall],
|
|
49
|
+
range: [, expectCallEnd]
|
|
50
|
+
} = expect;
|
|
51
|
+
const {
|
|
52
|
+
matcher
|
|
53
|
+
} = jestFnCall;
|
|
54
|
+
const matcherArg = (0, _utils2.getFirstMatcherArg)(jestFnCall);
|
|
55
|
+
if (!includesCall || matcherArg.type === _utils.AST_NODE_TYPES.SpreadElement || !_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || !(0, _utils2.isBooleanLiteral)(matcherArg) || !isFixableIncludesCallExpression(includesCall)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const hasNot = jestFnCall.modifiers.some(nod => (0, _utils2.getAccessorValue)(nod) === 'not');
|
|
59
|
+
context.report({
|
|
60
|
+
fix(fixer) {
|
|
61
|
+
const {
|
|
62
|
+
sourceCode
|
|
63
|
+
} = context;
|
|
64
|
+
|
|
65
|
+
// we need to negate the expectation if the current expected
|
|
66
|
+
// value is itself negated by the "not" modifier
|
|
67
|
+
const addNotModifier = matcherArg.value === hasNot;
|
|
68
|
+
return [
|
|
69
|
+
// remove the "includes" call entirely
|
|
70
|
+
fixer.removeRange([includesCall.callee.property.range[0] - 1, includesCall.range[1]]),
|
|
71
|
+
// replace the current matcher with "toContain", adding "not" if needed
|
|
72
|
+
fixer.replaceTextRange([expectCallEnd, matcher.parent.range[1]], addNotModifier ? `.${_utils2.ModifierName.not}.toContain` : '.toContain'),
|
|
73
|
+
// replace the matcher argument with the value from the "includes"
|
|
74
|
+
fixer.replaceText(jestFnCall.args[0], sourceCode.getText(includesCall.arguments[0]))];
|
|
75
|
+
},
|
|
76
|
+
messageId: 'useToContain',
|
|
77
|
+
node: matcher
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
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 {
|
|
37
|
+
matcher
|
|
38
|
+
} = jestFnCall;
|
|
39
|
+
if (!(0, _utils2.isSupportedAccessor)(matcher, 'toHaveLength')) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const [argument] = expect.arguments;
|
|
43
|
+
|
|
44
|
+
// check if the last property in the chain is `calls`
|
|
45
|
+
if (argument.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'calls')) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const {
|
|
49
|
+
object
|
|
50
|
+
} = argument;
|
|
51
|
+
|
|
52
|
+
// check if the second-to-last property in the chain is `mock`
|
|
53
|
+
if (object.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(object.property, 'mock')) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
context.report({
|
|
57
|
+
messageId: 'preferMatcher',
|
|
58
|
+
node: matcher,
|
|
59
|
+
fix(fixer) {
|
|
60
|
+
return [
|
|
61
|
+
// remove the "mock.calls" accessor chain
|
|
62
|
+
fixer.removeRange([object.property.range[0] - 1, argument.range[1]]),
|
|
63
|
+
// replace the current matcher with "toHaveBeenCalledTimes"
|
|
64
|
+
fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], '.toHaveBeenCalledTimes')];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
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 `toHaveLength()`'
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
useToHaveLength: 'Use toHaveLength() instead'
|
|
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
|
+
parent: expect
|
|
32
|
+
} = jestFnCall.head.node;
|
|
33
|
+
if (expect.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const [argument] = expect.arguments;
|
|
37
|
+
const {
|
|
38
|
+
matcher
|
|
39
|
+
} = jestFnCall;
|
|
40
|
+
if (!_utils2.EqualityMatcher.hasOwnProperty((0, _utils2.getAccessorValue)(matcher)) || argument.type !== _utils.AST_NODE_TYPES.MemberExpression || !(0, _utils2.isSupportedAccessor)(argument.property, 'length')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
context.report({
|
|
44
|
+
fix(fixer) {
|
|
45
|
+
return [
|
|
46
|
+
// remove the "length" property accessor
|
|
47
|
+
fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]),
|
|
48
|
+
// replace the current matcher with "toHaveLength"
|
|
49
|
+
fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], '.toHaveLength')];
|
|
50
|
+
},
|
|
51
|
+
messageId: 'useToHaveLength',
|
|
52
|
+
node: matcher
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
function isEmptyFunction(node) {
|
|
10
|
+
if (!(0, _utils2.isFunction)(node)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return node.body.type === _utils.AST_NODE_TYPES.BlockStatement && !node.body.body.length;
|
|
14
|
+
}
|
|
15
|
+
function createTodoFixer(jestFnCall, fixer) {
|
|
16
|
+
if (jestFnCall.members.length) {
|
|
17
|
+
return (0, _utils2.replaceAccessorFixer)(fixer, jestFnCall.members[0], 'todo');
|
|
18
|
+
}
|
|
19
|
+
return fixer.replaceText(jestFnCall.head.node, `${jestFnCall.head.local}.todo`);
|
|
20
|
+
}
|
|
21
|
+
const isTargetedTestCase = jestFnCall => {
|
|
22
|
+
if (jestFnCall.members.some(s => (0, _utils2.getAccessorValue)(s) !== 'skip')) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// todo: we should support this too (needs custom fixer)
|
|
27
|
+
if (jestFnCall.name.startsWith('x')) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return !jestFnCall.name.startsWith('f');
|
|
31
|
+
};
|
|
32
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
33
|
+
name: __filename,
|
|
34
|
+
meta: {
|
|
35
|
+
docs: {
|
|
36
|
+
description: 'Suggest using `test.todo`'
|
|
37
|
+
},
|
|
38
|
+
messages: {
|
|
39
|
+
emptyTest: 'Prefer todo test case over empty test case',
|
|
40
|
+
unimplementedTest: 'Prefer todo test case over unimplemented test case'
|
|
41
|
+
},
|
|
42
|
+
fixable: 'code',
|
|
43
|
+
schema: [],
|
|
44
|
+
type: 'layout'
|
|
45
|
+
},
|
|
46
|
+
defaultOptions: [],
|
|
47
|
+
create(context) {
|
|
48
|
+
return {
|
|
49
|
+
CallExpression(node) {
|
|
50
|
+
const [title, callback] = node.arguments;
|
|
51
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context);
|
|
52
|
+
if (!title || jestFnCall?.type !== 'test' || !isTargetedTestCase(jestFnCall) || !(0, _utils2.isStringNode)(title)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (callback && isEmptyFunction(callback)) {
|
|
56
|
+
context.report({
|
|
57
|
+
messageId: 'emptyTest',
|
|
58
|
+
node,
|
|
59
|
+
fix: fixer => [fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(jestFnCall, fixer)]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if ((0, _utils2.hasOnlyOneArgument)(node)) {
|
|
63
|
+
context.report({
|
|
64
|
+
messageId: 'unimplementedTest',
|
|
65
|
+
node,
|
|
66
|
+
fix: fixer => createTodoFixer(jestFnCall, fixer)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
const isJestFnCall = (node, context) => {
|
|
10
|
+
if ((0, _utils2.parseJestFnCall)(node, context)) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return !!(0, _utils2.getNodeName)(node)?.startsWith('jest.');
|
|
14
|
+
};
|
|
15
|
+
const isNullOrUndefined = node => {
|
|
16
|
+
return node.type === _utils.AST_NODE_TYPES.Literal && node.value === null || (0, _utils2.isIdentifier)(node, 'undefined');
|
|
17
|
+
};
|
|
18
|
+
const shouldBeInHook = (node, context, allowedFunctionCalls = []) => {
|
|
19
|
+
switch (node.type) {
|
|
20
|
+
case _utils.AST_NODE_TYPES.ExpressionStatement:
|
|
21
|
+
return shouldBeInHook(node.expression, context, allowedFunctionCalls);
|
|
22
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
23
|
+
return !(isJestFnCall(node, context) || allowedFunctionCalls.includes((0, _utils2.getNodeName)(node)));
|
|
24
|
+
case _utils.AST_NODE_TYPES.VariableDeclaration:
|
|
25
|
+
{
|
|
26
|
+
if (node.kind === 'const') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return node.declarations.some(({
|
|
30
|
+
init
|
|
31
|
+
}) => init !== null && !isNullOrUndefined(init));
|
|
32
|
+
}
|
|
33
|
+
default:
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var _default = exports.default = (0, _utils2.createRule)({
|
|
38
|
+
name: __filename,
|
|
39
|
+
meta: {
|
|
40
|
+
docs: {
|
|
41
|
+
description: 'Require setup and teardown code to be within a hook'
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
useHook: 'This should be done within a hook'
|
|
45
|
+
},
|
|
46
|
+
type: 'suggestion',
|
|
47
|
+
schema: [{
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
allowedFunctionCalls: {
|
|
51
|
+
type: 'array',
|
|
52
|
+
items: {
|
|
53
|
+
type: 'string'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
additionalProperties: false
|
|
58
|
+
}]
|
|
59
|
+
},
|
|
60
|
+
defaultOptions: [{
|
|
61
|
+
allowedFunctionCalls: []
|
|
62
|
+
}],
|
|
63
|
+
create(context) {
|
|
64
|
+
const {
|
|
65
|
+
allowedFunctionCalls
|
|
66
|
+
} = context.options[0] ?? {};
|
|
67
|
+
const checkBlockBody = body => {
|
|
68
|
+
for (const statement of body) {
|
|
69
|
+
if (shouldBeInHook(statement, context, allowedFunctionCalls)) {
|
|
70
|
+
context.report({
|
|
71
|
+
node: statement,
|
|
72
|
+
messageId: 'useHook'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
Program(program) {
|
|
79
|
+
checkBlockBody(program.body);
|
|
80
|
+
},
|
|
81
|
+
CallExpression(node) {
|
|
82
|
+
if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['describe']) || node.arguments.length < 2) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const [, testFn] = node.arguments;
|
|
86
|
+
if (!(0, _utils2.isFunction)(testFn) || testFn.body.type !== _utils.AST_NODE_TYPES.BlockStatement) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
checkBlockBody(testFn.body.body);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|