From 4fa5083d9dead67d95e66299859e98598fdc3243 Mon Sep 17 00:00:00 2001 From: Macklin Underdown Date: Tue, 13 Feb 2018 13:24:12 -0500 Subject: [PATCH] fix(valid-expect): validate async expect calls Resolves #54 --- rules/__tests__/valid-expect.test.js | 39 +++++++++++++++++-- rules/valid-expect.js | 56 +++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/rules/__tests__/valid-expect.test.js b/rules/__tests__/valid-expect.test.js index ed74b6463..9ffa78b78 100644 --- a/rules/__tests__/valid-expect.test.js +++ b/rules/__tests__/valid-expect.test.js @@ -3,7 +3,11 @@ const RuleTester = require('eslint').RuleTester; const rules = require('../..').rules; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 8, + }, +}); ruleTester.run('valid-expect', rules['valid-expect'], { valid: [ @@ -11,8 +15,12 @@ ruleTester.run('valid-expect', rules['valid-expect'], { 'expect(true).toBeDefined();', 'expect([1, 2, 3]).toEqual([1, 2, 3]);', 'expect(undefined).not.toBeDefined();', - 'expect(Promise.resolve(2)).resolves.toBeDefined();', - 'expect(Promise.reject(2)).rejects.toBeDefined();', + 'test("foo", () => { return expect(Promise.resolve(2)).resolves.toBeDefined(); });', + 'test("foo", async () => { await expect(Promise.reject(2)).rejects.toBeDefined(); });', + 'test("foo", () => expect(Promise.resolve(2)).resolves.toBeDefined());', + 'test("foo", async () => await expect(Promise.reject(2)).rejects.toBeDefined());', + 'test("foo", async () => { expect(await Promise.resolve(2)).resolves.toBeDefined(); });', + 'test("foo", async () => { expect(await Promise.reject(2)).rejects.toBeDefined(); });', ], invalid: [ @@ -131,5 +139,30 @@ ruleTester.run('valid-expect', rules['valid-expect'], { }, ], }, + { + code: + 'test("foo", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', + errors: [{ message: "Must return or await 'expect.resolves' statement" }], + }, + { + code: + 'test("foo", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', + errors: [{ message: "Must await 'expect.resolves' statement" }], + }, + { + code: + 'test("foo", () => { expect(Promise.reject(2)).rejects.toBeDefined(); });', + errors: [{ message: "Must return or await 'expect.rejects' statement" }], + }, + { + code: + 'test("foo", async () => { expect(Promise.reject(2)).rejects.toBeDefined(); });', + errors: [{ message: "Must await 'expect.rejects' statement" }], + }, + { + code: + 'test("foo", async () => expect(Promise.reject(2)).rejects.toBeDefined());', + errors: [{ message: 'Unexpected use of async without await' }], + }, ], }); diff --git a/rules/valid-expect.js b/rules/valid-expect.js index 231355649..4a1db996a 100644 --- a/rules/valid-expect.js +++ b/rules/valid-expect.js @@ -5,19 +5,71 @@ * MIT license, Tom Vincent. */ -const getDocsUrl = require('./util').getDocsUrl; +const util = require('./util'); const expectProperties = ['not', 'resolves', 'rejects']; +function isResolvesOrRejectsExpression(node) { + if (node.expression.type !== 'CallExpression') { + return false; + } + const callee = node.expression.callee; + return ( + callee.object && + callee.object.object && + callee.object.object.callee && + callee.object.object.callee.name === 'expect' && + callee.object.property && + (callee.object.property.name === 'resolves' || + callee.object.property.name === 'rejects') + ); +} + module.exports = { meta: { docs: { - url: getDocsUrl('valid-expect.md'), + url: util.getDocsUrl('valid-expect.md'), }, }, create(context) { + function validateAsyncExpects(node) { + const callback = node.arguments[1]; + if (callback && util.isFunction(callback)) { + if (callback.body.type === 'BlockStatement') { + callback.body.body + .filter(node => node.type === 'ExpressionStatement') + .filter(isResolvesOrRejectsExpression) + .forEach(node => { + if ( + node.expression.callee.object.object.arguments[0].type === + 'AwaitExpression' + ) { + return; + } + const propertyName = node.expression.callee.object.property.name; + context.report({ + node, + message: callback.async + ? "Must await 'expect.{{ propertyName }}' statement" + : "Must return or await 'expect.{{ propertyName }}' statement", + data: { propertyName }, + }); + }); + } else if (callback.async && callback.body.type !== 'AwaitExpression') { + context.report({ + node: callback.body, + message: 'Unexpected use of async without await', + }); + } + } + } + return { CallExpression(node) { + if (util.isTestCase(node)) { + validateAsyncExpects(node); + } + const calleeName = node.callee.name; if (calleeName === 'expect') {