From 2f1d2429f172645dc30fecc7619eb3e3662724bc Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sat, 28 Jan 2023 21:22:40 +0100 Subject: [PATCH 1/8] fix(await-async-utils): false positives related to destructuring --- lib/rules/await-async-utils.ts | 61 ++++++- tests/lib/rules/await-async-utils.test.ts | 184 ++++++++++++++++++++++ 2 files changed, 243 insertions(+), 2 deletions(-) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index dd805a06..780ec540 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/utils'; +import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { @@ -6,7 +6,9 @@ import { getFunctionName, getInnermostReturningFunction, getVariableReferences, + isObjectPattern, isPromiseHandled, + isProperty, } from '../node-utils'; export const RULE_NAME = 'await-async-utils'; @@ -47,7 +49,54 @@ export default createTestingLibraryRule({ } } + function detectDestructuredAsyncUtilWrapperAliases( + node: TSESTree.ObjectPattern + ) { + for (const property of node.properties) { + if (!isProperty(property)) { + continue; + } + + if ( + !ASTUtils.isIdentifier(property.key) || + !ASTUtils.isIdentifier(property.value) + ) { + continue; + } + + if (functionWrappersNames.includes(property.key.name)) { + if (property.value.name !== property.key.name) { + functionWrappersNames.push(property.value.name); + } + } + } + } + + const isDestructuredPropertyIdentifier = (node: TSESTree.Node): boolean => { + return ( + ASTUtils.isIdentifier(node) && isObjectPattern(node.parent?.parent) + ); + }; + + const isVariableDeclaratorInitializer = (node: TSESTree.Node): boolean => { + return ASTUtils.isVariableDeclarator(node.parent); + }; + return { + VariableDeclarator(node: TSESTree.VariableDeclarator) { + if (isObjectPattern(node.id)) { + detectDestructuredAsyncUtilWrapperAliases(node.id); + return; + } + + if ( + ASTUtils.isIdentifier(node.id) && + ASTUtils.isIdentifier(node.init) && + functionWrappersNames.includes(node.init.name) + ) { + functionWrappersNames.push(node.id.name); + } + }, 'CallExpression Identifier'(node: TSESTree.Identifier) { if (helpers.isAsyncUtil(node)) { // detect async query used within wrapper function for later analysis @@ -92,7 +141,15 @@ export default createTestingLibraryRule({ } } } - } else if (functionWrappersNames.includes(node.name)) { + + return; + } + + if ( + functionWrappersNames.includes(node.name) && + !isDestructuredPropertyIdentifier(node) && + !isVariableDeclaratorInitializer(node) + ) { // check async queries used within a wrapper previously detected if (!isPromiseHandled(node)) { context.report({ diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index 68d44d9f..bf8583e5 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -258,6 +258,54 @@ ruleTester.run(RULE_NAME, rule, { test('edge case for no innermost function scope', () => { const foo = waitFor }) + `, + }, + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { user, waitForLoadComplete } = setup(); + await waitForLoadComplete(); + + const myAlias = waitForLoadComplete; + await myAlias(); + + const { ...clone } = setup(); + await clone.waitForLoadComplete(); + + const { waitForLoadComplete: myAlias } = setup(); + await myAlias(); + + await setup().waitForLoadComplete(); + }); + `, + }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { user, ...rest } = setup(); + await rest.waitForLoadComplete(); + }); `, }, ]), @@ -441,6 +489,7 @@ ruleTester.run(RULE_NAME, rule, { ], } as const) ), + ...ASYNC_UTILS.map( (asyncUtil) => ({ @@ -463,5 +512,140 @@ ruleTester.run(RULE_NAME, rule, { ], } as const) ), + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { user, waitForLoadComplete } = setup(); + waitForLoadComplete(); + }); + `, + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForLoadComplete' }, + }, + ], + }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { user, waitForLoadComplete } = setup(); + const myAlias = waitForLoadComplete; + myAlias(); + }); + `, + errors: [ + { + line: 15, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { ...clone } = setup(); + clone.waitForLoadComplete(); + }); + `, + errors: [ + { + line: 14, + column: 17, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForLoadComplete' }, + }, + ], + }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + const { waitForLoadComplete: myAlias } = setup(); + myAlias(); + }); + `, + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('destructuring an async function wrapper & handling it later is valid', () => { + setup().waitForLoadComplete(); + }); + `, + errors: [ + { + line: 13, + column: 19, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForLoadComplete' }, + }, + ], + }, ]), }); From 43f5f23ec8a8470613ddb90e42cf7e25a8c1e6d2 Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sat, 28 Jan 2023 22:00:28 +0100 Subject: [PATCH 2/8] refactor(await-async-utils): move if conditions and reduce custom functions --- lib/rules/await-async-utils.ts | 102 ++++++++++++++------------------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index 780ec540..a0150b53 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -72,91 +72,77 @@ export default createTestingLibraryRule({ } } - const isDestructuredPropertyIdentifier = (node: TSESTree.Node): boolean => { - return ( - ASTUtils.isIdentifier(node) && isObjectPattern(node.parent?.parent) - ); - }; + // Either we report the async util directly, or a wrapper/alias name for it + const getMessageId = (node: TSESTree.Identifier): MessageIds => { + if (helpers.isAsyncUtil(node)) { + return 'awaitAsyncUtil'; + } - const isVariableDeclaratorInitializer = (node: TSESTree.Node): boolean => { - return ASTUtils.isVariableDeclarator(node.parent); + return 'asyncUtilWrapper'; }; return { VariableDeclarator(node: TSESTree.VariableDeclarator) { if (isObjectPattern(node.id)) { detectDestructuredAsyncUtilWrapperAliases(node.id); - return; } - if ( + const isAssigningKnownAsyncFunctionWrapper = ASTUtils.isIdentifier(node.id) && ASTUtils.isIdentifier(node.init) && - functionWrappersNames.includes(node.init.name) - ) { - functionWrappersNames.push(node.id.name); + functionWrappersNames.includes(node.init.name); + + if (isAssigningKnownAsyncFunctionWrapper) { + functionWrappersNames.push((node.id as TSESTree.Identifier).name); } }, 'CallExpression Identifier'(node: TSESTree.Identifier) { + const isAsyncUtilOrKnownAliasAroundIt = + helpers.isAsyncUtil(node) || + functionWrappersNames.includes(node.name); + if (!isAsyncUtilOrKnownAliasAroundIt) { + return; + } + + // detect async query used within wrapper function for later analysis if (helpers.isAsyncUtil(node)) { - // detect async query used within wrapper function for later analysis detectAsyncUtilWrapper(node); + } - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); + const closestCallExpression = findClosestCallExpressionNode(node, true); - if (!closestCallExpression?.parent) { - return; - } + if (!closestCallExpression?.parent) { + return; + } - const references = getVariableReferences( - context, - closestCallExpression.parent - ); + const references = getVariableReferences( + context, + closestCallExpression.parent + ); - if (references.length === 0) { - if (!isPromiseHandled(node)) { + if (references.length === 0) { + if (!isPromiseHandled(node)) { + context.report({ + node, + messageId: getMessageId(node), + data: { + name: node.name, + }, + }); + } + } else { + for (const reference of references) { + const referenceNode = reference.identifier as TSESTree.Identifier; + if (!isPromiseHandled(referenceNode)) { context.report({ node, - messageId: 'awaitAsyncUtil', + messageId: getMessageId(node), data: { name: node.name, }, }); + return; } - } else { - for (const reference of references) { - const referenceNode = reference.identifier as TSESTree.Identifier; - if (!isPromiseHandled(referenceNode)) { - context.report({ - node, - messageId: 'awaitAsyncUtil', - data: { - name: node.name, - }, - }); - return; - } - } - } - - return; - } - - if ( - functionWrappersNames.includes(node.name) && - !isDestructuredPropertyIdentifier(node) && - !isVariableDeclaratorInitializer(node) - ) { - // check async queries used within a wrapper previously detected - if (!isPromiseHandled(node)) { - context.report({ - node, - messageId: 'asyncUtilWrapper', - data: { name: node.name }, - }); } } }, From 93de4a75a2545aafc3127dcba6e5c0568572b28a Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 29 Jan 2023 12:32:54 +0100 Subject: [PATCH 3/8] refactor(await-async-utils): add test case and rename test cases --- lib/rules/await-async-utils.ts | 12 +++-- tests/lib/rules/await-async-utils.test.ts | 60 +++++++++++++++++------ 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index a0150b53..a175d1a5 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -3,6 +3,7 @@ import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; import { findClosestCallExpressionNode, + getDeepestIdentifierNode, getFunctionName, getInnermostReturningFunction, getVariableReferences, @@ -72,7 +73,10 @@ export default createTestingLibraryRule({ } } - // Either we report the async util directly, or a wrapper/alias name for it + /* + Either we report a direct usage of an async util or a usage of a wrapper + around an async util + */ const getMessageId = (node: TSESTree.Identifier): MessageIds => { if (helpers.isAsyncUtil(node)) { return 'awaitAsyncUtil'; @@ -89,8 +93,10 @@ export default createTestingLibraryRule({ const isAssigningKnownAsyncFunctionWrapper = ASTUtils.isIdentifier(node.id) && - ASTUtils.isIdentifier(node.init) && - functionWrappersNames.includes(node.init.name); + node.init !== null && + functionWrappersNames.includes( + getDeepestIdentifierNode(node.init)?.name ?? '' + ); if (isAssigningKnownAsyncFunctionWrapper) { functionWrappersNames.push((node.id as TSESTree.Identifier).name); diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index bf8583e5..b74f971e 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -266,7 +266,7 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; @@ -277,13 +277,18 @@ ruleTester.run(RULE_NAME, rule, { await waitForLoadComplete(); const myAlias = waitForLoadComplete; + const myOtherAlias = myAlias; await myAlias(); + await myOtherAlias(); const { ...clone } = setup(); await clone.waitForLoadComplete(); - const { waitForLoadComplete: myAlias } = setup(); - await myAlias(); + const { waitForLoadComplete: myDestructuredAlias } = setup(); + await myDestructuredAlias(); + + const { user, ...rest } = setup(); + await rest.waitForLoadComplete(); await setup().waitForLoadComplete(); }); @@ -296,7 +301,7 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; @@ -519,13 +524,13 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; } - test('destructuring an async function wrapper & handling it later is valid', () => { + test('unhandled promise from destructed property of async function wrapper is invalid', () => { const { user, waitForLoadComplete } = setup(); waitForLoadComplete(); }); @@ -546,13 +551,13 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; } - test('destructuring an async function wrapper & handling it later is valid', () => { + test('unhandled promise from assigning async function wrapper is invalid', () => { const { user, waitForLoadComplete } = setup(); const myAlias = waitForLoadComplete; myAlias(); @@ -574,13 +579,13 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; } - test('destructuring an async function wrapper & handling it later is valid', () => { + test('unhandled promise from rest element with async wrapper function member is invalid', () => { const { ...clone } = setup(); clone.waitForLoadComplete(); }); @@ -601,13 +606,13 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; } - test('destructuring an async function wrapper & handling it later is valid', () => { + test('unhandled promise from destructured property alias is invalid', () => { const { waitForLoadComplete: myAlias } = setup(); myAlias(); }); @@ -628,13 +633,13 @@ ruleTester.run(RULE_NAME, rule, { const utils = render(); const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByRole('progressbar')); + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); }; return { waitForLoadComplete, ...utils }; } - test('destructuring an async function wrapper & handling it later is valid', () => { + test('unhandled promise from object member with async wrapper value is invalid', () => { setup().waitForLoadComplete(); }); `, @@ -647,5 +652,32 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + + { + code: ` + function setup() { + const utils = render(); + + const waitForLoadComplete = () => { + return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + }; + + return { waitForLoadComplete, ...utils }; + } + + test('unhandled promise from object member with async wrapper value is invalid', () => { + const myAlias = setup().waitForLoadComplete; + myAlias(); + }); + `, + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + }, ]), }); From 8f9269c13302f1feb412b1938f16f4303afa96fc Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 29 Jan 2023 12:44:14 +0100 Subject: [PATCH 4/8] refactor(await-async-utils): move condition out to a variable --- lib/rules/await-async-utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index a175d1a5..5a9807b4 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -66,7 +66,10 @@ export default createTestingLibraryRule({ } if (functionWrappersNames.includes(property.key.name)) { - if (property.value.name !== property.key.name) { + const isDestructuredAsyncWrapperPropertyRenamed = + property.key.name !== property.value.name; + + if (isDestructuredAsyncWrapperPropertyRenamed) { functionWrappersNames.push(property.value.name); } } From 515b2161dcc223c8a8e924f58cd715da58ca628b Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 29 Jan 2023 12:48:34 +0100 Subject: [PATCH 5/8] refactor(await-async-utils): add comment --- lib/rules/await-async-utils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index 5a9807b4..4d7fbd39 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -50,6 +50,11 @@ export default createTestingLibraryRule({ } } + /* + Example: + `const { myAsyncWrapper: myRenamedValue } = someObject`; + Detects `myRenamedValue` and adds it to the known async wrapper names. + */ function detectDestructuredAsyncUtilWrapperAliases( node: TSESTree.ObjectPattern ) { From 022267f2470d1852c1c524399dcfc29c918f7dbe Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 29 Jan 2023 12:52:18 +0100 Subject: [PATCH 6/8] feat(await-async-util): return if node is object pattern when visiting variable declarator nodes --- lib/rules/await-async-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rules/await-async-utils.ts b/lib/rules/await-async-utils.ts index 4d7fbd39..9f545eba 100644 --- a/lib/rules/await-async-utils.ts +++ b/lib/rules/await-async-utils.ts @@ -97,6 +97,7 @@ export default createTestingLibraryRule({ VariableDeclarator(node: TSESTree.VariableDeclarator) { if (isObjectPattern(node.id)) { detectDestructuredAsyncUtilWrapperAliases(node.id); + return; } const isAssigningKnownAsyncFunctionWrapper = From 594420a27e886f1519f303803fef3a3f165106ef Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 29 Jan 2023 12:53:04 +0100 Subject: [PATCH 7/8] fix(await-async-utils): remove duplicate test case --- tests/lib/rules/await-async-utils.test.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index b74f971e..283e3262 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -292,25 +292,6 @@ ruleTester.run(RULE_NAME, rule, { await setup().waitForLoadComplete(); }); - `, - }, - - { - code: ` - function setup() { - const utils = render(); - - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); - }; - - return { waitForLoadComplete, ...utils }; - } - - test('destructuring an async function wrapper & handling it later is valid', () => { - const { user, ...rest } = setup(); - await rest.waitForLoadComplete(); - }); `, }, ]), From ce0cf94c669a954cb59f21f6a3ead36aaaa9169b Mon Sep 17 00:00:00 2001 From: Patrick Ahmetovic Date: Sun, 5 Feb 2023 16:29:53 +0100 Subject: [PATCH 8/8] test(await-async-utils): map through ASYNC_UTILS in the test cases --- tests/lib/rules/await-async-utils.test.ts | 250 ++++++++++++---------- 1 file changed, 131 insertions(+), 119 deletions(-) diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index 283e3262..65248eac 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -260,40 +260,40 @@ ruleTester.run(RULE_NAME, rule, { }) `, }, - { + ...ASYNC_UTILS.map((asyncUtil) => ({ code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } test('destructuring an async function wrapper & handling it later is valid', () => { - const { user, waitForLoadComplete } = setup(); - await waitForLoadComplete(); + const { user, waitForAsyncUtil } = setup(); + await waitForAsyncUtil(); - const myAlias = waitForLoadComplete; + const myAlias = waitForAsyncUtil; const myOtherAlias = myAlias; await myAlias(); await myOtherAlias(); const { ...clone } = setup(); - await clone.waitForLoadComplete(); + await clone.waitForAsyncUtil(); - const { waitForLoadComplete: myDestructuredAlias } = setup(); + const { waitForAsyncUtil: myDestructuredAlias } = setup(); await myDestructuredAlias(); const { user, ...rest } = setup(); - await rest.waitForLoadComplete(); + await rest.waitForAsyncUtil(); - await setup().waitForLoadComplete(); + await setup().waitForAsyncUtil(); }); `, - }, + })), ]), invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ ...ASYNC_UTILS.map( @@ -498,167 +498,179 @@ ruleTester.run(RULE_NAME, rule, { ], } as const) ), - - { - code: ` + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } test('unhandled promise from destructed property of async function wrapper is invalid', () => { - const { user, waitForLoadComplete } = setup(); - waitForLoadComplete(); - }); - `, - errors: [ - { - line: 14, - column: 11, - messageId: 'asyncUtilWrapper', - data: { name: 'waitForLoadComplete' }, - }, - ], - }, - - { - code: ` + const { user, waitForAsyncUtil } = setup(); + waitForAsyncUtil(); + }); + `, + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForAsyncUtil' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } - test('unhandled promise from assigning async function wrapper is invalid', () => { - const { user, waitForLoadComplete } = setup(); - const myAlias = waitForLoadComplete; + test('unhandled promise from destructed property of async function wrapper is invalid', () => { + const { user, waitForAsyncUtil } = setup(); + const myAlias = waitForAsyncUtil; myAlias(); }); `, - errors: [ - { - line: 15, - column: 11, - messageId: 'asyncUtilWrapper', - data: { name: 'myAlias' }, - }, - ], - }, - - { - code: ` + errors: [ + { + line: 15, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } - test('unhandled promise from rest element with async wrapper function member is invalid', () => { + test('unhandled promise from destructed property of async function wrapper is invalid', () => { const { ...clone } = setup(); - clone.waitForLoadComplete(); + clone.waitForAsyncUtil(); }); `, - errors: [ - { - line: 14, - column: 17, - messageId: 'asyncUtilWrapper', - data: { name: 'waitForLoadComplete' }, - }, - ], - }, - - { - code: ` + errors: [ + { + line: 14, + column: 17, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForAsyncUtil' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } - test('unhandled promise from destructured property alias is invalid', () => { - const { waitForLoadComplete: myAlias } = setup(); + test('unhandled promise from destructed property of async function wrapper is invalid', () => { + const { waitForAsyncUtil: myAlias } = setup(); myAlias(); }); `, - errors: [ - { - line: 14, - column: 11, - messageId: 'asyncUtilWrapper', - data: { name: 'myAlias' }, - }, - ], - }, - - { - code: ` + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } - test('unhandled promise from object member with async wrapper value is invalid', () => { - setup().waitForLoadComplete(); - }); - `, - errors: [ - { - line: 13, - column: 19, - messageId: 'asyncUtilWrapper', - data: { name: 'waitForLoadComplete' }, - }, - ], - }, - - { - code: ` + test('unhandled promise from destructed property of async function wrapper is invalid', () => { + setup().waitForAsyncUtil(); + }); + `, + errors: [ + { + line: 13, + column: 19, + messageId: 'asyncUtilWrapper', + data: { name: 'waitForAsyncUtil' }, + }, + ], + } as const) + ), + ...ASYNC_UTILS.map( + (asyncUtil) => + ({ + code: ` function setup() { const utils = render(); - const waitForLoadComplete = () => { - return waitForElementToBeRemoved(screen.queryByTestId('my-test-id')); + const waitForAsyncUtil = () => { + return ${asyncUtil}(screen.queryByTestId('my-test-id')); }; - return { waitForLoadComplete, ...utils }; + return { waitForAsyncUtil, ...utils }; } - test('unhandled promise from object member with async wrapper value is invalid', () => { - const myAlias = setup().waitForLoadComplete; + test('unhandled promise from destructed property of async function wrapper is invalid', () => { + const myAlias = setup().waitForAsyncUtil; myAlias(); }); `, - errors: [ - { - line: 14, - column: 11, - messageId: 'asyncUtilWrapper', - data: { name: 'myAlias' }, - }, - ], - }, + errors: [ + { + line: 14, + column: 11, + messageId: 'asyncUtilWrapper', + data: { name: 'myAlias' }, + }, + ], + } as const) + ), ]), });