From 61c2adf6f802985102b38161bf5038b2fce23c9c Mon Sep 17 00:00:00 2001 From: David Buchan-Swanson Date: Fri, 10 Aug 2018 00:24:11 +1000 Subject: [PATCH] feat(rules): add expect-expect (#133) --- docs/rules/expect-expect.md | 30 +++++++++++++ rules/__tests__/expect-expect.test.js | 48 ++++++++++++++++++++ rules/expect-expect.js | 63 +++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 docs/rules/expect-expect.md create mode 100644 rules/__tests__/expect-expect.test.js create mode 100644 rules/expect-expect.js diff --git a/docs/rules/expect-expect.md b/docs/rules/expect-expect.md new file mode 100644 index 000000000..e6c919148 --- /dev/null +++ b/docs/rules/expect-expect.md @@ -0,0 +1,30 @@ +# Enforce assertion to be made in a test body (expect-expect) + +Ensure that there is at least one `expect` call made in a test. + +## Rule details + +This rule triggers when there is no call made to `expect` in a test, to prevent +users from forgetting to add assertions. + +### Default configuration + +The following patterns are considered warnings: + +```js +it('should be a test', () => { + console.log('no assertion'); +}); +test('should assert something', () => {}); +``` + +The following patterns are not warnings: + +```js +it('should be a test', () => { + expect(true).toBeDefined(); +}); +it('should work with callbacks/async', () => { + somePromise().then(res => expect(res).toBe('passed')); +}); +``` diff --git a/rules/__tests__/expect-expect.test.js b/rules/__tests__/expect-expect.test.js new file mode 100644 index 000000000..fc0b3aaed --- /dev/null +++ b/rules/__tests__/expect-expect.test.js @@ -0,0 +1,48 @@ +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const rule = require('../expect-expect'); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + }, +}); + +ruleTester.run('expect-expect', rule, { + valid: [ + 'it("should pass", () => expect(true).toBeDefined())', + 'test("should pass", () => expect(true).toBeDefined())', + 'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))', + ], + + invalid: [ + { + code: 'it("should fail", () => {});', + errors: [ + { + message: 'Test has no assertions', + type: 'CallExpression', + }, + ], + }, + { + code: 'test("should fail", () => {});', + errors: [ + { + message: 'Test has no assertions', + type: 'CallExpression', + }, + ], + }, + { + code: 'it("should fail", () => { somePromise.then(() => {}); });', + errors: [ + { + message: 'Test has no assertions', + type: 'CallExpression', + }, + ], + }, + ], +}); diff --git a/rules/expect-expect.js b/rules/expect-expect.js new file mode 100644 index 000000000..67ed27b37 --- /dev/null +++ b/rules/expect-expect.js @@ -0,0 +1,63 @@ +'use strict'; + +/* + * This implementation is adapted from eslint-plugin-jasmine. + * MIT license, Remco Haszing. + */ + +const getDocsUrl = require('./util').getDocsUrl; + +module.exports = { + meta: { + docs: { + url: getDocsUrl(__filename), + }, + }, + create(context) { + // variables should be defined here + const unchecked = []; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + const isExpectCall = node => + // if we're not calling a function, ignore + node.type === 'CallExpression' && + // if we're not calling expect, ignore + node.callee.name === 'expect'; + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + return { + // give me methods + CallExpression(node) { + // keep track of `it` calls + if (['it', 'test'].indexOf(node.callee.name) > -1) { + unchecked.push(node); + return; + } + if (!isExpectCall(node)) { + return; + } + // here, we do have a call to expect + // use `some` to return early (in case of nested `it`s + context.getAncestors().some(ancestor => { + const index = unchecked.indexOf(ancestor); + if (index !== -1) { + unchecked.splice(index, 1); + return true; + } + return false; + }); + }, + 'Program:exit'() { + unchecked.forEach(node => + context.report({ + message: 'Test has no assertions', + node, + }) + ); + }, + }; + }, +};