Skip to content

Commit

Permalink
feat(rules): added new rule for prefer-to-have-style (#76)
Browse files Browse the repository at this point in the history
* feat(rules): added new rule prefer-to-have-style

* updated readme

* updated readme

* updated readme

* updated readme

fix(rules): use .range instead of start/end
  • Loading branch information
benmonro authored Aug 31, 2020
1 parent e3a12dc commit 8a55022
Show file tree
Hide file tree
Showing 14 changed files with 377 additions and 117 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,22 @@ module.exports = {

## Supported Rules

✔️ indicates that a rule is recommended for all users.
👍 indicates that a rule is recommended for all users.

🛠 indicates that a rule is fixable.
🔧 indicates that a rule is fixable.

<!-- __BEGIN AUTOGENERATED TABLE__ -->

| Name | ✔️ | 🛠 | Description |
| Name | 👍 | 🔧 | Description |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | -------------------------------------------------------------- |
| [prefer-checked](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-checked.md) | ✔️ | 🛠 | prefer toBeChecked over checking attributes |
| [prefer-empty](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-empty.md) | ✔️ | 🛠 | Prefer toBeEmpty over checking innerHTML |
| [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 |
| [prefer-focus](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-focus.md) | ✔️ | 🛠 | prefer toHaveFocus over checking document.activeElement |
| [prefer-required](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-required.md) | ✔️ | 🛠 | prefer toBeRequired over checking properties |
| [prefer-to-have-attribute](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-attribute.md) | ✔️ | 🛠 | prefer toHaveAttribute over checking getAttribute/hasAttribute |
| [prefer-to-have-text-content](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-text-content.md) | ✔️ | 🛠 | Prefer toHaveTextContent over checking element.textContent |
| [prefer-checked](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-checked.md) | 👍 | 🔧 | prefer toBeChecked over checking attributes |
| [prefer-empty](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-empty.md) | 👍 | 🔧 | Prefer toBeEmpty over checking innerHTML |
| [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 |
| [prefer-focus](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-focus.md) | 👍 | 🔧 | prefer toHaveFocus over checking document.activeElement |
| [prefer-required](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-required.md) | 👍 | 🔧 | prefer toBeRequired over checking properties |
| [prefer-to-have-attribute](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-attribute.md) | 👍 | 🔧 | prefer toHaveAttribute over checking getAttribute/hasAttribute |
| [prefer-to-have-style](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-style.md) | 👍 | 🔧 | prefer toHaveStyle over checking element style |
| [prefer-to-have-text-content](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-text-content.md) | 👍 | 🔧 | Prefer toHaveTextContent over checking element.textContent |

<!-- __END AUTOGENERATED TABLE__ -->

Expand Down Expand Up @@ -156,6 +157,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
6 changes: 3 additions & 3 deletions build/generate-readme-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ const expectedTableLines = Object.keys(rules)
lines.push(
[
`[${ruleId}](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/${ruleId}.md)`,
rule.meta.docs.recommended ? "✔️" : "",
rule.meta.fixable ? "🛠" : "",
rule.meta.docs.recommended ? "👍" : "",
rule.meta.fixable ? "🔧" : "",
rule.meta.docs.description,
].join(" | ")
);

return lines;
},
["Name | ✔️ | 🛠 | Description", "----- | ----- | ----- | -----"]
["Name | 👍 | 🔧 | Description", "----- | ----- | ----- | -----"]
)
.join("\n");

Expand Down
39 changes: 39 additions & 0 deletions docs/rules/prefer-to-have-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# prefer toHaveProperty over checking element.style (prefer-to-have-style)

This rule is an autofixable rule that reports usages of checking element.style in expect statements in preference of using the jest-dom
`toHaveStyle` matcher.

## Rule Details

Examples of **incorrect** code for this rule:

```js
expect(el.style.foo).toBe("bar");
expect(el.style.foo).not.toBe("bar");
expect(el.style).toHaveProperty("background-color", "green");
expect(screen.getByTestId("foo").style["scroll-snap-type"]).toBe("x mandatory");
expect(el.style).toContain("background-color");
expect(el.style).not.toContain("background-color");
expect(el).toHaveAttribute(
"style",
"background-color: green; border-width: 10px; color: blue;"
);
```

Examples of **correct** code for this rule:

```js
expect(el).toHaveStyle({ foo: "bar" });
expect(el.style).toMatchSnapshot();
expect(el.style).toEqual(foo);
```

## When Not To Use It

If you don't care about using built in matchers for checking style on dom
elements.

## Further Reading

- [jest-dom toHaveStyle](https://github.com/testing-library/jest-dom#tohavestyle)
- [ElementCSSInlineStyle.style](https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"requireindex": "^1.2.0"
},
"devDependencies": {
"jest-extended": "^0.11.5",
"kcd-scripts": "^6.0.0"
},
"peerDependencies": {
Expand Down
85 changes: 0 additions & 85 deletions src/__tests__/__snapshots__/index.test.js.snap

This file was deleted.

11 changes: 10 additions & 1 deletion src/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { rules, generateRecommendedConfig } from "../index";
import "jest-extended";

it("should have all the rules", () => {
expect(rules).toMatchSnapshot();
expect(Object.keys(rules).length).toBeGreaterThan(0);
});

it.each(Object.keys(rules))("%s should export required fields", (ruleName) => {
const rule = rules[ruleName];
expect(rule).toHaveProperty("create", expect.any(Function));
expect(rule.meta.docs.url).not.toBeEmpty();
expect(rule.meta.docs.category).toBe("jest-dom");
expect(rule.meta.docs.description).not.toBeEmpty();
});

it("should have a recommended config with recommended rules", () => {
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/lib/rules/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"rules":{
"no-template-curly-in-string":"off"
}}
62 changes: 62 additions & 0 deletions src/__tests__/lib/rules/prefer-to-have-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { RuleTester } from "eslint";
import * as rule from "../../../rules/prefer-to-have-style";

const errors = [
{ message: "Use toHaveStyle instead of asserting on element style" },
];
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
ruleTester.run("prefer-to-have-attribute", rule, {
valid: [
`expect(el).toHaveStyle({foo:"bar"})`,
`expect(el.style).toMatchSnapshot()`,
`expect(el.style).toEqual(foo)`,
`expect(el).toHaveAttribute("style")`,
],
invalid: [
{
code: `expect(el.style.foo).toBe("bar")`,
errors,
output: `expect(el).toHaveStyle({foo:"bar"})`,
},
{
code: `expect(el.style.foo).not.toBe("bar")`,
errors,
output: `expect(el).not.toHaveStyle({foo:"bar"})`,
},
{
code: `expect(el.style).toHaveProperty("background-color", "green")`,
errors,
output: `expect(el).toHaveStyle({backgroundColor: "green"})`,
},
{
code: `expect(el.style).not.toHaveProperty("background-color", "green")`,
errors,
output: `expect(el).not.toHaveStyle({backgroundColor: "green"})`,
},
{
code: `expect(screen.getByTestId("foo").style["scroll-snap-type"]).toBe("x mandatory")`,
errors,
output: `expect(screen.getByTestId("foo")).toHaveStyle({scrollSnapType: "x mandatory"})`,
},
{
code: `expect(screen.getByTestId("foo").style["scroll-snap-type"]).not.toBe("x mandatory")`,
errors,
output: `expect(screen.getByTestId("foo")).not.toHaveStyle({scrollSnapType: "x mandatory"})`,
},
{
code: `expect(el.style).toContain("background-color")`,
errors,
output: `expect(el).toHaveStyle({backgroundColor: expect.anything()})`,
},
{
code: `expect(el.style).not.toContain("background-color")`,
errors,
output: `expect(el).not.toHaveStyle({backgroundColor: expect.anything()})`,
},
{
code: `expect(el).toHaveAttribute("style", "background-color: green; border-width: 10px; color: blue;")`,
errors,
output: `expect(el).toHaveStyle("background-color: green; border-width: 10px; color: blue;")`,
},
],
});
4 changes: 2 additions & 2 deletions src/createBannedAttributeRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default ({ preferred, negatedPreferred, attributes }) => (context) => {
node
) {
const {
arguments: [{ property, property: { name } = {} }],
arguments: [{ object, property, property: { name } = {} }],
} = node.callee.object;
const matcher = node.callee.property.name;
const matcherArg = node.arguments.length && node.arguments[0].value;
Expand All @@ -58,7 +58,7 @@ export default ({ preferred, negatedPreferred, attributes }) => (context) => {
node,
message: `Use ${correctFunction}() instead of checking .${name} directly`,
fix: (fixer) => [
fixer.removeRange([property.range[0] - 1, property.range[1]]),
fixer.removeRange([object.range[1], property.range[1]]),
fixer.replaceTextRange(
[node.callee.property.range[0], node.range[1]],
`${correctFunction}()`
Expand Down
16 changes: 8 additions & 8 deletions src/rules/prefer-empty.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.left.property.range[0] - 1, node.range[1]]),
fixer.removeRange([node.left.object.range[1], node.range[1]]),
fixer.replaceText(
node.parent.parent.property,
Boolean(node.parent.parent.parent.arguments[0].value) ===
Expand All @@ -40,7 +40,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.left.property.range[0] - 1, node.range[1]]),
fixer.removeRange([node.left.object.range[1], node.range[1]]),
fixer.replaceText(
node.parent.parent.property,
Boolean(node.parent.parent.parent.arguments[0].value) ===
Expand All @@ -64,7 +64,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(node.parent.parent.property, "toBeEmptyDOMElement"),
fixer.remove(node.parent.parent.parent.arguments[0]),
],
Expand All @@ -83,7 +83,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(
node.parent.parent.parent.property,
"toBeEmptyDOMElement"
Expand All @@ -99,7 +99,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(node.parent.parent.property, "toBeEmptyDOMElement"),
],
});
Expand All @@ -115,7 +115,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(
node.parent.parent.parent.property,
"toBeEmptyDOMElement"
Expand All @@ -131,7 +131,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(
node.parent.parent.parent.property,
"toBeEmptyDOMElement"
Expand All @@ -150,7 +150,7 @@ export const create = (context) => ({
node,
message: "Use toBeEmptyDOMElement instead of checking inner html.",
fix: (fixer) => [
fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
fixer.removeRange([node.object.range[1], node.property.range[1]]),
fixer.replaceText(node.parent.parent.property, "toBeEmptyDOMElement"),
fixer.remove(node.parent.parent.parent.arguments[0]),
],
Expand Down
1 change: 1 addition & 0 deletions src/rules/prefer-focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const variantsOfDoc = [

export const meta = {
docs: {
url: "prefer-focus",
description: "prefer toHaveFocus over checking document.activeElement",
category: "jest-dom",
recommended: true,
Expand Down
4 changes: 3 additions & 1 deletion src/rules/prefer-to-have-attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

export const meta = {
docs: {
category: "jest-dom",
description:
"prefer toHaveAttribute over checking getAttribute/hasAttribute ",
url: "prefer-to-have-attribute",
recommended: true,
},
fixable: "code",
Expand Down Expand Up @@ -107,7 +109,7 @@ export const create = (context) => ({
message: "Invalid matcher for getAttribute",
});
},
[`CallExpression[callee.property.name='hasAttribute'][parent.callee.name='expect'][parent.parent.property.name=/toBe$|to(Striclty)?Equal/]`](
[`CallExpression[callee.property.name='hasAttribute'][parent.callee.name='expect'][parent.parent.property.name=/toBe$|to(Strict)?Equal/]`](
node
) {
if (typeof node.parent.parent.parent.arguments[0].value === "boolean") {
Expand Down
Loading

0 comments on commit 8a55022

Please sign in to comment.