diff --git a/docs/rules/await-async-queries.md b/docs/rules/await-async-queries.md index d0d41a3e..b0666f35 100644 --- a/docs/rules/await-async-queries.md +++ b/docs/rules/await-async-queries.md @@ -21,7 +21,7 @@ problems in the tests. The promise will be considered as handled when: - using the `await` operator - wrapped within `Promise.all` or `Promise.allSettled` methods -- chaining the `then` or `catch` method +- chaining the `then`, `catch`, `finally` method - chaining `resolves` or `rejects` from jest - chaining `toResolve()` or `toReject()` from [jest-extended](https://github.com/jest-community/jest-extended#promise) - chaining jasmine [async matchers](https://jasmine.github.io/api/edge/async-matchers.html) diff --git a/docs/rules/await-async-utils.md b/docs/rules/await-async-utils.md index ac22b228..7162c9c0 100644 --- a/docs/rules/await-async-utils.md +++ b/docs/rules/await-async-utils.md @@ -19,7 +19,7 @@ problems in the tests. The promise will be considered as handled when: - using the `await` operator - wrapped within `Promise.all` or `Promise.allSettled` methods -- chaining the `then` or `catch` method +- chaining the `then`, `catch`, `finally` method - chaining `resolves` or `rejects` from jest - chaining `toResolve()` or `toReject()` from [jest-extended](https://github.com/jest-community/jest-extended#promise) - chaining jasmine [async matchers](https://jasmine.github.io/api/edge/async-matchers.html) diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index c89d102e..ef1971b8 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -155,19 +155,19 @@ export function hasPromiseHandlerProperty(node: TSESTree.Node): boolean { return ( isMemberExpression(node) && ASTUtils.isIdentifier(node.property) && - ['then', 'catch'].includes(node.property.name) + ['then', 'catch', 'finally'].includes(node.property.name) ); } export function hasChainedPromiseHandler(node: TSESTree.Node): boolean { const parent = node.parent; - // wait(...).then(...) or wait(...).catch(...) + // wait(...).then(...) or wait(...).catch(...) or wait(...).finally(...) if (isCallExpression(parent) && parent.parent) { return hasPromiseHandlerProperty(parent.parent); } - // promise.then(...) or promise.catch(...) + // promise.then(...) or promise.catch(...) or promise(...).finally(...) return !!parent && hasPromiseHandlerProperty(parent); } @@ -222,7 +222,7 @@ export function isPromisesArrayResolved(node: TSESTree.Node): boolean { * - it belongs to the `await` expression * - it belongs to the `Promise.all` method * - it belongs to the `Promise.allSettled` method - * - it's chained with the `then` or `catch` method + * - it's chained with the `then`, `catch`, `finally` method * - it's returned from a function * - has `resolves` or `rejects` jest methods * - has `toResolve` or `toReject` jest-extended matchers diff --git a/tests/lib/rules/await-async-events.test.ts b/tests/lib/rules/await-async-events.test.ts index afa4495a..33b52d52 100644 --- a/tests/lib/rules/await-async-events.test.ts +++ b/tests/lib/rules/await-async-events.test.ts @@ -109,6 +109,16 @@ ruleTester.run(RULE_NAME, rule, { `, options: [{ eventModule: 'fireEvent' }] as const, })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('chain finally method to promise from event method is valid', async (done) => { + fireEvent.${eventMethod}(getByLabelText('username')) + .finally(() => { done() }) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), { code: ` import { fireEvent } from '${testingFramework}' @@ -350,6 +360,16 @@ ruleTester.run(RULE_NAME, rule, { ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ code: ` import userEvent from '${testingFramework}' + test('chain finally method to promise from event method is valid', async (done) => { + userEvent.${eventMethod}(getByLabelText('username')) + .finally(() => { done() }) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' test('chain then method to several promises from event methods is valid', async (done) => { userEvent.${eventMethod}(getByLabelText('username')).then(() => { userEvent.${eventMethod}(getByLabelText('username')).then(() => { done() }) diff --git a/tests/lib/rules/await-async-queries.test.ts b/tests/lib/rules/await-async-queries.test.ts index 5c190b2c..f5cb1c1b 100644 --- a/tests/lib/rules/await-async-queries.test.ts +++ b/tests/lib/rules/await-async-queries.test.ts @@ -169,6 +169,14 @@ ruleTester.run(RULE_NAME, rule, { ` ), + // async queries are valid with finally + ...createTestCase( + (query) => ` + const promise = ${query}('foo') + promise.finally((done) => done()) + ` + ), + // async queries are valid when wrapped within Promise.all + await expression ...createTestCase( (query) => ` diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts index 280f84b4..ec41ab8d 100644 --- a/tests/lib/rules/await-async-utils.test.ts +++ b/tests/lib/rules/await-async-utils.test.ts @@ -57,6 +57,15 @@ ruleTester.run(RULE_NAME, rule, { doSomethingElse(); ${asyncUtil}(() => getByLabelText('email')).catch((error) => { console.log('done') }); }); + `, + })), + ...ASYNC_UTILS.map((asyncUtil) => ({ + code: ` + import { ${asyncUtil} } from '${testingFramework}'; + test('${asyncUtil} util directly chained with finally is valid', () => { + doSomethingElse(); + ${asyncUtil}(() => getByLabelText('email')).finally(() => { console.log('done') }); + }); `, })), ...ASYNC_UTILS.map((asyncUtil) => ({