Skip to content

Commit abe42cd

Browse files
authored
feat: add no-test-id-queries rule (#1006)
Closes #279
1 parent bde567f commit abe42cd

File tree

5 files changed

+167
-1
lines changed

5 files changed

+167
-1
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

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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)
32+
- [Common mistakes with React Testing Library - Not querying by text](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-querying-by-text)

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

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { TSESTree } from '@typescript-eslint/utils';
2+
3+
import { createTestingLibraryRule } from '../create-testing-library-rule';
4+
import { ALL_QUERIES_VARIANTS } from '../utils';
5+
6+
export const RULE_NAME = 'no-test-id-queries';
7+
export type MessageIds = 'noTestIdQueries';
8+
type Options = [];
9+
10+
const QUERIES_REGEX = `/^(${ALL_QUERIES_VARIANTS.join('|')})TestId$/`;
11+
12+
export default createTestingLibraryRule<Options, MessageIds>({
13+
name: RULE_NAME,
14+
meta: {
15+
type: 'problem',
16+
docs: {
17+
description: 'Ensure no `data-testid` queries are used',
18+
recommendedConfig: {
19+
dom: false,
20+
angular: false,
21+
react: false,
22+
vue: false,
23+
svelte: false,
24+
marko: false,
25+
},
26+
},
27+
messages: {
28+
noTestIdQueries:
29+
'Using `data-testid` queries is not recommended. Use a more descriptive query instead.',
30+
},
31+
schema: [],
32+
},
33+
defaultOptions: [],
34+
35+
create(context) {
36+
return {
37+
[`CallExpression[callee.property.name=${QUERIES_REGEX}], CallExpression[callee.name=${QUERIES_REGEX}]`](
38+
node: TSESTree.CallExpression
39+
) {
40+
context.report({
41+
node,
42+
messageId: 'noTestIdQueries',
43+
});
44+
},
45+
};
46+
},
47+
});

tests/index.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resolve } from 'path';
33

44
import plugin from '../lib';
55

6-
const numberOfRules = 27;
6+
const numberOfRules = 28;
77
const ruleNames = Object.keys(plugin.rules);
88

99
// eslint-disable-next-line jest/expect-expect
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
import { render } from '@testing-library/react';
27+
28+
test('test', async () => {
29+
const { getByRole } = render(<MyComponent />);
30+
31+
expect(getByRole('button')).toBeInTheDocument();
32+
});
33+
`,
34+
35+
`
36+
import { render } from '@testing-library/react';
37+
38+
test('test', async () => {
39+
render(<MyComponent />);
40+
41+
expect(getTestId('button')).toBeInTheDocument();
42+
});
43+
`,
44+
],
45+
46+
invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((framework) =>
47+
QUERIES.flatMap((query) => [
48+
{
49+
code: `
50+
import { render } from '${framework}';
51+
52+
test('test', async () => {
53+
const { ${query} } = render(<MyComponent />);
54+
55+
expect(${query}('my-test-id')).toBeInTheDocument();
56+
});
57+
`,
58+
errors: [
59+
{
60+
messageId: 'noTestIdQueries',
61+
line: 7,
62+
column: 14,
63+
},
64+
],
65+
},
66+
{
67+
code: `
68+
import { render, screen } from '${framework}';
69+
70+
test('test', async () => {
71+
render(<MyComponent />);
72+
73+
expect(screen.${query}('my-test-id')).toBeInTheDocument();
74+
});
75+
`,
76+
errors: [
77+
{
78+
messageId: 'noTestIdQueries',
79+
line: 7,
80+
column: 14,
81+
},
82+
],
83+
},
84+
])
85+
),
86+
});

0 commit comments

Comments
 (0)