Skip to content

Commit 62ed216

Browse files
committed
feat(rule): prefer expect queryBy
1 parent b0c7ace commit 62ed216

File tree

7 files changed

+195
-38
lines changed

7 files changed

+195
-38
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,14 @@ To enable this configuration use the `extends` property in your
128128

129129
## Supported Rules
130130

131-
| Rule | Description | Configurations | Fixable |
132-
| -------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- | ------------------ |
133-
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
134-
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
135-
| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
136-
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
137-
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
131+
| Rule | Description | Configurations | Fixable |
132+
| -------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- | ------------------ |
133+
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
134+
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
135+
| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
136+
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
137+
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
138+
| [prefer-expect-query-by](docs/rules/prefer-expect-query-by.md) | Disallow the use of `expect(getBy*)` | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
138139

139140
[build-badge]: https://img.shields.io/travis/Belco90/eslint-plugin-testing-library?style=flat-square
140141
[build-url]: https://travis-ci.org/belco90/eslint-plugin-testing-library

docs/rules/prefer-expect-query-by.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Disallow the use of `expect(getBy*)` (prefer-expect-query-by)
2+
3+
The (DOM) Testing Library support two types of queries: `getBy*` and `queryBy*`. Using `getBy*` throws an error in case the element is not found. This is useful when using method like `waitForElement`, which are `async` functions that will wait for the element to be found until a certain timeout, after that the test will fail.
4+
However, when trying to assert if an element is not in the document, we can't use `getBy*` as the test will fail immediately. Instead it is recommended to use `queryBy*`, which does not throw and therefore we can assert that `expect(queryByText("Foo")).not.toBeInTheDocument()`.
5+
6+
## Rule details
7+
8+
This rule gives a notification whenever `expect(getBy*)` is used.
9+
10+
This rule is enabled by default.
11+
12+
### Default configuration
13+
14+
The following patterns is considered erroneous:
15+
16+
```js
17+
test('some test', () => {
18+
expect(getByText('Foo')).not.toBeInTheDocument();
19+
});
20+
```
21+
22+
The following pattern is considered non erroneous:
23+
24+
```js
25+
test('some test', async () => {
26+
expect(queryByText('Foo')).not.toBeInTheDocument();
27+
});
28+
```
29+
30+
## Further Reading
31+
32+
- [Appearance and Disappearance](https://testing-library.com/docs/guide-disappearance#asserting-elements-are-not-present)
33+
- [Testing Library queries cheatsheet](https://testing-library.com/docs/dom-testing-library/cheatsheet#queries)

lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ const rules = {
66
'no-await-sync-query': require('./rules/no-await-sync-query'),
77
'no-debug': require('./rules/no-debug'),
88
'no-dom-import': require('./rules/no-dom-import'),
9+
'prefer-expect-query-by': require('./rules/prefer-expect-query-by'),
910
};
1011

1112
const recommendedRules = {
1213
'testing-library/await-async-query': 'error',
1314
'testing-library/no-await-sync-query': 'error',
15+
'testing-library/prefer-expect-query-by': 'error',
1416
};
1517

1618
module.exports = {

lib/rules/prefer-expect-query-by.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
3+
const { getDocsUrl } = require('../utils');
4+
5+
const AST_NODE_TYPES = {
6+
Identifier: 'Identifier',
7+
MemberExpression: 'MemberExpression',
8+
};
9+
10+
function isIdentifier(node) {
11+
return node.type === AST_NODE_TYPES.Identifier;
12+
}
13+
14+
function isMemberExpression(node) {
15+
return node.type === AST_NODE_TYPES.MemberExpression;
16+
}
17+
18+
function isUsingWrongQueries(node) {
19+
return node.name.startsWith('getBy') || node.name.startsWith('getAllBy');
20+
}
21+
22+
function isNotNullOrUndefined(input) {
23+
return input != null;
24+
}
25+
26+
function mapNodesForWrongGetByQuery(node) {
27+
const nodeArguments = node.arguments;
28+
return nodeArguments
29+
.map(arg => {
30+
if (!arg.callee) {
31+
return null;
32+
}
33+
// Example: `expect(rendered.getBy*)`
34+
if (isMemberExpression(arg.callee)) {
35+
const node = arg.callee.property;
36+
if (isIdentifier(node) && isUsingWrongQueries(node)) {
37+
return node;
38+
}
39+
return null;
40+
}
41+
42+
// Example: `expect(getBy*)`
43+
if (isIdentifier(arg.callee) && isUsingWrongQueries(arg.callee)) {
44+
return arg.callee;
45+
}
46+
47+
return null;
48+
})
49+
.filter(isNotNullOrUndefined);
50+
}
51+
52+
function hasExpectWithWrongGetByQuery(node) {
53+
if (
54+
node.callee &&
55+
node.callee.type === AST_NODE_TYPES.Identifier &&
56+
node.callee.name === 'expect' &&
57+
node.arguments
58+
) {
59+
const nodesGetBy = mapNodesForWrongGetByQuery(node);
60+
return nodesGetBy.length > 0;
61+
}
62+
return false;
63+
}
64+
65+
module.exports = {
66+
meta: {
67+
docs: {
68+
category: 'Best Practices',
69+
description: 'Disallow using getBy* queries in expect calls',
70+
recommended: 'error',
71+
url: getDocsUrl('prefer-expect-query-by'),
72+
},
73+
messages: {
74+
expectQueryBy:
75+
'Using `expect(getBy*)` is not recommended, use `expect(queryBy*)` instead.',
76+
},
77+
schema: [],
78+
type: 'suggestion',
79+
fixable: 'code',
80+
},
81+
82+
create: context => ({
83+
CallExpression(node) {
84+
if (hasExpectWithWrongGetByQuery(node)) {
85+
const nodesGetBy = mapNodesForWrongGetByQuery(node);
86+
context.report({
87+
node: node.callee,
88+
messageId: 'expectQueryBy',
89+
fix(fixer) {
90+
return fixer.replaceText(
91+
nodesGetBy[0],
92+
nodesGetBy[0].name.replace(/^(get(All)?(.*))$/, 'query$2$3')
93+
);
94+
},
95+
});
96+
}
97+
},
98+
}),
99+
};

package-lock.json

Lines changed: 12 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/__snapshots__/index.test.js.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Object {
1313
"error",
1414
"angular",
1515
],
16+
"testing-library/prefer-expect-query-by": "error",
1617
},
1718
}
1819
`;
@@ -30,6 +31,7 @@ Object {
3031
"error",
3132
"react",
3233
],
34+
"testing-library/prefer-expect-query-by": "error",
3335
},
3436
}
3537
`;
@@ -42,6 +44,7 @@ Object {
4244
"rules": Object {
4345
"testing-library/await-async-query": "error",
4446
"testing-library/no-await-sync-query": "error",
47+
"testing-library/prefer-expect-query-by": "error",
4548
},
4649
}
4750
`;
@@ -60,6 +63,7 @@ Object {
6063
"error",
6164
"vue",
6265
],
66+
"testing-library/prefer-expect-query-by": "error",
6367
},
6468
}
6569
`;

0 commit comments

Comments
 (0)