From 4e61e9b8cb779c6e634f7c76ffefa3d66797a8ee Mon Sep 17 00:00:00 2001 From: Mathias Schreck Date: Thu, 6 Oct 2016 08:50:26 +0200 Subject: [PATCH] Add no-nested-tests rule --- docs/rules/README.md | 1 + docs/rules/no-nested-tests.md | 47 +++++++++++++++ index.js | 3 +- lib/rules/no-nested-tests.js | 38 ++++++++++++ lib/util/ast.js | 4 +- test/rules/no-nested-tests.js | 109 ++++++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 docs/rules/no-nested-tests.md create mode 100644 lib/rules/no-nested-tests.js create mode 100644 test/rules/no-nested-tests.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 0407771..3325116 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -16,3 +16,4 @@ * [no-top-level-hooks](no-top-level-hooks.md) - disallow top-level hooks * [no-identical-title](no-identical-title.md) - disallow identical titles * [max-top-level-suites](max-top-level-suites.md) - limit the number of top-level suites in a single file +* [no-nested-tests](no-nested-tests.md) - disallow tests to be nested within other tests diff --git a/docs/rules/no-nested-tests.md b/docs/rules/no-nested-tests.md new file mode 100644 index 0000000..2c78aa6 --- /dev/null +++ b/docs/rules/no-nested-tests.md @@ -0,0 +1,47 @@ +# Disallow tests to be nested within other tests (no-nested-tests) + +Test cases in mocha can be either global or within a suite but they can’t be nested within other tests. Unfortunately there is nothing stopping you from creating a test case within another test case but mocha will simply ignore those tests. + +```js +it('something', function () { + it('should work', function () { + assert(fasle); + }); +}); +``` +Something like this could be easily happen by accident where the outer test case was actually meant to be a suite instead of a test. +This rule reports such nested test cases in order to prevent problems where those nested tests are skipped silently. + +## Rule Details + +This rule looks for all test cases (`it`, `specify` and `test`) or suites (`describe`, `context` and `suite`) which are nested within another test case. + +The following patterns are considered problems: + +```js +it('something', function () { + it('should work', function () {}); +}); + +test('something', function () { + specify('should work', function () {}); +}); + +it('something', function () { + describe('other thing', function () { + // … + }); +}); + +``` + +These patterns would not be considered problems: + +```js +it('should work', function () {}); +it('should work too', function () {}); + +describe('something', function () { + it('should work', function () {}); +}); +``` diff --git a/index.js b/index.js index bc75223..9d776b0 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,8 @@ module.exports = { 'no-sibling-hooks': require('./lib/rules/no-sibling-hooks'), 'no-top-level-hooks': require('./lib/rules/no-top-level-hooks'), 'no-identical-title': require('./lib/rules/no-identical-title'), - 'max-top-level-suites': require('./lib/rules/max-top-level-suites') + 'max-top-level-suites': require('./lib/rules/max-top-level-suites'), + 'no-nested-tests': require('./lib/rules/no-nested-tests') }, configs: { recommended: { diff --git a/lib/rules/no-nested-tests.js b/lib/rules/no-nested-tests.js new file mode 100644 index 0000000..84fddef --- /dev/null +++ b/lib/rules/no-nested-tests.js @@ -0,0 +1,38 @@ +'use strict'; + +var astUtils = require('../util/ast'); + +module.exports = function noNestedTests(context) { + var testNestingLevel = 0; + + function report(callExpression, isTestCase) { + var message = isTestCase ? 'Unexpected test nested within another test.' : + 'Unexpected suite nested within a test.'; + + context.report({ + message: message, + node: callExpression.callee + }); + } + + return { + CallExpression: function (node) { + var isTestCase = astUtils.isTestCase(node), + isDescribe = astUtils.isDescribe(node); + + if (testNestingLevel > 0 && (isTestCase || isDescribe)) { + report(node, isTestCase); + } + + if (isTestCase) { + testNestingLevel += 1; + } + }, + + 'CallExpression:exit': function (node) { + if (astUtils.isTestCase(node)) { + testNestingLevel -= 1; + } + } + }; +}; diff --git a/lib/util/ast.js b/lib/util/ast.js index 50b47bb..d3fd5dd 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -6,7 +6,9 @@ var describeAliases = [ 'describe', 'xdescribe', 'describe.only', 'describe.skip 'context', 'xcontext', 'context.only', 'context.skip', 'suite', 'xsuite', 'suite.only', 'suite.skip' ], hooks = [ 'before', 'after', 'beforeEach', 'afterEach' ], - testCaseNames = [ 'it', 'it.only', 'test', 'test.only', 'specify', 'specify.only' ]; + testCaseNames = [ 'it', 'it.only', 'it.skip', + 'test', 'test.only', 'test.skip', + 'specify', 'specify.only', 'specify.skip' ]; function getNodeName(node) { if (node.type === 'MemberExpression') { diff --git a/test/rules/no-nested-tests.js b/test/rules/no-nested-tests.js new file mode 100644 index 0000000..a3c126c --- /dev/null +++ b/test/rules/no-nested-tests.js @@ -0,0 +1,109 @@ +'use strict'; + +var RuleTester = require('eslint').RuleTester, + rule = require('../../lib/rules/no-nested-tests'), + ruleTester = new RuleTester(); + +ruleTester.run('no-nested-tests', rule, { + valid: [ + 'it()', + 'it(); it(); it()', + 'describe("", function () { it(); })', + 'describe("", function () { describe("", function () { it(); }); it(); })' + ], + + invalid: [ + { + code: 'it("", function () { it() });', + errors: [ { + message: 'Unexpected test nested within another test.', + line: 1, + column: 22 + } ] + }, + { + code: 'it.only("", function () { it() });', + errors: [ { + message: 'Unexpected test nested within another test.', + line: 1, + column: 27 + } ] + }, + { + code: 'it.skip("", function () { it() });', + errors: [ { + message: 'Unexpected test nested within another test.', + line: 1, + column: 27 + } ] + }, + { + code: 'test("", function () { it() });', + errors: [ { + message: 'Unexpected test nested within another test.', + line: 1, + column: 24 + } ] + }, + { + code: 'specify("", function () { it() });', + errors: [ { + message: 'Unexpected test nested within another test.', + line: 1, + column: 27 + } ] + }, + { + code: 'it("", function () { describe() });', + errors: [ { + message: 'Unexpected suite nested within a test.', + line: 1, + column: 22 + } ] + }, + { + code: 'it("", function () { context() });', + errors: [ { + message: 'Unexpected suite nested within a test.', + line: 1, + column: 22 + } ] + }, + { + code: 'it("", function () { suite() });', + errors: [ { + message: 'Unexpected suite nested within a test.', + line: 1, + column: 22 + } ] + }, + { + code: 'it("", function () { describe.skip() });', + errors: [ { + message: 'Unexpected suite nested within a test.', + line: 1, + column: 22 + } ] + }, + { + code: 'it("", function () { describe("", function () { it(); it(); }); });', + errors: [ + { + message: 'Unexpected suite nested within a test.', + line: 1, + column: 22 + }, + { + message: 'Unexpected test nested within another test.', + line: 1, + column: 49 + }, + { + message: 'Unexpected test nested within another test.', + line: 1, + column: 55 + } + ] + } + ] +});