From 8544edaecce16012bdfb8cdee8c345b760342823 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Sat, 20 Jul 2024 20:12:48 +0300
Subject: [PATCH 1/7] feat: create a fixer

---
 lib/rules/await-async-queries.ts            | 15 +++++++++
 tests/lib/rules/await-async-queries.test.ts | 35 +++++++++++++++++++++
 2 files changed, 50 insertions(+)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index 6da6b0d2..7d35fa0f 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -7,6 +7,7 @@ import {
 	getFunctionName,
 	getInnermostReturningFunction,
 	getVariableReferences,
+	isMemberExpression,
 	isPromiseHandled,
 } from '../node-utils';
 
@@ -34,6 +35,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
 			asyncQueryWrapper:
 				'promise returned from `{{ name }}` wrapper over async query must be handled',
 		},
+		fixable: 'code',
 		schema: [],
 	},
 	defaultOptions: [],
@@ -82,6 +84,19 @@ export default createTestingLibraryRule<Options, MessageIds>({
 								node: identifierNode,
 								messageId: 'awaitAsyncQuery',
 								data: { name: identifierNode.name },
+								fix: (fixer) => {
+									if (
+										isMemberExpression(identifierNode.parent) &&
+										ASTUtils.isIdentifier(identifierNode.parent.object) &&
+										identifierNode.parent.object.name === 'screen'
+									) {
+										return fixer.insertTextBefore(
+											identifierNode.parent,
+											'await '
+										);
+									}
+									return fixer.insertTextBefore(identifierNode, 'await ');
+								},
 							});
 							return;
 						}
diff --git a/tests/lib/rules/await-async-queries.test.ts b/tests/lib/rules/await-async-queries.test.ts
index efab2cbd..5bc2c661 100644
--- a/tests/lib/rules/await-async-queries.test.ts
+++ b/tests/lib/rules/await-async-queries.test.ts
@@ -361,6 +361,14 @@ ruleTester.run(RULE_NAME, rule, {
       });
       `,
 						errors: [{ messageId: 'awaitAsyncQuery', line: 6, column: 21 }],
+						output: `// async queries without await operator or then method are not valid
+      import { render } from '${testingFramework}'
+
+      test("An example test", async () => {
+        doSomething()
+        const foo = await ${query}('foo')
+      });
+      `,
 					}) as const
 			)
 		),
@@ -382,6 +390,13 @@ ruleTester.run(RULE_NAME, rule, {
 							data: { name: query },
 						},
 					],
+					output: `// async screen queries without await operator or then method are not valid
+      import { render } from '@testing-library/react'
+
+      test("An example test", async () => {
+        await screen.${query}('foo')
+      });
+      `,
 				}) as const
 		),
 		...ALL_ASYNC_COMBINATIONS_TO_TEST.map(
@@ -403,6 +418,14 @@ ruleTester.run(RULE_NAME, rule, {
 							data: { name: query },
 						},
 					],
+					output: `
+      import { render } from '@testing-library/react'
+
+      test("An example test", async () => {
+        doSomething()
+        const foo = await ${query}('foo')
+      });
+      `,
 				}) as const
 		),
 		...ALL_ASYNC_COMBINATIONS_TO_TEST.map(
@@ -440,6 +463,13 @@ ruleTester.run(RULE_NAME, rule, {
         })
       `,
 					errors: [{ messageId: 'awaitAsyncQuery', line: 5, column: 27 }],
+					output: `
+        import { render } from "another-library"
+
+        test('An example test', async () => {
+          const example = await ${query}("my example")
+        })
+      `,
 				}) as const
 		),
 
@@ -517,6 +547,11 @@ ruleTester.run(RULE_NAME, rule, {
       })
       `,
 			errors: [{ messageId: 'awaitAsyncQuery', line: 3, column: 25 }],
+			output: `
+      test('An invalid example test', () => {
+        const element = await findByIcon('search')
+      })
+      `,
 		},
 
 		{

From 5059bc2bbccbba6a32cdcf70d25a7737c6fe7523 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Fri, 26 Jul 2024 17:51:03 +0300
Subject: [PATCH 2/7] feat: fix promise references

---
 lib/rules/await-async-queries.ts            | 9 +++++++++
 tests/lib/rules/await-async-queries.test.ts | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index 7d35fa0f..b0379485 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -114,6 +114,15 @@ export default createTestingLibraryRule<Options, MessageIds>({
 								node: identifierNode,
 								messageId: 'awaitAsyncQuery',
 								data: { name: identifierNode.name },
+								fix: (fixer) => {
+									const fixes = [];
+									for (const ref of references) {
+										fixes.push(
+											fixer.insertTextBefore(ref.identifier, 'await ')
+										);
+									}
+									return fixes;
+								},
 							});
 							return;
 						}
diff --git a/tests/lib/rules/await-async-queries.test.ts b/tests/lib/rules/await-async-queries.test.ts
index 5bc2c661..f97b281c 100644
--- a/tests/lib/rules/await-async-queries.test.ts
+++ b/tests/lib/rules/await-async-queries.test.ts
@@ -448,6 +448,15 @@ ruleTester.run(RULE_NAME, rule, {
 							data: { name: query },
 						},
 					],
+					output: `
+      import { render } from '@testing-library/react'
+
+      test("An example test", async () => {
+        const foo = ${query}('foo')
+        expect(await foo).toBeInTheDocument()
+        expect(await foo).toHaveAttribute('src', 'bar');
+      });
+      `,
 				}) as const
 		),
 

From fe691cddf3f814e48c17a4c1e6f5c6b371419cf4 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Fri, 26 Jul 2024 17:51:28 +0300
Subject: [PATCH 3/7] feat: fix promise wrappers

---
 lib/rules/await-async-queries.ts            | 40 ++++++++++++
 tests/lib/rules/await-async-queries.test.ts | 71 ++++++++++++++++++++-
 2 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index b0379485..6329448d 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -3,6 +3,7 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils';
 import { createTestingLibraryRule } from '../create-testing-library-rule';
 import {
 	findClosestCallExpressionNode,
+	findClosestFunctionExpressionNode,
 	getDeepestIdentifierNode,
 	getFunctionName,
 	getInnermostReturningFunction,
@@ -136,6 +137,45 @@ export default createTestingLibraryRule<Options, MessageIds>({
 						node: identifierNode,
 						messageId: 'asyncQueryWrapper',
 						data: { name: identifierNode.name },
+						fix: (fixer) => {
+							const functionExpression =
+								findClosestFunctionExpressionNode(node);
+
+							if (!functionExpression) return null;
+
+							let IdentifierNodeFixer;
+							// If the wrapper is a property of an object,
+							// add 'await' before the object, e.g.:
+							//   const obj = { wrapper: () => screen.findByText(/foo/i) };
+							//   await obj.wrapper();
+							if (isMemberExpression(identifierNode.parent)) {
+								IdentifierNodeFixer = fixer.insertTextBefore(
+									identifierNode.parent,
+									'await '
+								);
+								// Otherwise, add 'await' before the wrapper function, e.g.:
+								//   const wrapper = () => screen.findByText(/foo/i);
+								//   await wrapper();
+							} else {
+								IdentifierNodeFixer = fixer.insertTextBefore(
+									identifierNode,
+									'await '
+								);
+							}
+
+							if (functionExpression.async) {
+								return IdentifierNodeFixer;
+							} else {
+								// Mutate the actual node so if other nodes exist in this
+								// function expression body they don't also try to fix it.
+								functionExpression.async = true;
+
+								return [
+									IdentifierNodeFixer,
+									fixer.insertTextBefore(functionExpression, 'async '),
+								];
+							}
+						},
 					});
 				}
 			},
diff --git a/tests/lib/rules/await-async-queries.test.ts b/tests/lib/rules/await-async-queries.test.ts
index f97b281c..2dc2ccf1 100644
--- a/tests/lib/rules/await-async-queries.test.ts
+++ b/tests/lib/rules/await-async-queries.test.ts
@@ -497,11 +497,26 @@ ruleTester.run(RULE_NAME, rule, {
           const element = queryWrapper()
         })
 
-        test("An invalid example test", async () => {
+        test("A valid example test", async () => {
           const element = await queryWrapper()
         })
       `,
 					errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }],
+					output: `
+        function queryWrapper() {
+          doSomethingElse();
+
+          return screen.${query}('foo')
+        }
+
+        test("An invalid example test", async () => {
+          const element = await queryWrapper()
+        })
+
+        test("A valid example test", async () => {
+          const element = await queryWrapper()
+        })
+      `,
 				}) as const
 		),
 		// unhandled promise from async query arrow function wrapper is invalid
@@ -519,11 +534,26 @@ ruleTester.run(RULE_NAME, rule, {
           const element = queryWrapper()
         })
 
-        test("An invalid example test", async () => {
+        test("A valid example test", async () => {
           const element = await queryWrapper()
         })
       `,
 					errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }],
+					output: `
+        const queryWrapper = () => {
+          doSomethingElse();
+
+          return ${query}('foo')
+        }
+
+        test("An invalid example test", async () => {
+          const element = await queryWrapper()
+        })
+
+        test("A valid example test", async () => {
+          const element = await queryWrapper()
+        })
+      `,
 				}) as const
 		),
 		// unhandled promise implicitly returned from async query arrow function wrapper is invalid
@@ -537,11 +567,22 @@ ruleTester.run(RULE_NAME, rule, {
           const element = queryWrapper()
         })
 
-        test("An invalid example test", async () => {
+        test("A valid example test", async () => {
           const element = await queryWrapper()
         })
       `,
 					errors: [{ messageId: 'asyncQueryWrapper', line: 5, column: 27 }],
+					output: `
+        const queryWrapper = () => screen.${query}('foo')
+
+        test("An invalid example test", async () => {
+          const element = await queryWrapper()
+        })
+
+        test("A valid example test", async () => {
+          const element = await queryWrapper()
+        })
+      `,
 				}) as const
 		),
 
@@ -589,6 +630,30 @@ ruleTester.run(RULE_NAME, rule, {
       })
     `,
 			errors: [{ messageId: 'asyncQueryWrapper', line: 19, column: 34 }],
+			output: `// similar to issue #359 but forcing an error in no-awaited wrapper
+      import { render, screen } from 'mocks/test-utils'
+      import userEvent from '@testing-library/user-event'
+
+      const testData = {
+        name: 'John Doe',
+        email: 'john@doe.com',
+        password: 'extremeSecret',
+      }
+
+      const selectors = {
+        username: () => screen.findByRole('textbox', { name: /username/i }),
+        email: () => screen.findByRole('textbox', { name: /e-mail/i }),
+        password: () => screen.findByLabelText(/password/i),
+      }
+
+      test('this is a valid case', async () => {
+        render(<SomeComponent />)
+        userEvent.type(await selectors.username(), testData.name) // <-- unhandled here
+        userEvent.type(await selectors.email(), testData.email)
+        userEvent.type(await selectors.password(), testData.password)
+        // ...
+      })
+    `,
 		},
 	],
 });

From 5c724063705051ecb7a82f42bcea2087871aecb5 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Fri, 26 Jul 2024 18:10:03 +0300
Subject: [PATCH 4/7] docs: update docs

---
 README.md                         | 2 +-
 docs/rules/await-async-queries.md | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 0fa46197..17393614 100644
--- a/README.md
+++ b/README.md
@@ -295,7 +295,7 @@ module.exports = [
 | Name                                                                             | Description                                                                                  | 💼                                                                                 | ⚠️                                                                  | 🔧  |
 | :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :------------------------------------------------------------------ | :-- |
 | [await-async-events](docs/rules/await-async-events.md)                           | Enforce promises from async event methods are handled                                        | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] |                                                                     | 🔧  |
-| [await-async-queries](docs/rules/await-async-queries.md)                         | Enforce promises from async queries to be handled                                            | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] |                                                                     |     |
+| [await-async-queries](docs/rules/await-async-queries.md)                         | Enforce promises from async queries to be handled                                            | ![badge-angular][] ![badge-dom][] ![badge-marko][] ![badge-react][] ![badge-vue][] |                                                                     | 🔧  |
 | [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-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][]                                 |                                                                     |     |
diff --git a/docs/rules/await-async-queries.md b/docs/rules/await-async-queries.md
index 520b6968..baf26842 100644
--- a/docs/rules/await-async-queries.md
+++ b/docs/rules/await-async-queries.md
@@ -2,6 +2,8 @@
 
 💼 This rule is enabled in the following configs: `angular`, `dom`, `marko`, `react`, `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 promises returned by async queries are handled properly.

From 5fbbc5050e6fa8e323afba3b3dcd837f7243bc20 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Sat, 27 Jul 2024 16:54:36 +0300
Subject: [PATCH 5/7] feat: refactor loop to map in fixer

---
 lib/rules/await-async-queries.ts | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index 6329448d..be47f390 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -116,12 +116,9 @@ export default createTestingLibraryRule<Options, MessageIds>({
 								messageId: 'awaitAsyncQuery',
 								data: { name: identifierNode.name },
 								fix: (fixer) => {
-									const fixes = [];
-									for (const ref of references) {
-										fixes.push(
-											fixer.insertTextBefore(ref.identifier, 'await ')
-										);
-									}
+									const fixes = references.map((ref) =>
+										fixer.insertTextBefore(ref.identifier, 'await ')
+									);
 									return fixes;
 								},
 							});

From a5826063dd9b68c988eb9ec1d1b08b82f056f6b7 Mon Sep 17 00:00:00 2001
From: neriyarden <neriyarden@gmail.com>
Date: Sat, 27 Jul 2024 17:01:41 +0300
Subject: [PATCH 6/7] refactor: improve comments readability

---
 lib/rules/await-async-queries.ts | 40 ++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 15 deletions(-)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index be47f390..5e53dca0 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -77,8 +77,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
 						closestCallExpressionNode.parent
 					);
 
-					// check direct usage of async query:
-					// const element = await findByRole('button')
+					/**
+					 * Check direct usage of async query:
+					 * const element = await findByRole('button');
+					 */
 					if (references.length === 0) {
 						if (!isPromiseHandled(identifierNode)) {
 							context.report({
@@ -103,9 +105,11 @@ export default createTestingLibraryRule<Options, MessageIds>({
 						}
 					}
 
-					// check references usages of async query:
-					//  const promise = findByRole('button')
-					//  const element = await promise
+					/**
+					 * Check references usages of async query:
+					 * const promise = findByRole('button');
+					 * const element = await promise;
+					 */
 					for (const reference of references) {
 						if (
 							ASTUtils.isIdentifier(reference.identifier) &&
@@ -129,7 +133,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
 					functionWrappersNames.includes(identifierNode.name) &&
 					!isPromiseHandled(identifierNode)
 				) {
-					// check async queries used within a wrapper previously detected
+					// Check async queries used within a wrapper previously detected
 					context.report({
 						node: identifierNode,
 						messageId: 'asyncQueryWrapper',
@@ -141,19 +145,23 @@ export default createTestingLibraryRule<Options, MessageIds>({
 							if (!functionExpression) return null;
 
 							let IdentifierNodeFixer;
-							// If the wrapper is a property of an object,
-							// add 'await' before the object, e.g.:
-							//   const obj = { wrapper: () => screen.findByText(/foo/i) };
-							//   await obj.wrapper();
 							if (isMemberExpression(identifierNode.parent)) {
+								/**
+								 * If the wrapper is a property of an object,
+								 * add 'await' before the object, e.g.:
+								 * const obj = { wrapper: () => screen.findByText(/foo/i) };
+								 * await obj.wrapper();
+								 */
 								IdentifierNodeFixer = fixer.insertTextBefore(
 									identifierNode.parent,
 									'await '
 								);
-								// Otherwise, add 'await' before the wrapper function, e.g.:
-								//   const wrapper = () => screen.findByText(/foo/i);
-								//   await wrapper();
 							} else {
+								/**
+								 * Add 'await' before the wrapper function, e.g.:
+								 * const wrapper = () => screen.findByText(/foo/i);
+								 * await wrapper();
+								 */
 								IdentifierNodeFixer = fixer.insertTextBefore(
 									identifierNode,
 									'await '
@@ -163,8 +171,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
 							if (functionExpression.async) {
 								return IdentifierNodeFixer;
 							} else {
-								// Mutate the actual node so if other nodes exist in this
-								// function expression body they don't also try to fix it.
+								/**
+								 * Mutate the actual node so if other nodes exist in this
+								 * function expression body they don't also try to fix it.
+								 */
 								functionExpression.async = true;
 
 								return [

From bfbd4579ac443c31b0a3a98a3d4d2fa017bde153 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= <info@michaeldeboey.be>
Date: Sat, 19 Oct 2024 03:57:38 +0200
Subject: [PATCH 7/7] Update lib/rules/await-async-queries.ts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Michaël De Boey <info@michaeldeboey.be>
---
 lib/rules/await-async-queries.ts | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lib/rules/await-async-queries.ts b/lib/rules/await-async-queries.ts
index 5e53dca0..3d77f428 100644
--- a/lib/rules/await-async-queries.ts
+++ b/lib/rules/await-async-queries.ts
@@ -119,12 +119,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
 								node: identifierNode,
 								messageId: 'awaitAsyncQuery',
 								data: { name: identifierNode.name },
-								fix: (fixer) => {
-									const fixes = references.map((ref) =>
+								fix: (fixer) =>
+									references.map((ref) =>
 										fixer.insertTextBefore(ref.identifier, 'await ')
-									);
-									return fixes;
-								},
+									),
 							});
 							return;
 						}