diff --git a/src/rules/__tests__/valid-expect-in-promise.test.ts b/src/rules/__tests__/valid-expect-in-promise.test.ts index 74cd88fcf..b06a4cd46 100644 --- a/src/rules/__tests__/valid-expect-in-promise.test.ts +++ b/src/rules/__tests__/valid-expect-in-promise.test.ts @@ -15,6 +15,46 @@ ruleTester.run('valid-expect-in-promise', rule, { "test('something', () => Promise.resolve().then(() => expect(1).toBe(2)));", 'Promise.resolve().then(() => expect(1).toBe(2))', 'const x = Promise.resolve().then(() => expect(1).toBe(2))', + dedent` + it('is valid', async () => { + const promise = loadNumber().then(number => { + expect(typeof number).toBe('number'); + + return number + 1; + }); + + expect(await promise).toBeGreaterThan(1); + }); + `, + dedent` + it('is valid', async () => { + const promise = loadNumber().then(number => { + expect(typeof number).toBe('number'); + + return number + 1; + }); + + logValue(await promise); + }); + `, + dedent` + it('is valid', async () => { + const promise = loadNumber().then(number => { + expect(typeof number).toBe('number'); + + return 1; + }); + + expect.assertions(await promise); + }); + `, + dedent` + it('is valid', async () => { + await loadNumber().then(number => { + expect(typeof number).toBe('number'); + }); + }); + `, dedent` it('it1', () => new Promise((done) => { test() @@ -1356,5 +1396,23 @@ ruleTester.run('valid-expect-in-promise', rule, { }, ], }, + { + code: dedent` + test('that we error on this', () => { + const promise = something().then(value => { + expect(value).toBe('red'); + }); + + log(promise); + }); + `, + errors: [ + { + messageId: 'expectInFloatingPromise', + line: 2, + column: 9, + }, + ], + }, ], }); diff --git a/src/rules/valid-expect-in-promise.ts b/src/rules/valid-expect-in-promise.ts index 3bd562ab9..02caa3046 100644 --- a/src/rules/valid-expect-in-promise.ts +++ b/src/rules/valid-expect-in-promise.ts @@ -144,6 +144,33 @@ const isPromiseMethodThatUsesValue = ( ); }; +/** + * Attempts to determine if the runtime value represented by the given `identifier` + * is `await`ed as an argument along the given call expression + */ +const isValueAwaitedInArguments = ( + name: string, + call: TSESTree.CallExpression, +): boolean => { + for (const argument of call.arguments) { + if ( + argument.type === AST_NODE_TYPES.AwaitExpression && + isIdentifier(argument.argument, name) + ) { + return true; + } + } + + if ( + call.callee.type === AST_NODE_TYPES.MemberExpression && + call.callee.object.type === AST_NODE_TYPES.CallExpression + ) { + return isValueAwaitedInArguments(name, call.callee.object); + } + + return false; +}; + /** * Attempts to determine if the runtime value represented by the given `identifier` * is `await`ed or `return`ed within the given `body` of statements @@ -166,6 +193,14 @@ const isValueAwaitedOrReturned = ( } if (node.type === AST_NODE_TYPES.ExpressionStatement) { + // it's possible that we're awaiting the value as an argument + if ( + node.expression.type === AST_NODE_TYPES.CallExpression && + isValueAwaitedInArguments(name, node.expression) + ) { + return true; + } + if (node.expression.type === AST_NODE_TYPES.AwaitExpression) { return isPromiseMethodThatUsesValue(node.expression, identifier); }