Skip to content

Commit

Permalink
fix(rules): added more use cases for to have class and in docucment (#…
Browse files Browse the repository at this point in the history
…120)

* fixes to have class and in docucment

* removed unused code

* handle the nots

* Update src/rules/prefer-to-have-class.js
  • Loading branch information
benmonro committed Dec 4, 2020
1 parent 00b65fa commit 7506f94
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/__tests__/lib/rules/prefer-in-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ const valid = [
expect(content).toBeNull()
}
)`,
`expect(await screen.findAllByRole("button")).toHaveLength(
NUM_BUTTONS
)`,
`expect(await screen.findAllByRole("button")).not.toHaveLength(
NUM_BUTTONS
)`,
];
const invalid = [
// Invalid cases that applies to all variants
Expand Down
74 changes: 74 additions & 0 deletions src/__tests__/lib/rules/prefer-prefer-to-have-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,79 @@ ruleTester.run("prefer-to-have-class", rule, {
code: `expect(el.classList[0]).toContain(("fo"))`,
errors,
},

{
code: `expect(el.classList).toEqual(expect.objectContaining({0:"foo"}))`,
errors,
},

{
code: `expect(el.classList).toContain(className)`,
errors,
output: `expect(el).toHaveClass(className)`,
},
{
code: `expect(el.classList).toContain("className")`,
errors,
output: `expect(el).toHaveClass("className")`,
},

{
code: `expect(el.classList).toContain(foo("bar"))`,
errors,
output: `expect(el).toHaveClass(foo("bar"))`,
},

{
code: `expect(el.classList.contains("foo")).toBe(false)`,
errors,
output: `expect(el).not.toHaveClass("foo")`,
},

{
code: `expect(el.classList.contains("foo")).toBe(true)`,
errors,
output: `expect(el).toHaveClass("foo")`,
},
{
code: `expect(el.classList.contains("foo")).toBeTruthy()`,
errors,
output: `expect(el).toHaveClass("foo")`,
},
{
code: `expect(el.classList).not.toContain("bar")`,
errors,
output: `expect(el).not.toHaveClass("bar")`,
},
{
code: `expect(el.classList).not.toBe("bar")`,
errors,
},
{
code: `expect(el.classList[0]).not.toContain(("fo"))`,
errors,
},

{
code: `expect(el.classList).not.toEqual(expect.objectContaining({0:"foo"}))`,
errors,
},

{
code: `expect(el.classList).not.toContain(className)`,
errors,
output: `expect(el).not.toHaveClass(className)`,
},
{
code: `expect(el.classList).not.toContain("className")`,
errors,
output: `expect(el).not.toHaveClass("className")`,
},

{
code: `expect(el.classList).not.toContain(foo("bar"))`,
errors,
output: `expect(el).not.toHaveClass(foo("bar"))`,
},
],
});
5 changes: 4 additions & 1 deletion src/rules/prefer-in-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export const create = (context) => {
}) {
if (!queryNode || (!queryNode.name && !queryNode.property)) return;
// toHaveLength() is only invalid with 0 or 1
if (matcherNode.name === "toHaveLength" && matcherArguments[0].value > 1) {
if (
matcherNode.name === "toHaveLength" &&
(matcherArguments[0].type !== "Literal" || matcherArguments[0].value > 1)
) {
return;
}

Expand Down
61 changes: 54 additions & 7 deletions src/rules/prefer-to-have-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,41 @@ export const meta = {
};

export const create = (context) => ({
//expect(el.classList.contains("foo")).toBe(true)
[`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/]`](
node
) {
const classValue = node.callee.object.arguments[0].arguments[0];
const checkedProp = node.callee.object.arguments[0].callee.object.object;
const matcher = node.callee.property;
const [matcherArg] = node.arguments;
const [expectArg] = node.callee.object.arguments;
const isTruthy =
(matcher.name === "toBe" && matcherArg.value === true) ||
matcher.name === "toBeTruthy";

context.report({
node: matcher,
messageId,
fix(fixer) {
return [
fixer.removeRange([checkedProp.range[1], expectArg.range[1]]),

fixer.replaceText(matcher, `${isTruthy ? "" : "not."}toHaveClass`),
matcherArg
? fixer.replaceText(
matcherArg,
context.getSourceCode().getText(classValue)
)
: fixer.insertTextBeforeRange(
[node.range[1] - 1, node.range[1] - 1],
context.getSourceCode().getText(classValue)
),
];
},
});
},

//expect(el.classList[0]).toBe("bar")
[`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$/]`](
node
Expand Down Expand Up @@ -62,15 +97,23 @@ export const create = (context) => ({
messageId,
});
},
//expect(el.className).toBe("bar") / toStrict?Equal / toContain
[`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$/]`](
//expect(el.className | el.classList).toBe("bar") / toStrict?Equal / toContain
[`CallExpression[callee.object.callee.name=expect][callee.object.arguments.0.property.name=/class(Name|List)/][callee.property.name=/toBe$|to(Strict)?Equal|toContain/]`](
node
) {
const checkedProp = node.callee.object.arguments[0].property;
const [classValue] = node.arguments;
const matcher = node.callee.property;
const classNameProp = node.callee.object.arguments[0].object;

// don't report here if using `expect.foo()`

if (
classValue.type === "CallExpression" &&
classValue.callee.type === "MemberExpression" &&
classValue.callee.object.name === "expect"
)
return;
context.report({
node: matcher,
messageId,
Expand All @@ -91,19 +134,21 @@ export const create = (context) => ({
});
},

//expect(el.className).toEqual(expect.stringContaining("foo")) / toStrictEqual
[`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]`](
//expect(el.className | el.classList).toEqual(expect.stringContaining("foo") | objectContaining) / toStrictEqual
[`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]`](
node
) {
const className = node.callee.object.arguments[0].property;
const [classValue] = node.arguments[0].arguments;
const matcher = node.callee.property;
const classNameProp = node.callee.object.arguments[0].object;
const matcherArg = node.arguments[0].callee.property;

context.report({
node: matcher,
messageId,
fix(fixer) {
if (matcherArg.name !== "stringContaining") return;
return [
fixer.removeRange([classNameProp.range[1], className.range[1]]),
fixer.replaceText(matcher, "toHaveClass"),
Expand All @@ -116,11 +161,10 @@ export const create = (context) => ({
});
},

//expect(screen.getByRole("button").className).not.toBe("foo"); / toStrict?Equal / toContain
[`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$/]`](
//expect(screen.getByRole("button").className | classList).not.toBe("foo"); / toStrict?Equal / toContain
[`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/]`](
node
) {
//[callee.object.arguments.0.property.name=className][callee.property.name=/toBe$|to(Strict)?Equal|toContain/][arguments.0.type=/Literal$/]
const className = node.callee.object.object.arguments[0].property;
const [classValue] = node.arguments;
const matcher = node.callee.property;
Expand All @@ -130,6 +174,9 @@ export const create = (context) => ({
node: matcher,
messageId,
fix(fixer) {
if (className.name === "classList" && matcher.name !== "toContain")
return;

return [
fixer.removeRange([classNameProp.range[1], className.range[1]]),
fixer.replaceText(matcher, "toHaveClass"),
Expand Down

0 comments on commit 7506f94

Please sign in to comment.