Skip to content

Commit 7506f94

Browse files
authored
fix(rules): added more use cases for to have class and in docucment (#120)
* fixes to have class and in docucment * removed unused code * handle the nots * Update src/rules/prefer-to-have-class.js
1 parent 00b65fa commit 7506f94

File tree

4 files changed

+138
-8
lines changed

4 files changed

+138
-8
lines changed

src/__tests__/lib/rules/prefer-in-document.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ const valid = [
6868
expect(content).toBeNull()
6969
}
7070
)`,
71+
`expect(await screen.findAllByRole("button")).toHaveLength(
72+
NUM_BUTTONS
73+
)`,
74+
`expect(await screen.findAllByRole("button")).not.toHaveLength(
75+
NUM_BUTTONS
76+
)`,
7177
];
7278
const invalid = [
7379
// Invalid cases that applies to all variants

src/__tests__/lib/rules/prefer-prefer-to-have-class.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,79 @@ ruleTester.run("prefer-to-have-class", rule, {
133133
code: `expect(el.classList[0]).toContain(("fo"))`,
134134
errors,
135135
},
136+
137+
{
138+
code: `expect(el.classList).toEqual(expect.objectContaining({0:"foo"}))`,
139+
errors,
140+
},
141+
142+
{
143+
code: `expect(el.classList).toContain(className)`,
144+
errors,
145+
output: `expect(el).toHaveClass(className)`,
146+
},
147+
{
148+
code: `expect(el.classList).toContain("className")`,
149+
errors,
150+
output: `expect(el).toHaveClass("className")`,
151+
},
152+
153+
{
154+
code: `expect(el.classList).toContain(foo("bar"))`,
155+
errors,
156+
output: `expect(el).toHaveClass(foo("bar"))`,
157+
},
158+
159+
{
160+
code: `expect(el.classList.contains("foo")).toBe(false)`,
161+
errors,
162+
output: `expect(el).not.toHaveClass("foo")`,
163+
},
164+
165+
{
166+
code: `expect(el.classList.contains("foo")).toBe(true)`,
167+
errors,
168+
output: `expect(el).toHaveClass("foo")`,
169+
},
170+
{
171+
code: `expect(el.classList.contains("foo")).toBeTruthy()`,
172+
errors,
173+
output: `expect(el).toHaveClass("foo")`,
174+
},
175+
{
176+
code: `expect(el.classList).not.toContain("bar")`,
177+
errors,
178+
output: `expect(el).not.toHaveClass("bar")`,
179+
},
180+
{
181+
code: `expect(el.classList).not.toBe("bar")`,
182+
errors,
183+
},
184+
{
185+
code: `expect(el.classList[0]).not.toContain(("fo"))`,
186+
errors,
187+
},
188+
189+
{
190+
code: `expect(el.classList).not.toEqual(expect.objectContaining({0:"foo"}))`,
191+
errors,
192+
},
193+
194+
{
195+
code: `expect(el.classList).not.toContain(className)`,
196+
errors,
197+
output: `expect(el).not.toHaveClass(className)`,
198+
},
199+
{
200+
code: `expect(el.classList).not.toContain("className")`,
201+
errors,
202+
output: `expect(el).not.toHaveClass("className")`,
203+
},
204+
205+
{
206+
code: `expect(el.classList).not.toContain(foo("bar"))`,
207+
errors,
208+
output: `expect(el).not.toHaveClass(foo("bar"))`,
209+
},
136210
],
137211
});

src/rules/prefer-in-document.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export const create = (context) => {
3838
}) {
3939
if (!queryNode || (!queryNode.name && !queryNode.property)) return;
4040
// toHaveLength() is only invalid with 0 or 1
41-
if (matcherNode.name === "toHaveLength" && matcherArguments[0].value > 1) {
41+
if (
42+
matcherNode.name === "toHaveLength" &&
43+
(matcherArguments[0].type !== "Literal" || matcherArguments[0].value > 1)
44+
) {
4245
return;
4346
}
4447

src/rules/prefer-to-have-class.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,41 @@ export const meta = {
2222
};
2323

2424
export const create = (context) => ({
25+
//expect(el.classList.contains("foo")).toBe(true)
26+
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.callee.object.property.name=classList][callee.object.arguments.0.callee.property.name=contains][callee.property.name=/toBe(Truthy|Falsy)?|to(Strict)?Equal/]`](
27+
node
28+
) {
29+
const classValue = node.callee.object.arguments[0].arguments[0];
30+
const checkedProp = node.callee.object.arguments[0].callee.object.object;
31+
const matcher = node.callee.property;
32+
const [matcherArg] = node.arguments;
33+
const [expectArg] = node.callee.object.arguments;
34+
const isTruthy =
35+
(matcher.name === "toBe" && matcherArg.value === true) ||
36+
matcher.name === "toBeTruthy";
37+
38+
context.report({
39+
node: matcher,
40+
messageId,
41+
fix(fixer) {
42+
return [
43+
fixer.removeRange([checkedProp.range[1], expectArg.range[1]]),
44+
45+
fixer.replaceText(matcher, `${isTruthy ? "" : "not."}toHaveClass`),
46+
matcherArg
47+
? fixer.replaceText(
48+
matcherArg,
49+
context.getSourceCode().getText(classValue)
50+
)
51+
: fixer.insertTextBeforeRange(
52+
[node.range[1] - 1, node.range[1] - 1],
53+
context.getSourceCode().getText(classValue)
54+
),
55+
];
56+
},
57+
});
58+
},
59+
2560
//expect(el.classList[0]).toBe("bar")
2661
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.object.property.name=classList][callee.property.name=/toBe$|to(Strict)?Equal|toContain/][arguments.0.type=/Literal$/]`](
2762
node
@@ -62,15 +97,23 @@ export const create = (context) => ({
6297
messageId,
6398
});
6499
},
65-
//expect(el.className).toBe("bar") / toStrict?Equal / toContain
66-
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.property.name=/class(Name|List)/][callee.property.name=/toBe$|to(Strict)?Equal|toContain/][arguments.0.type=/Literal$/]`](
100+
//expect(el.className | el.classList).toBe("bar") / toStrict?Equal / toContain
101+
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.property.name=/class(Name|List)/][callee.property.name=/toBe$|to(Strict)?Equal|toContain/]`](
67102
node
68103
) {
69104
const checkedProp = node.callee.object.arguments[0].property;
70105
const [classValue] = node.arguments;
71106
const matcher = node.callee.property;
72107
const classNameProp = node.callee.object.arguments[0].object;
73108

109+
// don't report here if using `expect.foo()`
110+
111+
if (
112+
classValue.type === "CallExpression" &&
113+
classValue.callee.type === "MemberExpression" &&
114+
classValue.callee.object.name === "expect"
115+
)
116+
return;
74117
context.report({
75118
node: matcher,
76119
messageId,
@@ -91,19 +134,21 @@ export const create = (context) => ({
91134
});
92135
},
93136

94-
//expect(el.className).toEqual(expect.stringContaining("foo")) / toStrictEqual
95-
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.property.name=className][callee.property.name=/to(Strict)?Equal/][arguments.0.callee.object.name=expect][arguments.0.callee.property.name=stringContaining]`](
137+
//expect(el.className | el.classList).toEqual(expect.stringContaining("foo") | objectContaining) / toStrictEqual
138+
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.property.name=/class(Name|List)/][callee.property.name=/to(Strict)?Equal/][arguments.0.callee.object.name=expect]`](
96139
node
97140
) {
98141
const className = node.callee.object.arguments[0].property;
99142
const [classValue] = node.arguments[0].arguments;
100143
const matcher = node.callee.property;
101144
const classNameProp = node.callee.object.arguments[0].object;
145+
const matcherArg = node.arguments[0].callee.property;
102146

103147
context.report({
104148
node: matcher,
105149
messageId,
106150
fix(fixer) {
151+
if (matcherArg.name !== "stringContaining") return;
107152
return [
108153
fixer.removeRange([classNameProp.range[1], className.range[1]]),
109154
fixer.replaceText(matcher, "toHaveClass"),
@@ -116,11 +161,10 @@ export const create = (context) => ({
116161
});
117162
},
118163

119-
//expect(screen.getByRole("button").className).not.toBe("foo"); / toStrict?Equal / toContain
120-
[`CallExpression[callee.object.object.callee.name=expect][callee.object.object.arguments.0.property.name=className][callee.object.property.name=not][callee.property.name=/toBe$|to(Strict)?Equal|toContain/][arguments.0.type=/Literal$/]`](
164+
//expect(screen.getByRole("button").className | classList).not.toBe("foo"); / toStrict?Equal / toContain
165+
[`CallExpression[callee.object.object.callee.name=expect][callee.object.object.arguments.0.property.name=/class(Name|List)/][callee.object.property.name=not][callee.property.name=/toBe$|to(Strict)?Equal|toContain/]`](
121166
node
122167
) {
123-
//[callee.object.arguments.0.property.name=className][callee.property.name=/toBe$|to(Strict)?Equal|toContain/][arguments.0.type=/Literal$/]
124168
const className = node.callee.object.object.arguments[0].property;
125169
const [classValue] = node.arguments;
126170
const matcher = node.callee.property;
@@ -130,6 +174,9 @@ export const create = (context) => ({
130174
node: matcher,
131175
messageId,
132176
fix(fixer) {
177+
if (className.name === "classList" && matcher.name !== "toContain")
178+
return;
179+
133180
return [
134181
fixer.removeRange([classNameProp.range[1], className.range[1]]),
135182
fixer.replaceText(matcher, "toHaveClass"),

0 commit comments

Comments
 (0)