Skip to content

Commit fb59333

Browse files
committed
feat: add no-test-id-queries rule
Closes #279
1 parent bde567f commit fb59333

File tree

10 files changed

+162
-0
lines changed

10 files changed

+162
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ module.exports = [
338338
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
339339
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
340340
| [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
341+
| [no-test-id-queries](docs/rules/no-test-id-queries.md) | Ensure no `data-testid` queries are used | | | |
341342
| [no-unnecessary-act](docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | ![badge-marko][] ![badge-react][] | | |
342343
| [no-wait-for-multiple-assertions](docs/rules/no-wait-for-multiple-assertions.md) | Disallow the use of multiple `expect` calls inside `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
343344
| [no-wait-for-side-effects](docs/rules/no-wait-for-side-effects.md) | Disallow the use of side effects in `waitFor` | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |

docs/rules/no-test-id-queries.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Ensure no `data-testid` queries are used (`testing-library/no-test-id-queries`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
## Rule Details
6+
7+
This rule aims to reduce the usage of `*ByTestId` queries in your tests.
8+
9+
When using `*ByTestId` queries, you are coupling your tests to the implementation details of your components, and not to how they behave and being used.
10+
11+
Prefer using queries that are more related to the user experience, like `getByRole`, `getByLabelText`, etc.
12+
13+
Example of **incorrect** code for this rule:
14+
15+
```js
16+
const button = queryByTestId('my-button');
17+
const input = screen.queryByTestId('my-input');
18+
```
19+
20+
Examples of **correct** code for this rule:
21+
22+
```js
23+
const button = screen.getByRole('button');
24+
const input = screen.getByRole('textbox');
25+
```
26+
27+
## Further Reading
28+
29+
- [about `getByTestId`](https://testing-library.com/docs/queries/bytestid)
30+
- [about `getByRole`](https://testing-library.com/docs/queries/byrole)
31+
- [about `getByLabelText`](https://testing-library.com/docs/queries/bylabeltext)

lib/configs/angular.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export = {
2323
'testing-library/no-node-access': 'error',
2424
'testing-library/no-promise-in-fire-event': 'error',
2525
'testing-library/no-render-in-lifecycle': 'error',
26+
'testing-library/no-test-id-queries': 'error',
2627
'testing-library/no-wait-for-multiple-assertions': 'error',
2728
'testing-library/no-wait-for-side-effects': 'error',
2829
'testing-library/no-wait-for-snapshot': 'error',

lib/configs/dom.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export = {
1919
'testing-library/no-global-regexp-flag-in-query': 'error',
2020
'testing-library/no-node-access': 'error',
2121
'testing-library/no-promise-in-fire-event': 'error',
22+
'testing-library/no-test-id-queries': 'error',
2223
'testing-library/no-wait-for-multiple-assertions': 'error',
2324
'testing-library/no-wait-for-side-effects': 'error',
2425
'testing-library/no-wait-for-snapshot': 'error',

lib/configs/marko.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export = {
1919
'testing-library/no-node-access': 'error',
2020
'testing-library/no-promise-in-fire-event': 'error',
2121
'testing-library/no-render-in-lifecycle': 'error',
22+
'testing-library/no-test-id-queries': 'error',
2223
'testing-library/no-unnecessary-act': 'error',
2324
'testing-library/no-wait-for-multiple-assertions': 'error',
2425
'testing-library/no-wait-for-side-effects': 'error',

lib/configs/react.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export = {
2424
'testing-library/no-node-access': 'error',
2525
'testing-library/no-promise-in-fire-event': 'error',
2626
'testing-library/no-render-in-lifecycle': 'error',
27+
'testing-library/no-test-id-queries': 'error',
2728
'testing-library/no-unnecessary-act': 'error',
2829
'testing-library/no-wait-for-multiple-assertions': 'error',
2930
'testing-library/no-wait-for-side-effects': 'error',

lib/configs/svelte.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export = {
2020
'testing-library/no-node-access': 'error',
2121
'testing-library/no-promise-in-fire-event': 'error',
2222
'testing-library/no-render-in-lifecycle': 'error',
23+
'testing-library/no-test-id-queries': 'error',
2324
'testing-library/no-wait-for-multiple-assertions': 'error',
2425
'testing-library/no-wait-for-side-effects': 'error',
2526
'testing-library/no-wait-for-snapshot': 'error',

lib/configs/vue.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export = {
2020
'testing-library/no-node-access': 'error',
2121
'testing-library/no-promise-in-fire-event': 'error',
2222
'testing-library/no-render-in-lifecycle': 'error',
23+
'testing-library/no-test-id-queries': 'error',
2324
'testing-library/no-wait-for-multiple-assertions': 'error',
2425
'testing-library/no-wait-for-side-effects': 'error',
2526
'testing-library/no-wait-for-snapshot': 'error',

lib/rules/no-test-id-queries.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { TSESTree } from '@typescript-eslint/utils';
2+
3+
import { createTestingLibraryRule } from '../create-testing-library-rule';
4+
5+
export const RULE_NAME = 'no-test-id-queries';
6+
export type MessageIds = 'noTestIdQueries';
7+
type Options = [];
8+
9+
const QUERIES_REGEX = /^(get|query|getAll|queryAll|find|findAll)ByTestId$/;
10+
11+
export default createTestingLibraryRule<Options, MessageIds>({
12+
name: RULE_NAME,
13+
meta: {
14+
type: 'problem',
15+
docs: {
16+
description: 'Ensure no `data-testid` queries are used',
17+
recommendedConfig: {
18+
dom: 'error',
19+
angular: 'error',
20+
react: 'error',
21+
vue: 'error',
22+
svelte: 'error',
23+
marko: 'error',
24+
},
25+
},
26+
messages: {
27+
noTestIdQueries:
28+
'Using `data-testid` queries is not recommended. Use a more descriptive query instead.',
29+
},
30+
schema: [],
31+
},
32+
defaultOptions: [],
33+
34+
create(context) {
35+
return {
36+
[`CallExpression[callee.property.name=${String(QUERIES_REGEX)}], CallExpression[callee.name=${String(QUERIES_REGEX)}]`](
37+
node: TSESTree.CallExpression
38+
) {
39+
context.report({
40+
node,
41+
messageId: 'noTestIdQueries',
42+
});
43+
},
44+
};
45+
},
46+
});
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import rule, { RULE_NAME } from '../../../lib/rules/no-test-id-queries';
2+
import { createRuleTester } from '../test-utils';
3+
4+
const ruleTester = createRuleTester();
5+
6+
const SUPPORTED_TESTING_FRAMEWORKS = [
7+
'@testing-library/dom',
8+
'@testing-library/angular',
9+
'@testing-library/react',
10+
'@testing-library/vue',
11+
'@marko/testing-library',
12+
];
13+
14+
const QUERIES = [
15+
'getByTestId',
16+
'queryByTestId',
17+
'getAllByTestId',
18+
'queryAllByTestId',
19+
'findByTestId',
20+
'findAllByTestId',
21+
];
22+
23+
ruleTester.run(RULE_NAME, rule, {
24+
valid: [
25+
{
26+
code: `
27+
import { render } from '@testing-library/react';
28+
29+
test('test', async () => {
30+
const { getByRole } = render(<MyComponent />);
31+
32+
expect(getByRole('button')).toBeInTheDocument();
33+
});
34+
`,
35+
},
36+
],
37+
38+
invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((framework) =>
39+
QUERIES.flatMap((query) => [
40+
{
41+
code: `
42+
import { render } from '${framework}';
43+
44+
test('test', async () => {
45+
const { ${query} } = render(<MyComponent />);
46+
47+
expect(${query}('my-test-id')).toBeInTheDocument();
48+
});
49+
`,
50+
errors: [
51+
{
52+
messageId: 'noTestIdQueries',
53+
line: 7,
54+
column: 14,
55+
},
56+
],
57+
},
58+
{
59+
code: `
60+
import { render, screen } from '${framework}';
61+
62+
test('test', async () => {
63+
render(<MyComponent />);
64+
65+
expect(screen.${query}('my-test-id')).toBeInTheDocument();
66+
});
67+
`,
68+
errors: [
69+
{
70+
messageId: 'noTestIdQueries',
71+
line: 7,
72+
column: 14,
73+
},
74+
],
75+
},
76+
])
77+
),
78+
});

0 commit comments

Comments
 (0)