Skip to content

Commit e9010c4

Browse files
authored
feat: new rule prefer-empty (#22)
* feat: new rule prefer-empty * updated snapshot * added binary expression support * added url * -u
1 parent a76d74c commit e9010c4

File tree

5 files changed

+465
-0
lines changed

5 files changed

+465
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ To enable this configuration use the `extends` property in your
6969
Name | ✔️ | 🛠 | Description
7070
----- | ----- | ----- | -----
7171
[prefer-checked](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-checked.md) | ✔️ | 🛠 | prefer toBeChecked over checking attributes
72+
[prefer-empty](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-empty.md) | ✔️ | 🛠 | Prefer toBeEmpty over checking innerHTML
7273
[prefer-enabled-disabled](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-enabled-disabled.md) | ✔️ | 🛠 | prefer toBeDisabled or toBeEnabled over checking attributes
7374
[prefer-focus](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-focus.md) | ✔️ | 🛠 | prefer toHaveFocus over checking document.activeElement
7475
[prefer-required](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-required.md) | ✔️ | 🛠 | prefer toBeRequired over checking properties

docs/rules/prefer-empty.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Prefer toBeEmpty over checking innerHTML / firstChild (prefer-empty)
2+
3+
This rule ensures people will use toBeEmpty() rather than checking dom nodes/properties. It is primarily aimed at consistently using jest-dom for readability.
4+
5+
## Rule Details
6+
7+
This autofixable rule aims to ensure usage of `.toBeEmpty()`
8+
9+
Examples of **incorrect** code for this rule:
10+
11+
```js
12+
13+
expect(element.innerHTML).toBe('foo');
14+
expect(element.innerHTML).not.toBe('foo');
15+
expect(element.firstChild).toBe('foo');
16+
expect(element.firstChild).not.toBe('foo');
17+
expect(getByText("foo").innerHTML).toBe('foo');
18+
expect(getByText("foo").innerHTML).not.toBe('foo');
19+
expect(getByText("foo").firstChild).toBe('foo');
20+
expect(getByText("foo").firstChild).not.toBe('foo');
21+
expect(element.innerHTML === '').toBe(true);
22+
expect(element.innerHTML !== '').toBe(true);
23+
expect(element.innerHTML === '').toBe(false);
24+
expect(element.innerHTML !== '').toBe(false);
25+
expect(element.firstChild === null).toBe(true);
26+
expect(element.firstChild !== null).toBe(false);
27+
expect(element.firstChild === null).toBe(false);
28+
29+
```
30+
31+
Examples of **correct** code for this rule:
32+
33+
```js
34+
expect(element.innerHTML).toBe('');
35+
expect(element.innerHTML).toBe(null);
36+
expect(element.innerHTML).not.toBe(null);
37+
expect(element.innerHTML).not.toBe('');
38+
expect(element.firstChild).toBeNull();
39+
expect(element.firstChild).toBe(null);
40+
expect(element.firstChild).not.toBe(null);
41+
expect(element.firstChild).not.toBeNull();
42+
expect(getByText('foo').innerHTML).toBe('');
43+
expect(getByText('foo').innerHTML).toStrictEqual('');
44+
expect(getByText('foo').innerHTML).toStrictEqual(null);
45+
expect(getByText('foo').firstChild).toBe(null);
46+
expect(getByText('foo').firstChild).not.toBe(null);
47+
expect(element.innerHTML === 'foo').toBe(true);
48+
49+
50+
```
51+
52+
## When Not To Use It
53+
54+
Don't use this rule if you don't care if people use `.toBeEmpty()`.
55+
56+
## Further Reading
57+
58+
<https://github.com/testing-library/jest-dom#tobeempty>
59+
60+
<https://github.com/testing-library/jest-dom/blob/master/src/to-be-empty.js>

lib/rules/prefer-empty.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/**
2+
* @fileoverview Prefer toBeEmpty over checking innerHTML
3+
* @author Ben Monro
4+
*/
5+
6+
module.exports = {
7+
meta: {
8+
docs: {
9+
description: 'Prefer toBeEmpty over checking innerHTML',
10+
category: 'jest-dom',
11+
recommended: true,
12+
url: 'prefer-empty',
13+
},
14+
fixable: 'code', // or "code" or "whitespace"
15+
},
16+
17+
create: function(context) {
18+
return {
19+
[`BinaryExpression[left.property.name='innerHTML'][right.value=''][parent.callee.name='expect'][parent.parent.property.name=/toBe$|to(Strict)?Equal/]`](
20+
node
21+
) {
22+
context.report({
23+
node,
24+
message: 'Use toBeEmpty instead of checking inner html.',
25+
fix(fixer) {
26+
return [
27+
fixer.removeRange([node.left.property.start - 1, node.end]),
28+
fixer.replaceText(
29+
node.parent.parent.property,
30+
!!node.parent.parent.parent.arguments[0].value ===
31+
node.operator.startsWith('=') // binary expression XNOR matcher boolean
32+
? 'toBeEmpty'
33+
: 'not.toBeEmpty'
34+
),
35+
fixer.remove(node.parent.parent.parent.arguments[0]),
36+
];
37+
},
38+
});
39+
},
40+
[`BinaryExpression[left.property.name='firstChild'][right.value=null][parent.callee.name='expect'][parent.parent.property.name=/toBe$|to(Strict)?Equal/]`](
41+
node
42+
) {
43+
context.report({
44+
node,
45+
message: 'Use toBeEmpty instead of checking inner html.',
46+
fix(fixer) {
47+
return [
48+
fixer.removeRange([node.left.property.start - 1, node.end]),
49+
fixer.replaceText(
50+
node.parent.parent.property,
51+
!!node.parent.parent.parent.arguments[0].value ===
52+
node.operator.startsWith('=') // binary expression XNOR matcher boolean
53+
? 'toBeEmpty'
54+
: 'not.toBeEmpty'
55+
),
56+
fixer.remove(node.parent.parent.parent.arguments[0]),
57+
];
58+
},
59+
});
60+
},
61+
[`MemberExpression[property.name = 'innerHTML'][parent.callee.name = 'expect'][parent.parent.property.name = /toBe$|to(Strict)?Equal/]`](
62+
node
63+
) {
64+
if (!node.parent.parent.parent.arguments[0].value) {
65+
context.report({
66+
node,
67+
message: 'Use toBeEmpty instead of checking inner html.',
68+
fix(fixer) {
69+
return [
70+
fixer.removeRange([node.property.start - 1, node.property.end]),
71+
fixer.replaceText(node.parent.parent.property, 'toBeEmpty'),
72+
fixer.remove(node.parent.parent.parent.arguments[0]),
73+
];
74+
},
75+
});
76+
}
77+
},
78+
[`MemberExpression[property.name='innerHTML'][parent.parent.property.name='not'][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal$/][parent.parent.object.callee.name='expect']`](
79+
node
80+
) {
81+
if (!node.parent.parent.parent.parent.arguments[0].value) {
82+
context.report({
83+
node,
84+
message: 'Use toBeEmpty instead of checking inner html.',
85+
fix(fixer) {
86+
return [
87+
fixer.removeRange([node.property.start - 1, node.property.end]),
88+
fixer.replaceText(
89+
node.parent.parent.parent.property,
90+
'toBeEmpty'
91+
),
92+
fixer.remove(node.parent.parent.parent.parent.arguments[0]),
93+
];
94+
},
95+
});
96+
}
97+
},
98+
[`MemberExpression[property.name = 'firstChild'][parent.callee.name = 'expect'][parent.parent.property.name = /toBeNull$/]`](
99+
node
100+
) {
101+
context.report({
102+
node,
103+
message: 'Use toBeEmpty instead of checking inner html.',
104+
fix(fixer) {
105+
return [
106+
fixer.removeRange([node.property.start - 1, node.property.end]),
107+
fixer.replaceText(node.parent.parent.property, 'toBeEmpty'),
108+
];
109+
},
110+
});
111+
},
112+
113+
[`MemberExpression[property.name='firstChild'][parent.parent.property.name='not'][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal$/][parent.parent.object.callee.name='expect']`](
114+
node
115+
) {
116+
if (node.parent.parent.parent.parent.arguments[0].value === null) {
117+
context.report({
118+
node,
119+
message: 'Use toBeEmpty instead of checking inner html.',
120+
fix(fixer) {
121+
return [
122+
fixer.removeRange([node.property.start - 1, node.property.end]),
123+
fixer.replaceText(
124+
node.parent.parent.parent.property,
125+
'toBeEmpty'
126+
),
127+
fixer.remove(node.parent.parent.parent.parent.arguments[0]),
128+
];
129+
},
130+
});
131+
}
132+
},
133+
134+
[`MemberExpression[property.name='firstChild'][parent.parent.property.name='not'][parent.parent.parent.property.name=/toBeNull$/][parent.parent.object.callee.name='expect']`](
135+
node
136+
) {
137+
context.report({
138+
node,
139+
message: 'Use toBeEmpty instead of checking inner html.',
140+
fix(fixer) {
141+
return [
142+
fixer.removeRange([node.property.start - 1, node.property.end]),
143+
fixer.replaceText(
144+
node.parent.parent.parent.property,
145+
'toBeEmpty'
146+
),
147+
];
148+
},
149+
});
150+
},
151+
[`MemberExpression[property.name = 'firstChild'][parent.callee.name = 'expect'][parent.parent.property.name = /toBe$|to(Strict)?Equal/]`](
152+
node
153+
) {
154+
if (node.parent.parent.parent.arguments[0].value === null) {
155+
context.report({
156+
node,
157+
message: 'Use toBeEmpty instead of checking inner html.',
158+
fix(fixer) {
159+
return [
160+
fixer.removeRange([node.property.start - 1, node.property.end]),
161+
fixer.replaceText(node.parent.parent.property, 'toBeEmpty'),
162+
fixer.remove(node.parent.parent.parent.arguments[0]),
163+
];
164+
},
165+
});
166+
}
167+
},
168+
};
169+
},
170+
};

tests/__snapshots__/index.test.js.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ Object {
1414
"fixable": "code",
1515
},
1616
},
17+
"prefer-empty": Object {
18+
"create": [Function],
19+
"meta": Object {
20+
"docs": Object {
21+
"category": "jest-dom",
22+
"description": "Prefer toBeEmpty over checking innerHTML",
23+
"recommended": true,
24+
"url": "prefer-empty",
25+
},
26+
"fixable": "code",
27+
},
28+
},
1729
"prefer-enabled-disabled": Object {
1830
"create": [Function],
1931
"meta": Object {

0 commit comments

Comments
 (0)