Skip to content

Commit

Permalink
feat: Custom test rules (#93)
Browse files Browse the repository at this point in the history
* feat: add new custom rule await-user-event

* feat: add new custom rule prefer-user-event

* feat: add rule await-user-event to the tests config

* chore: add RNTL as dev dep to the example app

* fix: update docs that somehow were not correctly generated before rebase

* fix: make rule break again in breaking example

* fix: config emoji for test config

* feat: update messages for eslint rule prefer-user-event
  • Loading branch information
pierrezimmermannbam authored Oct 26, 2023
1 parent 3ee3591 commit 5aee16c
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { fireEvent, userEvent } from "@testing-library/react-native";
// Save without formatting: [⌘ + K] > [S]

// This should trigger an error breaking eslint-testing-library rule:
// testing-library/no-await-sync-events
// @bam.tech/await-user-event

it("a test", () => {
it("a test", async () => {
await fireEvent();
userEvent.press(button);
});
1 change: 1 addition & 0 deletions example-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"devDependencies": {
"@bam.tech/eslint-plugin": "*",
"@bam.tech/typescript-config": "*",
"@testing-library/react-native": "^12.3.1",
"@types/jest": "^29.5.2",
"@types/react": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^5.61.0",
Expand Down
12 changes: 9 additions & 3 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,15 @@ This plugin exports some custom rules that you can optionally use in your projec

<!-- begin auto-generated rules list -->

| Name | Description |
| :------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------- |
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect |
💼 Configurations enabled in.\
🧪 Set in the `tests` configuration.\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).

| Name | Description | 💼 | 🔧 |
| :------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------- | :-- | :-- |
| [await-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/await-user-event.md) | Enforces awaiting userEvent calls | 🧪 | 🔧 |
| [prefer-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/prefer-user-event.md) | Enforces usage of userEvent over fireEvent in tests. | | 🔧 |
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |

<!-- end auto-generated rules list -->

Expand Down
23 changes: 23 additions & 0 deletions packages/eslint-plugin/docs/rules/await-user-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Enforces awaiting userEvent calls (`@bam.tech/await-user-event`)

💼 This rule is enabled in the 🧪 `tests` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

Makes sure calls to `userEvent` APIs are awaited

## Rule details

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

```jsx
userEvent.press(button);
```

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

```jsx
await userEvent.press(button);
```
29 changes: 29 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-user-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Enforces usage of userEvent over fireEvent in tests (`@bam.tech/prefer-user-event`)

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

Enforces the usage of `userEvent.type` over `fireEvent.changeText` and `userEvent.press` over `fireEvent.press`

## Rule details

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

```jsx
fireEvent.press(button);
```

```jsx
fireEvent.changeText(input, "text");
```

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

```jsx
await userEvent.press(button);
```

```jsx
await userEvent.type(input, "text");
```
1 change: 1 addition & 0 deletions packages/eslint-plugin/lib/configs/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ module.exports = defineConfig({
"testing-library/prefer-presence-queries": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/prefer-screen-queries": "error",
"@bam.tech/await-user-event": "error",
},
});
49 changes: 49 additions & 0 deletions packages/eslint-plugin/lib/rules/await-user-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @fileoverview Makes sure userEvent.press and userEvent.type are awaited
* @author Pierre Zimmermann
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "Enforces awaiting userEvent calls",
category: "Possible Errors",
recommended: true,
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/await-user-event.md",
},
messages: {
missingAwait: "userEvent calls should be preceded by 'await'.",
},
schema: [],
fixable: "code",
},

create(context) {
return {
CallExpression(node) {
if (
node.callee.type === "MemberExpression" &&
node.callee.object.name === "userEvent"
) {
// Check if the parent is not an AwaitExpression
if (node.parent.type !== "AwaitExpression") {
context.report({
node,
messageId: "missingAwait",
fix(fixer) {
return fixer.insertTextBefore(node, "await ");
},
});
}
}
},
};
},
};
60 changes: 60 additions & 0 deletions packages/eslint-plugin/lib/rules/prefer-user-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @fileoverview Forces usage of userEvent.press over fireEvent.press and userEvent.type over fireEvent.changeText
* @author Pierre Zimmermann
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "Enforces usage of userEvent over fireEvent in tests.",
category: "Possible Errors",
recommended: true,
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/prefer-user-event.md",
},
messages: {
replacePress: "Replace `fireEvent.press` with `await userEvent.press.`",
replaceChangeText: "Replace `fireEvent.changeText` with `await userEvent.type.`",
},
fixable: "code",
schema: [],
},

create(context) {
return {
MemberExpression: (node) => {
if (node.object.name === "fireEvent") {
if (node.property.name === "press") {
context.report({
node: node.property,
messageId: "replacePress",
fix(fixer) {
return [
fixer.replaceText(node.object, "await userEvent"),
fixer.replaceText(node.property, "press"),
];
},
});
} else if (node.property.name === "changeText") {
context.report({
node: node.property,
messageId: "replaceChangeText",
fix(fixer) {
return [
fixer.replaceText(node.object, "await userEvent"),
fixer.replaceText(node.property, "type"),
];
},
});
}
}
},
};
},
};
2 changes: 1 addition & 1 deletion packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"lint:js": "eslint .",
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
"test": "mocha tests --recursive",
"update:eslint-docs": "eslint-doc-generator"
"update:eslint-docs": "eslint-doc-generator --config-emoji tests,🧪"
},
"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^5.61.0",
Expand Down
55 changes: 55 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,13 @@
dependencies:
"@sinclair/typebox" "^0.27.8"

"@jest/schemas@^29.6.3":
version "29.6.3"
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
dependencies:
"@sinclair/typebox" "^0.27.8"

"@jest/source-map@^29.6.0":
version "29.6.0"
resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz"
Expand Down Expand Up @@ -2672,6 +2679,15 @@
dependencies:
"@sinonjs/commons" "^3.0.0"

"@testing-library/react-native@^12.3.1":
version "12.3.1"
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-12.3.1.tgz#7ac584711f214c7a1702fa4f637a9b3b22f0d093"
integrity sha512-nSd+trdQv8gbTSiAbjROVW9p7VZ6xhoy3qKy0q6vdnbzJCQlkKN2SzhZe92/evgu/aJZj575dajxuE37EcHA/Q==
dependencies:
jest-matcher-utils "^29.7.0"
pretty-format "^29.7.0"
redent "^3.0.0"

"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz"
Expand Down Expand Up @@ -4620,6 +4636,11 @@ diff-sequences@^29.4.3:
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz"
integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==

diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==

[email protected]:
version "5.0.0"
resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz"
Expand Down Expand Up @@ -7024,6 +7045,16 @@ jest-diff@^29.2.1, jest-diff@^29.6.0:
jest-get-type "^29.4.3"
pretty-format "^29.6.0"

jest-diff@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==
dependencies:
chalk "^4.0.0"
diff-sequences "^29.6.3"
jest-get-type "^29.6.3"
pretty-format "^29.7.0"

jest-docblock@^29.4.3:
version "29.4.3"
resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz"
Expand Down Expand Up @@ -7059,6 +7090,11 @@ jest-get-type@^29.4.3:
resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz"
integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==

jest-get-type@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==

jest-haste-map@^29.6.0:
version "29.6.0"
resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.0.tgz"
Expand Down Expand Up @@ -7096,6 +7132,16 @@ jest-matcher-utils@^29.6.0:
jest-get-type "^29.4.3"
pretty-format "^29.6.0"

jest-matcher-utils@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12"
integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
dependencies:
chalk "^4.0.0"
jest-diff "^29.7.0"
jest-get-type "^29.6.3"
pretty-format "^29.7.0"

jest-message-util@^29.6.0:
version "29.6.0"
resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.0.tgz"
Expand Down Expand Up @@ -9668,6 +9714,15 @@ pretty-format@^29.0.0, pretty-format@^29.6.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"

pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
dependencies:
"@jest/schemas" "^29.6.3"
ansi-styles "^5.0.0"
react-is "^18.0.0"

proc-log@^2.0.0, proc-log@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz"
Expand Down

0 comments on commit 5aee16c

Please sign in to comment.