diff --git a/src/rules/__tests__/valid-expect.test.js b/src/rules/__tests__/valid-expect.test.js index 6a75086c6..a10caea02 100644 --- a/src/rules/__tests__/valid-expect.test.js +++ b/src/rules/__tests__/valid-expect.test.js @@ -42,6 +42,35 @@ ruleTester.run('valid-expect', rule, { 'test("valid-expect", async () => { await Promise.race([expect(Promise.reject(2)).not.rejects.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });', 'test("valid-expect", async () => { await Promise.allSettled([expect(Promise.reject(2)).not.rejects.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });', 'test("valid-expect", async () => { await Promise.any([expect(Promise.reject(2)).not.rejects.toBeDefined(), expect(Promise.reject(2)).rejects.not.toBeDefined()]); });', + 'test("valid-expect", async () => { return expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")); });', + 'test("valid-expect", async () => { return expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")).then(() => console.log("another valid case")); });', + 'test("valid-expect", async () => { return expect(Promise.reject(2)).not.resolves.toBeDefined().catch(() => console.log("valid-case")); });', + 'test("valid-expect", async () => { return expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")).catch(() => console.log("another valid case")); });', + 'test("valid-expect", async () => { return expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")).then(() => console.log("another valid case")); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().catch(() => console.log("valid-case")); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => console.log("valid-case")).catch(() => console.log("another valid case")); });', + 'test("valid-expect", async () => { await expect(Promise.reject(2)).not.resolves.toBeDefined().then(() => { expect(someMock).toHaveBeenCalledTimes(1); }); });', + { + code: `test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { + return expect(Promise.resolve(2)).resolves.toBe(1); + }); + });`, + }, + { + code: `test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { + await expect(Promise.resolve(2)).resolves.toBe(1); + }); + });`, + }, + { + code: `test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => expect(Promise.resolve(2)).resolves.toBe(1)); + });`, + }, ], invalid: [ @@ -484,5 +513,40 @@ ruleTester.run('valid-expect', rule, { }, ], }, + { + code: `test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(() => { + expect(Promise.resolve(2)).resolves.toBe(1); + }); + });`, + errors: [ + { + line: 3, + column: 11, + endLine: 3, + endColumn: 54, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, + ], + }, + { + code: `test("valid-expect", () => { + return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => { + await expect(Promise.resolve(2)).resolves.toBe(1); + expect(Promise.resolve(4)).resolves.toBe(4); + }); + });`, + errors: [ + { + line: 4, + column: 11, + endLine: 4, + endColumn: 54, + messageId: 'asyncMustBeAwaited', + data: { orReturned: ' or returned' }, + }, + ], + }, ], }); diff --git a/src/rules/valid-expect.js b/src/rules/valid-expect.js index ec46e7ddc..b72def594 100644 --- a/src/rules/valid-expect.js +++ b/src/rules/valid-expect.js @@ -63,6 +63,24 @@ const getPromiseCallExpressionNode = node => { return null; }; +const getParentIfThenified = node => { + const grandParentNode = node.parent && node.parent.parent; + + if ( + grandParentNode && + grandParentNode.type === 'CallExpression' && + grandParentNode.callee && + grandParentNode.callee.type === 'MemberExpression' && + ['then', 'catch'].includes(grandParentNode.callee.property.name) && + grandParentNode.parent + ) { + // Just in case `then`s are chained look one above. + return getParentIfThenified(grandParentNode); + } + + return node; +}; + const checkIfValidReturn = (parentCallExpressionNode, allowReturn) => { const validParentNodeTypes = ['ArrowFunctionExpression', 'AwaitExpression']; if (allowReturn) { @@ -223,6 +241,8 @@ export default { const orReturned = allowReturn ? ' or returned' : ''; let messageId = 'asyncMustBeAwaited'; + parentNode = getParentIfThenified(parentNode); + // Promise.x([expect()]) || Promise.x(expect()) if (promiseArgumentTypes.includes(parentNode.parent.type)) { const promiseNode = getPromiseCallExpressionNode(