Skip to content

Commit 95160d2

Browse files
authored
feat(eslint-plugin-jest): add prefer-called-with rule (#1025)
1 parent 32ab52b commit 95160d2

6 files changed

Lines changed: 203 additions & 0 deletions

File tree

internal/plugins/jest/all.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_mocks_import"
1515
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_standalone_expect"
1616
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/no_test_prefixes"
17+
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/prefer_called_with"
1718
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/prefer_strict_equal"
1819
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/prefer_to_be"
1920
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/prefer_to_contain"
@@ -39,6 +40,7 @@ func GetAllRules() []rule.Rule {
3940
no_mocks_import.NoMocksImportRule,
4041
no_standalone_expect.NoStandaloneExpectRule,
4142
no_test_prefixes.NoTestPrefixesRule,
43+
prefer_called_with.PreferCalledWithRule,
4244
prefer_strict_equal.PreferStrictEqualRule,
4345
prefer_to_be.PreferToBeRule,
4446
prefer_to_contain.PreferToContainRule,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package prefer_called_with
2+
3+
import (
4+
"slices"
5+
6+
"github.com/microsoft/typescript-go/shim/ast"
7+
jestUtils "github.com/web-infra-dev/rslint/internal/plugins/jest/utils"
8+
"github.com/web-infra-dev/rslint/internal/rule"
9+
)
10+
11+
var preferCalledWithMatcherNames = map[string]bool{
12+
"toBeCalled": true,
13+
"toHaveBeenCalled": true,
14+
}
15+
16+
// Message builder
17+
18+
func buildPreferCalledWithMessage(matcherName string) rule.RuleMessage {
19+
return rule.RuleMessage{
20+
Id: "preferCalledWith",
21+
Description: "Prefer " + matcherName + "With(/* expected args */)",
22+
Data: map[string]string{
23+
"matcherName": matcherName,
24+
},
25+
}
26+
}
27+
28+
var PreferCalledWithRule = rule.Rule{
29+
Name: "jest/prefer-called-with",
30+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
31+
return rule.RuleListeners{
32+
ast.KindCallExpression: func(node *ast.Node) {
33+
jestFnCall := jestUtils.ParseJestFnCall(node, ctx)
34+
if jestFnCall == nil ||
35+
jestFnCall.Kind != jestUtils.JestFnTypeExpect ||
36+
slices.Contains(jestFnCall.Modifiers, "not") {
37+
return
38+
}
39+
40+
matcherName := jestFnCall.Matcher
41+
if !preferCalledWithMatcherNames[matcherName] {
42+
return
43+
}
44+
45+
reportNode := node
46+
if jestFnCall.MatcherEntry != nil && jestFnCall.MatcherEntry.Node != nil {
47+
reportNode = jestFnCall.MatcherEntry.Node
48+
}
49+
50+
ctx.ReportNode(reportNode, buildPreferCalledWithMessage(matcherName))
51+
},
52+
}
53+
},
54+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# prefer-called-with
2+
3+
## Rule Details
4+
5+
The `toHaveBeenCalled()` and `toBeCalled()` matchers assert that a mock function
6+
has been called one or more times, without checking the arguments passed. The
7+
assertion is stronger when arguments are also validated using
8+
`toHaveBeenCalledWith()` or `toBeCalledWith()`. When some arguments are difficult
9+
to check, using generic matchers such as `expect.anything()` at least enforces the
10+
number and position of arguments.
11+
12+
Examples of **incorrect** code for this rule:
13+
14+
```js
15+
expect(someFunction).toBeCalled();
16+
17+
expect(someFunction).toHaveBeenCalled();
18+
```
19+
20+
Examples of **correct** code for this rule:
21+
22+
```js
23+
expect(noArgsFunction).toHaveBeenCalledWith();
24+
25+
expect(roughArgsFunction).toHaveBeenCalledWith(
26+
expect.anything(),
27+
expect.any(Date),
28+
);
29+
30+
expect(anyArgsFunction).toHaveBeenCalledTimes(1);
31+
32+
expect(uncalledFunction).not.toHaveBeenCalled();
33+
```
34+
35+
## Original Documentation
36+
37+
- [jest/prefer-called-with](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-called-with.md)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package prefer_called_with_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/web-infra-dev/rslint/internal/plugins/jest/fixtures"
7+
"github.com/web-infra-dev/rslint/internal/plugins/jest/rules/prefer_called_with"
8+
"github.com/web-infra-dev/rslint/internal/rule_tester"
9+
)
10+
11+
func TestPreferCalledWithRule(t *testing.T) {
12+
rule_tester.RunRuleTester(
13+
fixtures.GetRootDir(),
14+
"tsconfig.json",
15+
t,
16+
&prefer_called_with.PreferCalledWithRule,
17+
[]rule_tester.ValidTestCase{
18+
{Code: `expect(fn).toBeCalledWith();`},
19+
{Code: `expect(fn).toHaveBeenCalledWith();`},
20+
{Code: `expect(fn).toBeCalledWith(expect.anything());`},
21+
{Code: `expect(fn).toHaveBeenCalledWith(expect.anything());`},
22+
{Code: `expect(fn).not.toBeCalled();`},
23+
{Code: `expect(fn).rejects.not.toBeCalled();`},
24+
{Code: `expect(fn).not.toHaveBeenCalled();`},
25+
{Code: `expect(fn).not.toBeCalledWith();`},
26+
{Code: `expect(fn).not.toHaveBeenCalledWith();`},
27+
{Code: `expect(fn).resolves.not.toHaveBeenCalledWith();`},
28+
{Code: `expect(fn).toBeCalledTimes(0);`},
29+
{Code: `expect(fn).toHaveBeenCalledTimes(0);`},
30+
{Code: `expect(fn);`},
31+
},
32+
[]rule_tester.InvalidTestCase{
33+
{
34+
Code: `expect(fn).toBeCalled();`,
35+
Errors: []rule_tester.InvalidTestCaseError{
36+
{MessageId: "preferCalledWith", Line: 1, Column: 12},
37+
},
38+
},
39+
{
40+
Code: `expect(fn).resolves.toBeCalled();`,
41+
Errors: []rule_tester.InvalidTestCaseError{
42+
{MessageId: "preferCalledWith", Line: 1, Column: 21},
43+
},
44+
},
45+
{
46+
Code: `expect(fn).toHaveBeenCalled();`,
47+
Errors: []rule_tester.InvalidTestCaseError{
48+
{MessageId: "preferCalledWith", Line: 1, Column: 12},
49+
},
50+
},
51+
},
52+
)
53+
}

packages/rslint-test-tools/rstest.config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ export default defineConfig({
438438
'./tests/eslint-plugin-jest/rules/no-mocks-import.test.ts',
439439
'./tests/eslint-plugin-jest/rules/no-standalone-expect.test.ts',
440440
'./tests/eslint-plugin-jest/rules/no-test-prefixes.test.ts',
441+
'./tests/eslint-plugin-jest/rules/prefer-called-with.test.ts',
441442
'./tests/eslint-plugin-jest/rules/prefer-strict-equal.test.ts',
442443
'./tests/eslint-plugin-jest/rules/prefer-to-be.test.ts',
443444
'./tests/eslint-plugin-jest/rules/prefer-to-contain.test.ts',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { RuleTester } from '../rule-tester';
2+
3+
const ruleTester = new RuleTester();
4+
5+
ruleTester.run('prefer-called-with', {} as never, {
6+
valid: [
7+
{ code: 'expect(fn).toBeCalledWith();' },
8+
{ code: 'expect(fn).toHaveBeenCalledWith();' },
9+
{ code: 'expect(fn).toBeCalledWith(expect.anything());' },
10+
{ code: 'expect(fn).toHaveBeenCalledWith(expect.anything());' },
11+
{ code: 'expect(fn).not.toBeCalled();' },
12+
{ code: 'expect(fn).rejects.not.toBeCalled();' },
13+
{ code: 'expect(fn).not.toHaveBeenCalled();' },
14+
{ code: 'expect(fn).not.toBeCalledWith();' },
15+
{ code: 'expect(fn).not.toHaveBeenCalledWith();' },
16+
{ code: 'expect(fn).resolves.not.toHaveBeenCalledWith();' },
17+
{ code: 'expect(fn).toBeCalledTimes(0);' },
18+
{ code: 'expect(fn).toHaveBeenCalledTimes(0);' },
19+
{ code: 'expect(fn);' },
20+
],
21+
invalid: [
22+
{
23+
code: 'expect(fn).toBeCalled();',
24+
errors: [
25+
{
26+
messageId: 'preferCalledWith',
27+
data: { matcherName: 'toBeCalled' },
28+
column: 12,
29+
line: 1,
30+
},
31+
],
32+
},
33+
{
34+
code: 'expect(fn).resolves.toBeCalled();',
35+
errors: [
36+
{
37+
messageId: 'preferCalledWith',
38+
data: { matcherName: 'toBeCalled' },
39+
column: 21,
40+
line: 1,
41+
},
42+
],
43+
},
44+
{
45+
code: 'expect(fn).toHaveBeenCalled();',
46+
errors: [
47+
{
48+
messageId: 'preferCalledWith',
49+
data: { matcherName: 'toHaveBeenCalled' },
50+
column: 12,
51+
line: 1,
52+
},
53+
],
54+
},
55+
],
56+
});

0 commit comments

Comments
 (0)