Skip to content

Commit 1aa9238

Browse files
feat: new rule no-get-by-for-checking-element-not-present (#65)
BREAKING CHANGE: rule prefer-expect-query-by has disappeared in favor of new rule no-get-by-for-checking-element-not-present
1 parent 6420867 commit 1aa9238

8 files changed

+257
-254
lines changed

README.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,17 @@ To enable this configuration use the `extends` property in your
134134

135135
## Supported Rules
136136

137-
| Rule | Description | Configurations | Fixable |
138-
| -------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------ |
139-
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
140-
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
141-
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
142-
| [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][] | |
143-
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
144-
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
145-
| [prefer-expect-query-by](docs/rules/prefer-expect-query-by.md) | Disallow the use of `expect(getBy*)` | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
146-
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
147-
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | |
137+
| Rule | Description | Configurations | Fixable |
138+
| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------ |
139+
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
140+
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
141+
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
142+
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | |
143+
| [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][] | |
144+
| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
145+
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
146+
| [no-get-by-for-checking-element-not-present](docs/rules/no-get-by-for-checking-element-not-present) | Disallow the use of `getBy*` queries when checking elements are not present | | |
147+
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
148148

149149
[build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-testing-library?style=flat-square
150150
[build-url]: https://travis-ci.org/testing-library/eslint-plugin-testing-library
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Disallow the use of `getBy*` queries when checking elements are not present (no-get-by-for-checking-element-not-present)
2+
3+
The (DOM) Testing Library allows to query DOM elements using different types of queries such as `getBy*` and `queryBy*`. Using `getBy*` throws an error in case the element is not found. This is useful when:
4+
5+
- 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.
6+
- using `getBy` queries as an assert itself, so if the element is not found the error thrown will work as the check itself within the test.
7+
8+
However, when asserting if an element is not present or waiting for disappearance, using `getBy*` will make the test fail immediately. Instead it is recommended to use `queryBy*`, which does not throw and therefore we can:
9+
10+
- assert element does not exist: `expect(queryByText("Foo")).not.toBeInTheDocument()`
11+
- wait for disappearance: `await waitForElementToBeRemoved(() => queryByText('the mummy'))`
12+
13+
## Rule details
14+
15+
This rule fires whenever:
16+
17+
- `expect` is used to assert element does not exist with `.not.toBeInTheDocument()` or `.toBeNull()` matchers
18+
- `waitForElementToBeRemoved` async util is used to wait for element to be removed from DOM
19+
20+
Examples of **incorrect** code for this rule:
21+
22+
```js
23+
test('some test', () => {
24+
const { getByText } = render(<App />);
25+
expect(getByText('Foo')).not.toBeInTheDocument();
26+
expect(getByText('Foo')).toBeFalsy();
27+
expect(getByText('Foo')).toBeNull();
28+
});
29+
```
30+
31+
```js
32+
test('some test', async () => {
33+
const utils = render(<App />);
34+
await waitForElementToBeRemoved(() => utils.getByText('Foo'));
35+
});
36+
```
37+
38+
Examples of **correct** code for this rule:
39+
40+
```js
41+
test('some test', () => {
42+
const { getByText } = render(<App />);
43+
expect(getByText('Foo')).toBeInTheDocument();
44+
expect(queryByText('Foo')).not.toBeInTheDocument();
45+
expect(queryByText('Foo')).toBeFalsy();
46+
});
47+
```
48+
49+
```js
50+
test('some test', async () => {
51+
const utils = render(<App />);
52+
await waitForElementToBeRemoved(() => utils.queryByText('Foo'));
53+
});
54+
```
55+
56+
## Further Reading
57+
58+
- [Asserting elements are not present](https://testing-library.com/docs/guide-disappearance#asserting-elements-are-not-present)
59+
- [Waiting for disappearance](https://testing-library.com/docs/guide-disappearance#waiting-for-disappearance)
60+
- [jest-dom note about using `getBy` within assertions](https://testing-library.com/docs/ecosystem-jest-dom)
61+
- [Testing Library queries cheatsheet](https://testing-library.com/docs/dom-testing-library/cheatsheet#queries)

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

-81
This file was deleted.

lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const rules = {
88
'no-await-sync-query': require('./rules/no-await-sync-query'),
99
'no-debug': require('./rules/no-debug'),
1010
'no-dom-import': require('./rules/no-dom-import'),
11-
'prefer-expect-query-by': require('./rules/prefer-expect-query-by'),
11+
'no-get-by-for-checking-element-not-present': require('./rules/no-get-by-for-checking-element-not-present'),
1212
'prefer-explicit-assert': require('./rules/prefer-explicit-assert'),
1313
};
1414

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict';
2+
3+
const { getDocsUrl } = require('../utils');
4+
5+
const falsyMatchers = ['toBeNull', 'toBeFalsy'];
6+
7+
module.exports = {
8+
meta: {
9+
docs: {
10+
category: 'Best Practices',
11+
description:
12+
'Disallow the use of `getBy*` queries when checking elements are not present',
13+
recommended: 'error',
14+
url: getDocsUrl('no-get-by-for-checking-element-not-present'),
15+
},
16+
messages: {
17+
expectQueryBy:
18+
'Use `getBy*` only when checking elements are present, otherwise use `queryBy*`',
19+
},
20+
schema: [],
21+
type: 'suggestion',
22+
fixable: null,
23+
},
24+
25+
create: context => ({
26+
[`Identifier[name=/getBy|getAllBy/]`](node) {
27+
const expectCallNode = findClosestCallNode(node, 'expect');
28+
29+
// expect(getByText("foo"))...
30+
if (expectCallNode) {
31+
const expectStatement = expectCallNode.parent;
32+
const matcher = expectStatement.property.name;
33+
34+
if (matcher === 'not') {
35+
const negatedMatcher = expectStatement.parent.property.name;
36+
37+
if (!falsyMatchers.includes(negatedMatcher)) {
38+
return context.report({
39+
node,
40+
messageId: 'expectQueryBy',
41+
});
42+
}
43+
}
44+
45+
if (falsyMatchers.includes(matcher)) {
46+
return context.report({
47+
node,
48+
messageId: 'expectQueryBy',
49+
});
50+
}
51+
}
52+
53+
const waitCallNode = findClosestCallNode(
54+
node,
55+
'waitForElementToBeRemoved'
56+
);
57+
58+
if (waitCallNode) {
59+
return context.report({
60+
node,
61+
messageId: 'expectQueryBy',
62+
});
63+
}
64+
},
65+
}),
66+
};
67+
68+
function findClosestCallNode(node, name) {
69+
if (!node.parent) {
70+
return false;
71+
}
72+
73+
if (node.type === 'CallExpression' && node.callee.name === name) {
74+
return node;
75+
} else {
76+
return findClosestCallNode(node.parent, name);
77+
}
78+
}

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

-102
This file was deleted.

0 commit comments

Comments
 (0)