Skip to content

Commit 4ab50a0

Browse files
authored
feat(prefer-presence-queries): add autofix support (#1020)
Closes #916
1 parent 5eed1dd commit 4ab50a0

File tree

4 files changed

+113
-5
lines changed

4 files changed

+113
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ module.exports = [
346346
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than standalone queries | | | |
347347
| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
348348
| [prefer-implicit-assert](docs/rules/prefer-implicit-assert.md) | Suggest using implicit assertions for getBy* & findBy* queries | | | |
349-
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
349+
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Ensure appropriate `get*`/`query*` queries are used with their respective matchers | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
350350
| [prefer-query-by-disappearance](docs/rules/prefer-query-by-disappearance.md) | Suggest using `queryBy*` queries when waiting for disappearance | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
351351
| [prefer-query-matchers](docs/rules/prefer-query-matchers.md) | Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | |
352352
| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using `screen` while querying | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |

docs/rules/prefer-presence-queries.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `svelte`, `vue`.
44

5+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
57
<!-- end auto-generated rule header -->
68

79
The (DOM) Testing Library allows to query DOM elements using different types of queries such as `get*` and `query*`. Using `get*` throws an error in case the element is not found, while `query*` returns null instead of throwing (or empty array for `queryAllBy*` ones). These differences are useful in some situations:

lib/rules/prefer-presence-queries.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
3333
wrongAbsenceQuery:
3434
'Use `queryBy*` queries rather than `getBy*` for checking element is NOT present',
3535
},
36+
fixable: 'code',
3637
schema: [
3738
{
3839
type: 'object',
@@ -62,7 +63,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
6263
const expectCallNode = findClosestCallNode(node, 'expect');
6364
const withinCallNode = findClosestCallNode(node, 'within');
6465

65-
if (!expectCallNode || !isMemberExpression(expectCallNode.parent)) {
66+
if (!isMemberExpression(expectCallNode?.parent)) {
6667
return;
6768
}
6869

@@ -86,14 +87,25 @@ export default createTestingLibraryRule<Options, MessageIds>({
8687
(withinCallNode || isPresenceAssert) &&
8788
!isPresenceQuery
8889
) {
89-
context.report({ node, messageId: 'wrongPresenceQuery' });
90+
const newQueryName = node.name.replace(/^query/, 'get');
91+
92+
context.report({
93+
node,
94+
messageId: 'wrongPresenceQuery',
95+
fix: (fixer) => fixer.replaceText(node, newQueryName),
96+
});
9097
} else if (
9198
!withinCallNode &&
9299
absence &&
93100
isAbsenceAssert &&
94101
isPresenceQuery
95102
) {
96-
context.report({ node, messageId: 'wrongAbsenceQuery' });
103+
const newQueryName = node.name.replace(/^get/, 'query');
104+
context.report({
105+
node,
106+
messageId: 'wrongAbsenceQuery',
107+
fix: (fixer) => fixer.replaceText(node, newQueryName),
108+
});
97109
}
98110
},
99111
};

tests/lib/rules/prefer-presence-queries.test.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,34 @@ const getDisabledValidAssertion = ({
8282
};
8383
};
8484

85+
const toggleQueryPrefix = (query: string): string => {
86+
if (query.startsWith('get')) return query.replace(/^get/, 'query');
87+
if (query.startsWith('query')) return query.replace(/^query/, 'get');
88+
return query;
89+
};
90+
91+
const applyScreenPrefix = (query: string, shouldUseScreen: boolean): string =>
92+
shouldUseScreen ? `screen.${query}` : query;
93+
8594
const getInvalidAssertions = ({
8695
query,
8796
matcher,
8897
messageId,
8998
shouldUseScreen = false,
9099
assertionType,
91100
}: AssertionFnParams): RuleInvalidTestCase[] => {
92-
const finalQuery = shouldUseScreen ? `screen.${query}` : query;
101+
const finalQuery = applyScreenPrefix(query, shouldUseScreen);
93102
const code = `expect(${finalQuery}('Hello'))${matcher}`;
103+
104+
const outputQuery = toggleQueryPrefix(query);
105+
const finalOutputQuery = applyScreenPrefix(outputQuery, shouldUseScreen);
106+
const output = `expect(${finalOutputQuery}('Hello'))${matcher}`;
107+
94108
return [
95109
{
96110
code,
97111
errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }],
112+
output,
98113
},
99114
{
100115
code,
@@ -105,6 +120,7 @@ const getInvalidAssertions = ({
105120
},
106121
],
107122
errors: [{ messageId, line: 1, column: shouldUseScreen ? 15 : 8 }],
123+
output,
108124
},
109125
];
110126
};
@@ -1307,46 +1323,68 @@ ruleTester.run(RULE_NAME, rule, {
13071323
{
13081324
code: 'expect(screen.getAllByText("button")[1]).not.toBeInTheDocument()',
13091325
errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }],
1326+
output:
1327+
'expect(screen.queryAllByText("button")[1]).not.toBeInTheDocument()',
13101328
},
13111329
{
13121330
code: 'expect(screen.getAllByText("button")[1]).not.toBeOnTheScreen()',
13131331
errors: [{ messageId: 'wrongAbsenceQuery', line: 1, column: 15 }],
1332+
output:
1333+
'expect(screen.queryAllByText("button")[1]).not.toBeOnTheScreen()',
13141334
},
13151335
{
13161336
code: 'expect(screen.queryAllByText("button")[1]).toBeInTheDocument()',
13171337
errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }],
1338+
output: 'expect(screen.getAllByText("button")[1]).toBeInTheDocument()',
13181339
},
13191340
{
13201341
code: 'expect(screen.queryAllByText("button")[1]).toBeOnTheScreen()',
13211342
errors: [{ messageId: 'wrongPresenceQuery', line: 1, column: 15 }],
1343+
output: 'expect(screen.getAllByText("button")[1]).toBeOnTheScreen()',
13221344
},
13231345
{
13241346
code: `
13251347
// case: asserting presence incorrectly with custom queryBy* query
13261348
expect(queryByCustomQuery("button")).toBeInTheDocument()
13271349
`,
13281350
errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }],
1351+
output: `
1352+
// case: asserting presence incorrectly with custom queryBy* query
1353+
expect(getByCustomQuery("button")).toBeInTheDocument()
1354+
`,
13291355
},
13301356
{
13311357
code: `
13321358
// case: asserting presence incorrectly with custom queryBy* query
13331359
expect(queryByCustomQuery("button")).toBeOnTheScreen()
13341360
`,
13351361
errors: [{ messageId: 'wrongPresenceQuery', line: 3, column: 16 }],
1362+
output: `
1363+
// case: asserting presence incorrectly with custom queryBy* query
1364+
expect(getByCustomQuery("button")).toBeOnTheScreen()
1365+
`,
13361366
},
13371367
{
13381368
code: `
13391369
// case: asserting absence incorrectly with custom getBy* query
13401370
expect(getByCustomQuery("button")).not.toBeInTheDocument()
13411371
`,
13421372
errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }],
1373+
output: `
1374+
// case: asserting absence incorrectly with custom getBy* query
1375+
expect(queryByCustomQuery("button")).not.toBeInTheDocument()
1376+
`,
13431377
},
13441378
{
13451379
code: `
13461380
// case: asserting absence incorrectly with custom getBy* query
13471381
expect(getByCustomQuery("button")).not.toBeOnTheScreen()
13481382
`,
13491383
errors: [{ messageId: 'wrongAbsenceQuery', line: 3, column: 16 }],
1384+
output: `
1385+
// case: asserting absence incorrectly with custom getBy* query
1386+
expect(queryByCustomQuery("button")).not.toBeOnTheScreen()
1387+
`,
13501388
},
13511389
{
13521390
settings: {
@@ -1358,6 +1396,11 @@ ruleTester.run(RULE_NAME, rule, {
13581396
expect(queryByRole("button")).toBeInTheDocument()
13591397
`,
13601398
errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }],
1399+
output: `
1400+
// case: asserting presence incorrectly importing custom module
1401+
import 'test-utils'
1402+
expect(getByRole("button")).toBeInTheDocument()
1403+
`,
13611404
},
13621405
{
13631406
settings: {
@@ -1369,6 +1412,11 @@ ruleTester.run(RULE_NAME, rule, {
13691412
expect(queryByRole("button")).toBeOnTheScreen()
13701413
`,
13711414
errors: [{ line: 4, column: 14, messageId: 'wrongPresenceQuery' }],
1415+
output: `
1416+
// case: asserting presence incorrectly importing custom module
1417+
import 'test-utils'
1418+
expect(getByRole("button")).toBeOnTheScreen()
1419+
`,
13721420
},
13731421
{
13741422
settings: {
@@ -1380,6 +1428,11 @@ ruleTester.run(RULE_NAME, rule, {
13801428
expect(getByRole("button")).not.toBeInTheDocument()
13811429
`,
13821430
errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }],
1431+
output: `
1432+
// case: asserting absence incorrectly importing custom module
1433+
import 'test-utils'
1434+
expect(queryByRole("button")).not.toBeInTheDocument()
1435+
`,
13831436
},
13841437
{
13851438
settings: {
@@ -1391,18 +1444,29 @@ ruleTester.run(RULE_NAME, rule, {
13911444
expect(getByRole("button")).not.toBeOnTheScreen()
13921445
`,
13931446
errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }],
1447+
output: `
1448+
// case: asserting absence incorrectly importing custom module
1449+
import 'test-utils'
1450+
expect(queryByRole("button")).not.toBeOnTheScreen()
1451+
`,
13941452
},
13951453
{
13961454
code: `
13971455
// case: asserting within check does still work with improper outer clause
13981456
expect(within(screen.getByRole("button")).getByText("Hello")).not.toBeInTheDocument()`,
13991457
errors: [{ line: 3, column: 46, messageId: 'wrongAbsenceQuery' }],
1458+
output: `
1459+
// case: asserting within check does still work with improper outer clause
1460+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`,
14001461
},
14011462
{
14021463
code: `
14031464
// case: asserting within check does still work with improper outer clause
14041465
expect(within(screen.getByRole("button")).queryByText("Hello")).toBeInTheDocument()`,
14051466
errors: [{ line: 3, column: 46, messageId: 'wrongPresenceQuery' }],
1467+
output: `
1468+
// case: asserting within check does still work with improper outer clause
1469+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`,
14061470
},
14071471
{
14081472
code: `
@@ -1412,18 +1476,27 @@ ruleTester.run(RULE_NAME, rule, {
14121476
{ line: 3, column: 25, messageId: 'wrongPresenceQuery' },
14131477
{ line: 3, column: 48, messageId: 'wrongAbsenceQuery' },
14141478
],
1479+
output: `
1480+
// case: asserting within check does still work with improper outer clause and improper inner clause
1481+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`,
14151482
},
14161483
{
14171484
code: `
14181485
// case: asserting within check does still work with proper outer clause and improper inner clause
14191486
expect(within(screen.queryByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`,
14201487
errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }],
1488+
output: `
1489+
// case: asserting within check does still work with proper outer clause and improper inner clause
1490+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`,
14211491
},
14221492
{
14231493
code: `
14241494
// case: asserting within check does still work with proper outer clause and improper inner clause
14251495
expect(within(screen.queryByRole("button")).getByText("Hello")).toBeInTheDocument()`,
14261496
errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }],
1497+
output: `
1498+
// case: asserting within check does still work with proper outer clause and improper inner clause
1499+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`,
14271500
},
14281501
{
14291502
code: `
@@ -1433,18 +1506,27 @@ ruleTester.run(RULE_NAME, rule, {
14331506
{ line: 3, column: 25, messageId: 'wrongPresenceQuery' },
14341507
{ line: 3, column: 48, messageId: 'wrongPresenceQuery' },
14351508
],
1509+
output: `
1510+
// case: asserting within check does still work with improper outer clause and improper inner clause
1511+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument()`,
14361512
},
14371513
{
14381514
code: `
14391515
// case: asserting within check does still work with improper outer clause
14401516
expect(within(screen.getByRole("button")).getByText("Hello")).not.toBeOnTheScreen()`,
14411517
errors: [{ line: 3, column: 46, messageId: 'wrongAbsenceQuery' }],
1518+
output: `
1519+
// case: asserting within check does still work with improper outer clause
1520+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`,
14421521
},
14431522
{
14441523
code: `
14451524
// case: asserting within check does still work with improper outer clause
14461525
expect(within(screen.getByRole("button")).queryByText("Hello")).toBeOnTheScreen()`,
14471526
errors: [{ line: 3, column: 46, messageId: 'wrongPresenceQuery' }],
1527+
output: `
1528+
// case: asserting within check does still work with improper outer clause
1529+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`,
14481530
},
14491531
{
14501532
code: `
@@ -1454,18 +1536,27 @@ ruleTester.run(RULE_NAME, rule, {
14541536
{ line: 3, column: 25, messageId: 'wrongPresenceQuery' },
14551537
{ line: 3, column: 48, messageId: 'wrongAbsenceQuery' },
14561538
],
1539+
output: `
1540+
// case: asserting within check does still work with improper outer clause and improper inner clause
1541+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`,
14571542
},
14581543
{
14591544
code: `
14601545
// case: asserting within check does still work with proper outer clause and improper inner clause
14611546
expect(within(screen.queryByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`,
14621547
errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }],
1548+
output: `
1549+
// case: asserting within check does still work with proper outer clause and improper inner clause
1550+
expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeOnTheScreen()`,
14631551
},
14641552
{
14651553
code: `
14661554
// case: asserting within check does still work with proper outer clause and improper inner clause
14671555
expect(within(screen.queryByRole("button")).getByText("Hello")).toBeOnTheScreen()`,
14681556
errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }],
1557+
output: `
1558+
// case: asserting within check does still work with proper outer clause and improper inner clause
1559+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`,
14691560
},
14701561
{
14711562
code: `
@@ -1475,6 +1566,9 @@ ruleTester.run(RULE_NAME, rule, {
14751566
{ line: 3, column: 25, messageId: 'wrongPresenceQuery' },
14761567
{ line: 3, column: 48, messageId: 'wrongPresenceQuery' },
14771568
],
1569+
output: `
1570+
// case: asserting within check does still work with improper outer clause and improper inner clause
1571+
expect(within(screen.getByRole("button")).getByText("Hello")).toBeOnTheScreen()`,
14781572
},
14791573
],
14801574
});

0 commit comments

Comments
 (0)