Skip to content

Commit 82a518a

Browse files
authoredSep 28, 2023
feat: Add prefer-to-have-count rule (#165)
* feat: Add `prefer-to-have-count` rule * docs: add `prefer-to-have-count` to the README
1 parent 6aeff37 commit 82a518a

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ command line option.\
105105
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
106106
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |
107107
| | 🔧 | | [prefer-to-contain](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-contain.md) | Suggest using `toContain()` |
108+
| | 🔧 | | [prefer-to-have-count](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md) | Suggest using `toHaveCount()` |
108109
| | 🔧 | | [prefer-to-have-length](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
109110
|| 🔧 | | [prefer-web-first-assertions](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-web-first-assertions.md) | Suggest using web first assertions |
110111
| | | | [require-top-level-describe](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `test.describe` block |

‎docs/rules/prefer-to-have-count.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Suggest using `toHaveCount()` (`prefer-to-have-count`)
2+
3+
In order to have a better failure message, `toHaveCount()` should be used upon
4+
asserting expectations on locators `count()` method.
5+
6+
## Rule details
7+
8+
This rule triggers a warning if `toBe()`, `toEqual()` or `toStrictEqual()` is
9+
used to assert locators `count()` method.
10+
11+
The following patterns are considered warnings:
12+
13+
```javascript
14+
expect(await files.count()).toBe(1);
15+
expect(await files.count()).toEqual(1);
16+
expect(await files.count()).toStrictEqual(1);
17+
```
18+
19+
The following pattern is **not** a warning:
20+
21+
```javascript
22+
await expect(files).toHaveCount(1);
23+
```

‎src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import preferLowercaseTitle from './rules/prefer-lowercase-title';
2020
import preferStrictEqual from './rules/prefer-strict-equal';
2121
import preferToBe from './rules/prefer-to-be';
2222
import preferToContain from './rules/prefer-to-contain';
23+
import preferToHaveCount from './rules/prefer-to-have-count';
2324
import preferToHaveLength from './rules/prefer-to-have-length';
2425
import preferWebFirstAssertions from './rules/prefer-web-first-assertions';
2526
import requireSoftAssertions from './rules/require-soft-assertions';
@@ -113,6 +114,7 @@ export = {
113114
'prefer-strict-equal': preferStrictEqual,
114115
'prefer-to-be': preferToBe,
115116
'prefer-to-contain': preferToContain,
117+
'prefer-to-have-count': preferToHaveCount,
116118
'prefer-to-have-length': preferToHaveLength,
117119
'prefer-web-first-assertions': preferWebFirstAssertions,
118120
'require-soft-assertions': requireSoftAssertions,

‎src/rules/prefer-to-have-count.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Rule } from 'eslint';
2+
import { replaceAccessorFixer } from '../utils/fixer';
3+
import { parseExpectCall } from '../utils/parseExpectCall';
4+
5+
const matchers = new Set(['toBe', 'toEqual', 'toStrictEqual']);
6+
7+
export default {
8+
create(context) {
9+
return {
10+
CallExpression(node) {
11+
const expectCall = parseExpectCall(node);
12+
if (!expectCall || !matchers.has(expectCall.matcherName)) {
13+
return;
14+
}
15+
16+
const [argument] = node.arguments;
17+
if (
18+
argument.type !== 'AwaitExpression' ||
19+
argument.argument.type !== 'CallExpression' ||
20+
argument.argument.callee.type !== 'MemberExpression'
21+
) {
22+
return;
23+
}
24+
25+
const callee = argument.argument.callee;
26+
context.report({
27+
fix(fixer) {
28+
return [
29+
// remove the "await" expression
30+
fixer.removeRange([
31+
argument.range![0],
32+
argument.range![0] + 'await'.length + 1,
33+
]),
34+
// remove the "count()" method accessor
35+
fixer.removeRange([
36+
callee.property.range![0] - 1,
37+
argument.argument.range![1],
38+
]),
39+
// replace the current matcher with "toHaveCount"
40+
replaceAccessorFixer(fixer, expectCall.matcher, 'toHaveCount'),
41+
// insert "await" to before "expect()"
42+
fixer.insertTextBefore(node, 'await '),
43+
];
44+
},
45+
messageId: 'useToHaveCount',
46+
node: expectCall.matcher,
47+
});
48+
},
49+
};
50+
},
51+
meta: {
52+
docs: {
53+
category: 'Best Practices',
54+
description: 'Suggest using `toHaveCount()`',
55+
recommended: false,
56+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md',
57+
},
58+
fixable: 'code',
59+
messages: {
60+
useToHaveCount: 'Use toHaveCount() instead',
61+
},
62+
schema: [],
63+
type: 'suggestion',
64+
},
65+
} as Rule.RuleModule;
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import rule from '../../src/rules/prefer-to-have-count';
2+
import { runRuleTester } from '../utils/rule-tester';
3+
4+
runRuleTester('prefer-to-have-count', rule, {
5+
invalid: [
6+
{
7+
code: 'expect(await files.count()).toBe(1)',
8+
errors: [
9+
{ column: 29, endColumn: 33, line: 1, messageId: 'useToHaveCount' },
10+
],
11+
output: 'await expect(files).toHaveCount(1)',
12+
},
13+
{
14+
code: 'expect(await files.count()).not.toBe(1)',
15+
errors: [
16+
{ column: 33, endColumn: 37, line: 1, messageId: 'useToHaveCount' },
17+
],
18+
output: 'await expect(files).not.toHaveCount(1)',
19+
},
20+
{
21+
code: 'expect.soft(await files["count"]()).not.toBe(1)',
22+
errors: [
23+
{ column: 41, endColumn: 45, line: 1, messageId: 'useToHaveCount' },
24+
],
25+
output: 'await expect.soft(files).not.toHaveCount(1)',
26+
},
27+
{
28+
code: 'expect(await files["count"]()).not["toBe"](1)',
29+
errors: [
30+
{ column: 36, endColumn: 42, line: 1, messageId: 'useToHaveCount' },
31+
],
32+
output: 'await expect(files).not["toHaveCount"](1)',
33+
},
34+
{
35+
code: 'expect(await files.count())[`toEqual`](1)',
36+
errors: [
37+
{ column: 29, endColumn: 38, line: 1, messageId: 'useToHaveCount' },
38+
],
39+
output: 'await expect(files)[`toHaveCount`](1)',
40+
},
41+
{
42+
code: 'expect(await files.count()).toStrictEqual(1)',
43+
errors: [
44+
{ column: 29, endColumn: 42, line: 1, messageId: 'useToHaveCount' },
45+
],
46+
output: 'await expect(files).toHaveCount(1)',
47+
},
48+
{
49+
code: 'expect(await files.count()).not.toStrictEqual(1)',
50+
errors: [
51+
{ column: 33, endColumn: 46, line: 1, messageId: 'useToHaveCount' },
52+
],
53+
output: 'await expect(files).not.toHaveCount(1)',
54+
},
55+
],
56+
valid: [
57+
'await expect(files).toHaveCount(1)',
58+
"expect(files.name).toBe('file')",
59+
"expect(files['name']).toBe('file')",
60+
"expect(files[`name`]).toBe('file')",
61+
'expect(result).toBe(true)',
62+
`expect(user.getUserName(5)).not.toEqual('Paul')`,
63+
`expect(user.getUserName(5)).not.toEqual('Paul')`,
64+
'expect(a)',
65+
],
66+
});

0 commit comments

Comments
 (0)
Please sign in to comment.