Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ module.exports = [
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | |
| [no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | ![badge-angular][] ![badge-dom][] ![badge-react][] | | |
| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-await-sync-queries](docs/rules/no-await-sync-queries.md) | Disallow unnecessary `await` for sync queries | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | |
| [no-debugging-utils](docs/rules/no-debugging-utils.md) | Disallow the use of debugging utilities like `debug` | | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | |
| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![badge-angular][] ![badge-marko][] ![badge-react][] ![badge-svelte][] ![badge-vue][] | | 🔧 |
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/no-await-sync-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

Ensure that sync queries are not awaited unnecessarily.
Expand Down
13 changes: 12 additions & 1 deletion lib/rules/no-await-sync-queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createTestingLibraryRule } from '../create-testing-library-rule';
import { getDeepestIdentifierNode } from '../node-utils';
import { getSourceCode } from '../utils';

import type { TSESTree } from '@typescript-eslint/utils';

Expand Down Expand Up @@ -27,12 +28,16 @@ export default createTestingLibraryRule<Options, MessageIds>({
'`{{ name }}` query is sync so it does not need to be awaited',
},
schema: [],
fixable: 'code',
},
defaultOptions: [],

create(context, _, helpers) {
return {
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) {
'AwaitExpression > CallExpression'(
node: TSESTree.CallExpression & { parent: TSESTree.AwaitExpression }
) {
const awaitExpression = node.parent;
const deepestIdentifierNode = getDeepestIdentifierNode(node);

if (!deepestIdentifierNode) {
Expand All @@ -46,6 +51,12 @@ export default createTestingLibraryRule<Options, MessageIds>({
data: {
name: deepestIdentifierNode.name,
},
fix: (fixer) => {
const awaitToken =
getSourceCode(context).getFirstToken(awaitExpression);

return awaitToken ? fixer.remove(awaitToken) : null;
},
});
}
},
Expand Down
188 changes: 125 additions & 63 deletions tests/lib/rules/no-await-sync-queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ import {
} from '../../../lib/utils';
import { createRuleTester } from '../test-utils';

import type { MessageIds } from '../../../lib/rules/no-await-sync-queries';
import type {
InvalidTestCase,
ValidTestCase,
} from '@typescript-eslint/rule-tester';

const ruleTester = createRuleTester();

type RuleValidTestCase = ValidTestCase<[]>;
type RuleInvalidTestCase = InvalidTestCase<MessageIds, []>;

const SUPPORTED_TESTING_FRAMEWORKS = [
'@testing-library/dom',
'@testing-library/angular',
Expand All @@ -18,7 +27,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [
ruleTester.run(RULE_NAME, rule, {
valid: [
// sync queries without await are valid
...SYNC_QUERIES_COMBINATIONS.map((query) => ({
...SYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
code: `() => {
const element = ${query}('foo')
}
Expand Down Expand Up @@ -61,23 +70,23 @@ ruleTester.run(RULE_NAME, rule, {
},

// sync queries without await inside assert are valid
...SYNC_QUERIES_COMBINATIONS.map((query) => ({
...SYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
code: `() => {
expect(${query}('foo')).toBeEnabled()
}
`,
})),

// async queries with await operator are valid
...ASYNC_QUERIES_COMBINATIONS.map((query) => ({
...ASYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
code: `async () => {
const element = await ${query}('foo')
}
`,
})),

// async queries with then method are valid
...ASYNC_QUERIES_COMBINATIONS.map((query) => ({
...ASYNC_QUERIES_COMBINATIONS.map<RuleValidTestCase>((query) => ({
code: `() => {
${query}('foo').then(() => {});
}
Expand Down Expand Up @@ -128,22 +137,23 @@ ruleTester.run(RULE_NAME, rule, {

invalid: [
// sync queries with await operator are not valid
...SYNC_QUERIES_COMBINATIONS.map(
(query) =>
({
code: `async () => {
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
code: `async () => {
const element = await ${query}('foo')
}
`,
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 31,
},
],
}) as const
),
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 31,
},
],
output: `async () => {
const element = ${query}('foo')
}
`,
})),
// custom sync queries with await operator are not valid
{
code: `
Expand All @@ -152,6 +162,11 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
output: `
async () => {
const element = getByIcon('search')
}
`,
},
{
code: `
Expand All @@ -160,6 +175,11 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
output: `
async () => {
const element = queryByIcon('search')
}
`,
},
{
code: `
Expand All @@ -168,6 +188,11 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
output: `
async () => {
const element = screen.getAllByIcon('search')
}
`,
},
{
code: `
Expand All @@ -176,75 +201,88 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
output: `
async () => {
const element = screen.queryAllByIcon('search')
}
`,
},
// sync queries with await operator inside assert are not valid
...SYNC_QUERIES_COMBINATIONS.map(
(query) =>
({
code: `async () => {
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
code: `async () => {
expect(await ${query}('foo')).toBeEnabled()
}
`,
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 22,
},
],
}) as const
),
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 22,
},
],
output: `async () => {
expect( ${query}('foo')).toBeEnabled()
}
`,
})),

// sync queries in screen with await operator are not valid
...SYNC_QUERIES_COMBINATIONS.map(
(query) =>
({
code: `async () => {
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
code: `async () => {
const element = await screen.${query}('foo')
}
`,
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 38,
},
],
}) as const
),
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 38,
},
],
output: `async () => {
const element = screen.${query}('foo')
}
`,
})),

// sync queries in screen with await operator inside assert are not valid
...SYNC_QUERIES_COMBINATIONS.map(
(query) =>
({
code: `async () => {
...SYNC_QUERIES_COMBINATIONS.map<RuleInvalidTestCase>((query) => ({
code: `async () => {
expect(await screen.${query}('foo')).toBeEnabled()
}
`,
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 29,
},
],
}) as const
),
errors: [
{
messageId: 'noAwaitSyncQuery',
line: 2,
column: 29,
},
],
output: `async () => {
expect( screen.${query}('foo')).toBeEnabled()
}
`,
})),

// sync query awaited and related to testing library module
// with custom module setting is not valid
...SUPPORTED_TESTING_FRAMEWORKS.map(
(testingFramework) =>
({
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
...SUPPORTED_TESTING_FRAMEWORKS.map<RuleInvalidTestCase>(
(testingFramework) => ({
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { screen } from '${testingFramework}'
() => {
const element = await screen.getByRole('button')
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
}) as const
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
output: `
import { screen } from '${testingFramework}'
() => {
const element = screen.getByRole('button')
}
`,
})
),
// sync query awaited and related to custom module is not valid
{
Expand All @@ -256,6 +294,12 @@ ruleTester.run(RULE_NAME, rule, {
}
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 4, column: 38 }],
output: `
import { screen } from 'test-utils'
() => {
const element = screen.getByRole('button')
}
`,
},

// awaited custom sync query matching custom-queries setting is invalid
Expand All @@ -269,6 +313,24 @@ ruleTester.run(RULE_NAME, rule, {
})
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 31 }],
output: `
test('A valid example test', async () => {
const element = queryByIcon('search')
})
`,
},
{
code: `
test('A valid example test', async () => {
const element = await(screen.getByRole('button'))
})
`,
errors: [{ messageId: 'noAwaitSyncQuery', line: 3, column: 38 }],
output: `
test('A valid example test', async () => {
const element = (screen.getByRole('button'))
})
`,
},
],
});