├── .eslintignore ├── .gitignore ├── .npmignore ├── test ├── .eslintrc ├── rules │ ├── no-pending-tests.js │ ├── no-hooks.js │ ├── valid-suite-description.js │ ├── no-top-level-hooks.js │ ├── valid-test-description.js │ ├── no-synchronous-tests.js │ ├── no-nested-tests.js │ ├── no-mocha-arrows.js │ ├── no-global-tests.js │ ├── no-sibling-hooks.js │ ├── handle-done-callback.js │ ├── no-skipped-tests.js │ ├── no-return-and-callback.js │ ├── no-exclusive-tests.js │ ├── no-identical-title.js │ ├── max-top-level-suites.js │ └── no-hooks-for-single-case.js └── index.js ├── .editorconfig ├── .travis.yml ├── .istanbul.yml ├── lib ├── rules │ ├── no-hooks.js │ ├── no-pending-tests.js │ ├── valid-suite-description.js │ ├── valid-test-description.js │ ├── no-top-level-hooks.js │ ├── no-nested-tests.js │ ├── no-global-tests.js │ ├── no-sibling-hooks.js │ ├── max-top-level-suites.js │ ├── no-exclusive-tests.js │ ├── no-identical-title.js │ ├── no-hooks-for-single-case.js │ ├── no-synchronous-tests.js │ ├── handle-done-callback.js │ ├── no-return-and-callback.js │ ├── no-mocha-arrows.js │ └── no-skipped-tests.js └── util │ ├── settings.js │ └── ast.js ├── LICENSE ├── docs └── rules │ ├── no-mocha-arrows.md │ ├── no-pending-tests.md │ ├── no-global-tests.md │ ├── max-top-level-suites.md │ ├── no-synchronous-tests.md │ ├── no-hooks.md │ ├── no-top-level-hooks.md │ ├── no-nested-tests.md │ ├── no-return-and-callback.md │ ├── README.md │ ├── valid-test-description.md │ ├── no-identical-title.md │ ├── valid-suite-description.md │ ├── handle-done-callback.md │ ├── no-sibling-hooks.md │ ├── no-hooks-for-single-case.md │ ├── no-exclusive-tests.md │ └── no-skipped-tests.md ├── index.js ├── README.md ├── package.json ├── .eslintrc └── CHANGELOG.md /.eslintignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | build/** 3 | node_modules/** 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | .idea/ 4 | *.log 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | build/ 3 | docs/ 4 | test/ 5 | .DS_Store 6 | .travis.yml 7 | *.iml 8 | .idea/ 9 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | 6 | "rules": { 7 | "max-nested-callbacks": [ 2, 8 ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4" 5 | - "5" 6 | - "6" 7 | - "7" 8 | notifications: 9 | email: false 10 | after_success: 11 | - npm run coveralls 12 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | root: . 3 | default-excludes: true 4 | excludes: 5 | - '.idea/**' 6 | - 'build/**' 7 | embed-source: false 8 | complete-copy: false 9 | include-all-sources: true 10 | reporting: 11 | print: summary 12 | reporty: 13 | - lcov 14 | dir: ./build/coverage 15 | watermarks: 16 | statements: [99, 100] 17 | lines: [99, 100] 18 | functions: [99, 100] 19 | branches: [99, 100] 20 | -------------------------------------------------------------------------------- /lib/rules/no-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtil = require('../util/ast'); 4 | 5 | module.exports = function (context) { 6 | return { 7 | CallExpression: function (node) { 8 | if (astUtil.isHookIdentifier(node.callee)) { 9 | context.report({ 10 | node: node.callee, 11 | message: 'Unexpected use of Mocha `' + node.callee.name + '` hook' 12 | }); 13 | } 14 | } 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /lib/util/settings.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node*/ 2 | 3 | 'use strict'; 4 | 5 | function settingFor(propertyName) { 6 | return function (settings) { 7 | var value = settings['mocha/' + propertyName], 8 | mochaSettings = settings.mocha || {}; 9 | 10 | return value || mochaSettings[propertyName] || []; 11 | }; 12 | } 13 | 14 | module.exports = { 15 | getAdditionalTestFunctions: settingFor('additionalTestFunctions'), 16 | getAdditionalXFunctions: settingFor('additionalXFunctions') 17 | }; 18 | -------------------------------------------------------------------------------- /lib/rules/no-pending-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (context) { 4 | var mochaTestFunctionNames = [ 5 | 'it', 6 | 'test', 7 | 'specify' 8 | ]; 9 | 10 | function isMochaTest(callee) { 11 | return callee.type === 'Identifier' && 12 | mochaTestFunctionNames.indexOf(callee.name) !== -1; 13 | } 14 | 15 | function isPendingMochaTest(node) { 16 | return isMochaTest(node.callee) && 17 | node.arguments.length === 1 && 18 | node.arguments[0].type === 'Literal'; 19 | } 20 | 21 | return { 22 | CallExpression: function (node) { 23 | if (node.callee && isPendingMochaTest(node)) { 24 | context.report({ 25 | node: node, 26 | message: 'Unexpected pending mocha test.' 27 | }); 28 | } 29 | } 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /lib/rules/valid-suite-description.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @fileoverview Match suite descriptions to match a pre-configured regular expression 5 | * @author Alexander Afanasyev 6 | */ 7 | 8 | var defaultSuiteNames = [ 'describe', 'context', 'suite' ]; 9 | 10 | module.exports = function (context) { 11 | var pattern = new RegExp(context.options[0]), 12 | suiteNames = context.options[1] ? context.options[1] : defaultSuiteNames; 13 | 14 | return { 15 | CallExpression: function (node) { 16 | var callee = node.callee, 17 | args; 18 | 19 | if (callee && callee.name && suiteNames.indexOf(callee.name) > -1) { 20 | args = node.arguments; 21 | if (args && args[0] && typeof args[0].value === 'string' && !pattern.test(args[0].value)) { 22 | context.report(node, 'Invalid "' + callee.name + '()" description found.'); 23 | } 24 | } 25 | } 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/rules/valid-test-description.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @fileoverview Match test descriptions to match a pre-configured regular expression 5 | * @author Alexander Afanasyev 6 | */ 7 | 8 | var defaultTestNames = [ 'it', 'test', 'specify' ]; 9 | 10 | module.exports = function (context) { 11 | var pattern = context.options[0] ? new RegExp(context.options[0]) : /^should/, 12 | testNames = context.options[1] ? context.options[1] : defaultTestNames; 13 | 14 | return { 15 | CallExpression: function (node) { 16 | var callee = node.callee, 17 | args; 18 | 19 | if (callee && callee.name && testNames.indexOf(callee.name) > -1) { 20 | args = node.arguments; 21 | if (args && args[0] && typeof args[0].value === 'string' && !pattern.test(args[0].value)) { 22 | context.report(node, 'Invalid "' + callee.name + '()" description found.'); 23 | } 24 | } 25 | } 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/rules/no-top-level-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtil = require('../util/ast'); 4 | 5 | module.exports = function (context) { 6 | var testSuiteStack = []; 7 | 8 | return { 9 | CallExpression: function (node) { 10 | if (astUtil.isDescribe(node)) { 11 | testSuiteStack.push(node); 12 | return; 13 | } 14 | 15 | if (!astUtil.isHookIdentifier(node.callee)) { 16 | return; 17 | } 18 | 19 | if (testSuiteStack.length === 0) { 20 | context.report({ 21 | node: node.callee, 22 | message: 'Unexpected use of Mocha `' + node.callee.name + '` hook outside of a test suite' 23 | }); 24 | } 25 | }, 26 | 27 | 'CallExpression:exit': function (node) { 28 | if (testSuiteStack[testSuiteStack.length - 1] === node) { 29 | testSuiteStack.pop(); 30 | } 31 | } 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mathias Schreck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/rules/no-nested-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtils = require('../util/ast'); 4 | 5 | module.exports = function noNestedTests(context) { 6 | var testNestingLevel = 0; 7 | 8 | function report(callExpression, isTestCase) { 9 | var message = isTestCase ? 'Unexpected test nested within another test.' : 10 | 'Unexpected suite nested within a test.'; 11 | 12 | context.report({ 13 | message: message, 14 | node: callExpression.callee 15 | }); 16 | } 17 | 18 | return { 19 | CallExpression: function (node) { 20 | var isTestCase = astUtils.isTestCase(node), 21 | isDescribe = astUtils.isDescribe(node); 22 | 23 | if (testNestingLevel > 0 && (isTestCase || isDescribe)) { 24 | report(node, isTestCase); 25 | } 26 | 27 | if (isTestCase) { 28 | testNestingLevel += 1; 29 | } 30 | }, 31 | 32 | 'CallExpression:exit': function (node) { 33 | if (astUtils.isTestCase(node)) { 34 | testNestingLevel -= 1; 35 | } 36 | } 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /test/rules/no-pending-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(), 6 | expectedErrorMessage = 'Unexpected pending mocha test.'; 7 | 8 | ruleTester.run('no-pending-tests', rules['no-pending-tests'], { 9 | 10 | valid: [ 11 | 'it()', 12 | 'it("should be false", function() { assert(something, false); })', 13 | 'test()', 14 | 'test("should be false", function() { assert(something, false); })', 15 | 'specify()', 16 | 'specify("should be false", function() { assert(something, false); })', 17 | 'something.it()', 18 | 'something.it("test")' 19 | ], 20 | 21 | invalid: [ 22 | { 23 | code: 'it("is pending")', 24 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 25 | }, 26 | { 27 | code: 'test("is pending")', 28 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 29 | }, 30 | { 31 | code: 'specify("is pending")', 32 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 33 | } 34 | ] 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /docs/rules/no-mocha-arrows.md: -------------------------------------------------------------------------------- 1 | # Disallow Arrow Functions as Arguments to Mocha Functions (no-mocha-arrows) 2 | 3 | Mocha [discourages](http://mochajs.org/#arrow-functions) passing it arrow functions as arguments. This rule prevents their use on the Mocha globals. 4 | 5 | ## Rule Details 6 | 7 | This rule looks for occurrences of the Mocha functions (`describe`, `it`, `beforeEach`, etc.) within the source code. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | it(() => { assert(something, false); }) 13 | it("should be false", () => { assert(something, false); }) 14 | beforeEach(() => { doSomething(); }) 15 | beforeEach((done) => { doSomething(); done(); }) 16 | ``` 17 | 18 | These patterns would not be considered warnings: 19 | 20 | ```js 21 | it() 22 | it(function() { assert(something, false); }) 23 | it("should be false", function() { assert(something, false); }) 24 | ``` 25 | 26 | This does not check usage of the [`require` interface](http://mochajs.org/#require) for Mocha, only the globals. 27 | 28 | ## When Not To Use It 29 | 30 | * If you want to pass arrow functions to mocha, turn this rule off. 31 | * If you have other globals which share names with mocha globals, you should turn this rule off, because it would raise warnings. 32 | -------------------------------------------------------------------------------- /lib/rules/no-global-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (context) { 4 | var testFunctionNames = [ 5 | 'it', 6 | 'it.only', 7 | 'it.skip', 8 | 'test', 9 | 'test.only', 10 | 'test.skip', 11 | 'specify', 12 | 'specify.only', 13 | 'specify.skip' 14 | ]; 15 | 16 | function getFnName(callee) { 17 | if (callee.type === 'MemberExpression') { 18 | if (callee.computed) { 19 | return callee.object.name + '.' + callee.property.value; 20 | } 21 | 22 | return callee.object.name + '.' + callee.property.name; 23 | } 24 | 25 | return callee.name; 26 | } 27 | 28 | function isGlobalScope(scope) { 29 | return scope.type === 'global' || scope.type === 'module'; 30 | } 31 | 32 | return { 33 | CallExpression: function (node) { 34 | var callee = node.callee, 35 | fnName = getFnName(callee), 36 | scope = context.getScope(); 37 | 38 | if (testFunctionNames.indexOf(fnName) !== -1 && isGlobalScope(scope)) { 39 | context.report(callee, 'Unexpected global mocha test.'); 40 | } 41 | } 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /docs/rules/no-pending-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow Pending Tests (no-pending-tests) 2 | 3 | Mocha allows specification of pending tests, which represent tests that aren't yet implemented, but are intended to be implemented eventually. These are designated like a normal mocha test, but with only the first argument provided (no callback for the actual implementation). For example: `it('unimplemented test');` 4 | 5 | This rule allows you to raise ESLint warnings or errors on pending tests. This can be useful, for example, for reminding developers that pending tests exist in the repository, so they're more likely to get implemented. 6 | 7 | ## Rule Details 8 | 9 | This rule looks for `it`, `test`, and `specify` function calls with only one argument, where the argument is a string literal. 10 | 11 | The following patterns are considered warnings: 12 | 13 | ```js 14 | it("foo"); 15 | specify("foo"); 16 | test("foo"); 17 | ``` 18 | 19 | These patterns are not considered warnings: 20 | 21 | ```js 22 | it("foo", function() {}); 23 | specify("foo", function() {}); 24 | test("foo", function() {}); 25 | ``` 26 | 27 | ## When Not To Use It 28 | 29 | * If the existence of pending/unimplemented tests isn't considered important enough to warrant raising lint warnings/errors. 30 | 31 | ## Further Reading 32 | 33 | * [Pending Tests](http://mochajs.org/#pending-tests) 34 | -------------------------------------------------------------------------------- /docs/rules/no-global-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow global tests (no-global-tests) 2 | 3 | Mocha gives you the possibility to structure your tests inside of suites using `describe`, `suite` or `context`. 4 | 5 | Example: 6 | 7 | ```js 8 | describe('something', function () { 9 | it('should work', function () {}); 10 | }); 11 | ``` 12 | 13 | This rule aims to prevent writing tests outside of test-suites: 14 | 15 | ```js 16 | it('should work', function () {}); 17 | ``` 18 | 19 | ## Rule Details 20 | 21 | This rule checks each mocha test function to not be located directly in the global scope. 22 | 23 | The following patterns are considered problems: 24 | 25 | ```js 26 | it('foo'); 27 | 28 | test('bar'); 29 | 30 | it.only('foo'); 31 | 32 | test.skip('bar'); 33 | ``` 34 | 35 | These patterns would not be considered problems: 36 | 37 | ```js 38 | describe('foo', function () { 39 | it('bar'); 40 | }); 41 | 42 | suite('foo', function () { 43 | test('bar'); 44 | }); 45 | ``` 46 | 47 | ## Caveats 48 | 49 | It is not always possible to statically detect in which scope a mocha test function will be used at runtime. 50 | For example imagine a parameterized test which is generated inside of a `forEach` callback: 51 | 52 | ```js 53 | function parameterizedTest(params) { 54 | params.forEach(function (i) { 55 | it('should work with ' + i, function () {}); 56 | }); 57 | } 58 | 59 | parameterizedTest([1,2,3]); 60 | ``` 61 | -------------------------------------------------------------------------------- /lib/rules/no-sibling-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtil = require('../util/ast'); 4 | 5 | function newDescribeLayer(describeNode) { 6 | return { 7 | describeNode: describeNode, 8 | before: false, 9 | after: false, 10 | beforeEach: false, 11 | afterEach: false 12 | }; 13 | } 14 | 15 | module.exports = function (context) { 16 | var isUsed = []; 17 | 18 | return { 19 | Program: function (node) { 20 | isUsed.push(newDescribeLayer(node)); 21 | }, 22 | 23 | CallExpression: function (node) { 24 | var name = node.callee && node.callee.name; 25 | if (astUtil.isDescribe(node)) { 26 | isUsed.push(newDescribeLayer(node)); 27 | return; 28 | } 29 | 30 | if (!astUtil.isHookIdentifier(node.callee)) { 31 | return; 32 | } 33 | 34 | if (isUsed[isUsed.length - 1][name]) { 35 | context.report({ 36 | node: node.callee, 37 | message: 'Unexpected use of duplicate Mocha `' + name + '` hook' 38 | }); 39 | } 40 | 41 | isUsed[isUsed.length - 1][name] = true; 42 | }, 43 | 44 | 'CallExpression:exit': function (node) { 45 | if (isUsed[isUsed.length - 1].describeNode === node) { 46 | isUsed.pop(); 47 | } 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /lib/rules/max-top-level-suites.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @fileoverview Limit the number of top-level suites in a single file 5 | * @author Alexander Afanasyev 6 | */ 7 | 8 | var R = require('ramda'), 9 | astUtil = require('../util/ast'), 10 | defaultSuiteLimit = 1; 11 | 12 | module.exports = function (context) { 13 | var stack = [], 14 | topLevelDescribes = [], 15 | options = context.options[0] || {}, 16 | suiteLimit; 17 | 18 | if (R.isNil(options.limit)) { 19 | suiteLimit = defaultSuiteLimit; 20 | } else { 21 | suiteLimit = options.limit; 22 | } 23 | 24 | return { 25 | CallExpression: function (node) { 26 | if (astUtil.isDescribe(node)) { 27 | stack.push(node); 28 | } 29 | }, 30 | 31 | 'CallExpression:exit': function (node) { 32 | if (astUtil.isDescribe(node)) { 33 | if (stack.length === 1) { 34 | topLevelDescribes.push(node); 35 | } 36 | 37 | stack.pop(node); 38 | } 39 | }, 40 | 41 | 'Program:exit': function () { 42 | if (topLevelDescribes.length > suiteLimit) { 43 | context.report({ 44 | node: topLevelDescribes[suiteLimit], 45 | message: 'The number of top-level suites is more than ' + suiteLimit + '.' 46 | }); 47 | } 48 | } 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /docs/rules/max-top-level-suites.md: -------------------------------------------------------------------------------- 1 | # Limit the number of top-level suites in a single file (max-top-level-suites) 2 | 3 | This rule enforces having a limited amount of top-level suites in a file - by default a single suite per file is allowed. 4 | 5 | Multiple `describe` blocks is often a sign that a test should be broken down to multiple files. 6 | One of the possible problems if having multiple suites is that, if you are, for example, going through tests one by one and focusing them (using `describe.only`), you might not notice `describe` block(s) that are down there at the bottom of a file which may lead to tests being unintentionally skipped. 7 | 8 | ## Rule Details 9 | 10 | The rule supports "describe", "context" and "suite" suite function names and different valid suite name prefixes like "skip" or "only". 11 | 12 | The following patterns are considered warnings: 13 | 14 | ```js 15 | describe('foo', function () { 16 | it('should do foo', function() {}); 17 | }); 18 | 19 | describe('bar', function() { 20 | it('should do bar', function() {}); 21 | }); 22 | ``` 23 | 24 | These patterns would not be considered warnings: 25 | 26 | ```js 27 | describe('foo', function () { 28 | it('should do foo', function() {}); 29 | 30 | describe('bar', function() { 31 | it('should do bar', function() {}); 32 | }); 33 | }); 34 | ``` 35 | 36 | If you want to change the suite limit to, for instance, 2 suites per file: 37 | 38 | ```js 39 | rules: { 40 | "mocha/max-top-level-suites": ["warning", {limit: 2}] 41 | }, 42 | ``` 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | 'no-exclusive-tests': require('./lib/rules/no-exclusive-tests'), 6 | 'no-pending-tests': require('./lib/rules/no-pending-tests'), 7 | 'no-skipped-tests': require('./lib/rules/no-skipped-tests'), 8 | 'handle-done-callback': require('./lib/rules/handle-done-callback'), 9 | 'no-synchronous-tests': require('./lib/rules/no-synchronous-tests'), 10 | 'no-global-tests': require('./lib/rules/no-global-tests'), 11 | 'no-return-and-callback': require('./lib/rules/no-return-and-callback'), 12 | 'valid-test-description': require('./lib/rules/valid-test-description'), 13 | 'valid-suite-description': require('./lib/rules/valid-suite-description'), 14 | 'no-mocha-arrows': require('./lib/rules/no-mocha-arrows'), 15 | 'no-hooks': require('./lib/rules/no-hooks'), 16 | 'no-hooks-for-single-case': require('./lib/rules/no-hooks-for-single-case'), 17 | 'no-sibling-hooks': require('./lib/rules/no-sibling-hooks'), 18 | 'no-top-level-hooks': require('./lib/rules/no-top-level-hooks'), 19 | 'no-identical-title': require('./lib/rules/no-identical-title'), 20 | 'max-top-level-suites': require('./lib/rules/max-top-level-suites'), 21 | 'no-nested-tests': require('./lib/rules/no-nested-tests') 22 | }, 23 | configs: { 24 | recommended: { 25 | rules: { 26 | 'mocha/no-exclusive-tests': 2 27 | } 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /docs/rules/no-synchronous-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow Synchronous Tests (no-synchronous-tests) 2 | 3 | Mocha automatically determines whether a test is synchronous or asynchronous based on the arity of the function passed into it. When writing tests for a asynchronous function, omitting the `done` callback or forgetting to return a promise can often lead to false-positive test cases. This rule warns against the implicit synchronous feature, and should be combined with `handle-done-callback` for best results. 4 | 5 | ## Rule Details 6 | 7 | This rule looks for either an asynchronous callback or a return statement within the function body of any mocha test statement. If the mocha callback is not used and a promise is not returned, this rule will raise a warning. 8 | 9 | ### Caveats: 10 | 11 | This rule cannot guarantee that a returned function call is actually a promise, it only confirms that the return was made. 12 | 13 | If a dynamic function is passed into the test call, it cannot be inspected because the function is only defined at runtime. Example: 14 | 15 | ```js 16 | var myTestFn = function(){ 17 | // it cannot verify this 18 | } 19 | it('test name', myTestFn); 20 | ``` 21 | 22 | ## When Not To Use It 23 | 24 | * If you are primarily writing synchronous tests, and rarely need the `done` callback or promise functionality. 25 | 26 | ## Further Reading 27 | 28 | * [Synchronous Code](http://mochajs.org/#synchronous-code) 29 | * [Asynchronous Code](http://mochajs.org/#asynchronous-code) 30 | * [Working with Promises](http://mochajs.org/#working-with-promises) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM Version](https://img.shields.io/npm/v/eslint-plugin-mocha.svg?style=flat)](https://www.npmjs.org/package/eslint-plugin-mocha) 2 | [![Build Status](https://img.shields.io/travis/lo1tuma/eslint-plugin-mocha/master.svg?style=flat)](https://travis-ci.org/lo1tuma/eslint-plugin-mocha) 3 | [![Coverage Status](https://img.shields.io/coveralls/lo1tuma/eslint-plugin-mocha/master.svg?style=flat)](https://coveralls.io/r/lo1tuma/eslint-plugin-mocha) 4 | [![Peer Dependencies](http://img.shields.io/david/peer/lo1tuma/eslint-plugin-mocha.svg?style=flat)](https://david-dm.org/lo1tuma/eslint-plugin-mocha#info=peerDependencies&view=table) 5 | [![NPM Downloads](https://img.shields.io/npm/dm/eslint-plugin-mocha.svg?style=flat)](https://www.npmjs.org/package/eslint-plugin-mocha) 6 | 7 | # eslint-plugin-mocha 8 | 9 | ESLint rules for [mocha](http://mochajs.org/). 10 | 11 | ## Install and configure 12 | 13 | This plugin requires ESLint `2.0.0` or later. 14 | 15 | `npm install --save-dev eslint-plugin-mocha` 16 | 17 | Then add a reference to this plugin and selected rules in your eslint config: 18 | 19 | ```json 20 | { 21 | "plugins": [ 22 | "mocha" 23 | ], 24 | "rules": { 25 | "mocha/no-exclusive-tests": "error" 26 | } 27 | } 28 | ``` 29 | See [Configuring Eslint](http://eslint.org/docs/user-guide/configuring) on [eslint.org](http://eslint.org) for more info. 30 | 31 | ## Rules documentation 32 | 33 | The documentation of the rules can be found [here](docs/rules). 34 | 35 | ## When Not To Use It 36 | 37 | If you are not using mocha you should not use this plugin. 38 | -------------------------------------------------------------------------------- /docs/rules/no-hooks.md: -------------------------------------------------------------------------------- 1 | # Disallow hooks (no-hooks) 2 | 3 | Mocha proposes hooks that allow code to be run before or after every or all tests. This helps define a common setup or teardown process for every test. The use of these hooks promotes the use of shared state between the tests, and defeats the purpose of having isolated unit tests. 4 | 5 | ## Rule Details 6 | 7 | This rule looks for every call to `before`, `after`, `beforeEach` and `afterEach`. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | describe('foo', function () { 13 | var mockUser; 14 | 15 | before(function () { // Not allowed 16 | mockUser = {age: 50}; 17 | }); 18 | 19 | after(function () { /* ... */ }); // Not allowed 20 | beforeEach(function () { /* ... */ }); // Not allowed 21 | afterEach(function () { /* ... */ }); // Not allowed 22 | 23 | it(function () { 24 | assert.equals(lib.method(mockUser), 'expectedValue'); 25 | }); 26 | }); 27 | ``` 28 | 29 | These patterns would not be considered warnings: 30 | 31 | ```js 32 | function createFixtures() { 33 | return { 34 | mockUser: {age: 50} 35 | }; 36 | } 37 | 38 | describe('foo', function () { 39 | it(function () { 40 | var fixtures = createFixtures(); 41 | assert.equals(lib.method(fixtures.mockUser), 'expectedValue'); 42 | }); 43 | }); 44 | ``` 45 | 46 | ## When Not To Use It 47 | 48 | * If you use another library which exposes a similar API as mocha (e.g. `before`, `after`), you should turn this rule off, because it would raise warnings. 49 | -------------------------------------------------------------------------------- /docs/rules/no-top-level-hooks.md: -------------------------------------------------------------------------------- 1 | # Disallow top-level hooks (no-top-level-hooks) 2 | 3 | Mocha proposes hooks that allow code to be run before or after every or all tests. This helps define a common setup or teardown process for every test. 4 | These hooks should only be declared inside test suites, as they would otherwise be run before or after every test or test suite of the project, even if the test suite of the file they were declared in was skipped. This can lead to very confusing and unwanted effects. 5 | 6 | ## Rule Details 7 | 8 | This rule looks for every call to `before`, `after`, `beforeEach` and `afterEach` that are not in a test suite. 9 | 10 | The following patterns are considered warnings: 11 | 12 | ```js 13 | before(function () { /* ... */ }); // Not allowed 14 | after(function () { /* ... */ }); // Not allowed 15 | beforeEach(function () { /* ... */ }); // Not allowed 16 | afterEach(function () { /* ... */ }); // Not allowed 17 | ``` 18 | 19 | These patterns would not be considered warnings: 20 | 21 | ```js 22 | describe('foo', function () { 23 | before(function () { /* ... */ }); 24 | after(function () { /* ... */ }); 25 | beforeEach(function () { /* ... */ }); 26 | afterEach(function () { /* ... */ }); 27 | }); 28 | ``` 29 | 30 | ## When Not To Use It 31 | 32 | * If you use another library which exposes a similar API as mocha (e.g. `before`, `after`), you should turn this rule off, because it would raise warnings. 33 | * If you turned `no-hooks` on, you should turn this rule off, because it would raise several warnings for the same root cause. 34 | -------------------------------------------------------------------------------- /docs/rules/no-nested-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow tests to be nested within other tests (no-nested-tests) 2 | 3 | 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. 4 | 5 | ```js 6 | it('something', function () { 7 | it('should work', function () { 8 | assert(fasle); 9 | }); 10 | }); 11 | ``` 12 | 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. 13 | This rule reports such nested test cases in order to prevent problems where those nested tests are skipped silently. 14 | 15 | ## Rule Details 16 | 17 | This rule looks for all test cases (`it`, `specify` and `test`) or suites (`describe`, `context` and `suite`) which are nested within another test case. 18 | 19 | The following patterns are considered problems: 20 | 21 | ```js 22 | it('something', function () { 23 | it('should work', function () {}); 24 | }); 25 | 26 | test('something', function () { 27 | specify('should work', function () {}); 28 | }); 29 | 30 | it('something', function () { 31 | describe('other thing', function () { 32 | // … 33 | }); 34 | }); 35 | 36 | ``` 37 | 38 | These patterns would not be considered problems: 39 | 40 | ```js 41 | it('should work', function () {}); 42 | it('should work too', function () {}); 43 | 44 | describe('something', function () { 45 | it('should work', function () {}); 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /lib/util/ast.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node*/ 2 | 3 | 'use strict'; 4 | 5 | var describeAliases = [ 'describe', 'xdescribe', 'describe.only', 'describe.skip', 6 | 'context', 'xcontext', 'context.only', 'context.skip', 7 | 'suite', 'xsuite', 'suite.only', 'suite.skip' ], 8 | hooks = [ 'before', 'after', 'beforeEach', 'afterEach' ], 9 | testCaseNames = [ 'it', 'it.only', 'it.skip', 10 | 'test', 'test.only', 'test.skip', 11 | 'specify', 'specify.only', 'specify.skip' ]; 12 | 13 | function getPropertyName(property) { 14 | return property.name || property.value; 15 | } 16 | 17 | function getNodeName(node) { 18 | if (node.type === 'MemberExpression') { 19 | return getNodeName(node.object) + '.' + getPropertyName(node.property); 20 | } 21 | return node.name; 22 | } 23 | 24 | function isDescribe(node) { 25 | return node 26 | && node.type === 'CallExpression' 27 | && describeAliases.indexOf(getNodeName(node.callee)) > -1; 28 | } 29 | 30 | function isHookIdentifier(node) { 31 | return node 32 | && node.type === 'Identifier' 33 | && hooks.indexOf(node.name) !== -1; 34 | } 35 | 36 | function isTestCase(node) { 37 | return node 38 | && node.type === 'CallExpression' 39 | && testCaseNames.indexOf(getNodeName(node.callee)) > -1; 40 | } 41 | 42 | module.exports = { 43 | isDescribe: isDescribe, 44 | isHookIdentifier: isHookIdentifier, 45 | isTestCase: isTestCase, 46 | getPropertyName: getPropertyName, 47 | getNodeName: getNodeName 48 | }; 49 | -------------------------------------------------------------------------------- /docs/rules/no-return-and-callback.md: -------------------------------------------------------------------------------- 1 | # Disallow returning in a test or hook function that uses a callback (no-return-and-callback) 2 | 3 | Mocha's tests or hooks (like `before`) may be asynchronous by either returning a Promise or specifying a callback parameter for the function. It can be confusing to have both methods used in a test or hook, and from Mocha v3 on, causes the test to fail in order to force developers to remove this source of confusion. 4 | 5 | ## Rule Details 6 | 7 | This rule looks for every test and hook (`before`, `after`, `beforeEach` and `afterEach`) and reports when the function takes a parameter and returns a value. Returning a non-Promise value is fine from Mocha's perspective, though it is ignored, but helps the linter catch more error cases. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | describe('suite', function () { 13 | before('title', function(done) { 14 | return foo(done); 15 | }); 16 | 17 | it('title', function(done) { 18 | return bar().then(function() { 19 | done(); 20 | }); 21 | }); 22 | }); 23 | ``` 24 | 25 | These patterns would not be considered warnings: 26 | 27 | ```js 28 | describe('suite', function () { 29 | before('title', function(done) { 30 | foo(done); 31 | }); 32 | 33 | it('title', function() { 34 | return bar(); 35 | }); 36 | }); 37 | ``` 38 | 39 | ## When Not To Use It 40 | 41 | * If you use another library which exposes a similar API as mocha (e.g. `before`, `after`), you should turn this rule off, because it would raise warnings. 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-mocha", 3 | "version": "4.8.0", 4 | "description": "Eslint rules for mocha.", 5 | "main": "index.js", 6 | "scripts": { 7 | "pretest": "eslint .", 8 | "test": "npm run test:unit --coverage && npm run check-coverage", 9 | "test:unit": "istanbul test _mocha test -- --recursive --reporter dot", 10 | "check-coverage": "istanbul check-coverage --statement 100 --branch 100 --function 100 --lines 100", 11 | "coveralls": "cat ./build/coverage/lcov.info | coveralls", 12 | "changelog": "pr-log" 13 | }, 14 | "dependencies": { 15 | "ramda": "^0.22.1" 16 | }, 17 | "devDependencies": { 18 | "chai": "^3.5.0", 19 | "pr-log": "^1.3.0", 20 | "istanbul": "^0.4.2", 21 | "mocha": "^3.0.0", 22 | "eslint": "^3.0.0", 23 | "coveralls": "^2.11.6" 24 | }, 25 | "peerDependencies": { 26 | "eslint": "^2.0.0 || ^3.0.0" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/lo1tuma/eslint-plugin-mocha.git" 31 | }, 32 | "author": "Mathias Schreck ", 33 | "contributors": [ 34 | "Alexander Schmidt " 35 | ], 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/lo1tuma/eslint-plugin-mocha/issues" 39 | }, 40 | "homepage": "https://github.com/lo1tuma/eslint-plugin-mocha", 41 | "keywords": [ 42 | "eslint", 43 | "eslintplugin", 44 | "eslint-plugin", 45 | "mocha" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /docs/rules/README.md: -------------------------------------------------------------------------------- 1 | # Rules 2 | 3 | * [handle-done-callback](handle-done-callback.md) - enforces handling of callbacks for async tests 4 | * [max-top-level-suites](max-top-level-suites.md) - limit the number of top-level suites in a single file 5 | * [no-exclusive-tests](no-exclusive-tests.md) - disallow exclusive mocha tests 6 | * [no-global-tests](no-global-tests.md) - disallow global tests 7 | * [no-hooks](no-hooks.md) - disallow hooks 8 | * [no-hooks-for-single-case](no-hooks-for-single-case.md) - disallow hooks for a single test or test suite 9 | * [no-identical-title](no-identical-title.md) - disallow identical titles 10 | * [no-mocha-arrows](no-mocha-arrows.md) - disallow arrow functions as arguments to mocha globals 11 | * [no-nested-tests](no-nested-tests.md) - disallow tests to be nested within other tests 12 | * [no-pending-tests](no-pending-tests.md) - disallow pending/unimplemented mocha tests 13 | * [no-return-and-callback](no-return-and-callback.md) - disallow returning in a test or hook function that uses a callback 14 | * [no-sibling-hooks](no-sibling-hooks.md) - disallow duplicate uses of a hook at the same level inside a describe 15 | * [no-skipped-tests](no-skipped-tests.md) - disallow skipped mocha tests (fixable) 16 | * [no-synchronous-tests](no-synchronous-tests.md) - disallow synchronous tests 17 | * [no-top-level-hooks](no-top-level-hooks.md) - disallow top-level hooks 18 | * [valid-suite-description](valid-suite-description.md) - match suite descriptions against a pre-configured regular expression 19 | * [valid-test-description](valid-test-description.md) - match test descriptions against a pre-configured regular expression 20 | -------------------------------------------------------------------------------- /docs/rules/valid-test-description.md: -------------------------------------------------------------------------------- 1 | # Match test descriptions against a pre-configured regular expression (valid-test-description) 2 | 3 | This rule enforces the test descriptions to follow the desired format. 4 | 5 | ## Rule Details 6 | 7 | By default, the regular expression is configured to be "^should" which requires test descriptions to start with "should". 8 | By default, the rule supports "it", "specify" and "test" test function names, but it can be configured to look for different test names via rule configuration. 9 | 10 | Example of a custom rule configuration: 11 | 12 | ```js 13 | rules: { 14 | "mocha/valid-test-description": ["warning", "mypattern$", ["it", "specify", "test", "mytestname"]] 15 | }, 16 | ``` 17 | 18 | where: 19 | 20 | * `warning` is a rule error level (see [Configuring Rules](http://eslint.org/docs/user-guide/configuring#configuring-rules)) 21 | * `mypattern$` is a custom regular expression pattern to match test names against 22 | * `["it", "specify", "test", "mytestname"]` is an array of custom test names 23 | 24 | The following patterns are considered warnings (with the default rule configuration): 25 | 26 | ```js 27 | // bdd 28 | it("does something", function() { }); 29 | specify("does something", function() { }); 30 | 31 | // tdd 32 | test("does something", function() { }); 33 | ``` 34 | 35 | These patterns would not be considered warnings: 36 | 37 | ```js 38 | // bdd 39 | it("should respond to GET", function() { }); 40 | it("should do something"); 41 | specify("should respond to GET", function() { }); 42 | specify("should do something"); 43 | 44 | // tdd 45 | test("should respond to GET", function() { }); 46 | test("should do something"); 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/rules/no-identical-title.md: -------------------------------------------------------------------------------- 1 | # Disallow identical titles (no-identical-title) 2 | 3 | Having identical titles for two different tests or test suites may create confusion. For example, when a test with the same title as another test in the same test suite fails, it is harder to know which one failed and thus harder to fix. 4 | 5 | ## Rule Details 6 | 7 | This rule looks at the title of every test and test suites. It will report when two test suites or two test cases at the same level of a test suite have the same title. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | describe('foo', function () { 13 | it('should do bar', function() {}); 14 | it('should do bar', function() {}); // Has the same title as the previous test 15 | 16 | describe('baz', function() { 17 | // ... 18 | }); 19 | 20 | describe('baz', function() { // Has the same title as a previous test suite 21 | // ... 22 | }); 23 | }); 24 | ``` 25 | 26 | These patterns would not be considered warnings: 27 | 28 | ```js 29 | describe('foo', function () { 30 | it('should do foo', function() {}); 31 | it('should do bar', function() {}); 32 | 33 | // Has the same name as a parent test suite, which is fine 34 | describe('foo', function() { 35 | // Has the same name as a test in a parent test suite, which is fine 36 | it('should do foo', function() {}); 37 | it('should work', function() {}); 38 | }); 39 | 40 | describe('baz', function() { // Has the same title as a previous test suite 41 | // Has the same name as a test in a sibling test suite, which is fine 42 | it('should work', function() {}); 43 | }); 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/rules/valid-suite-description.md: -------------------------------------------------------------------------------- 1 | # Match suite descriptions against a pre-configured regular expression (valid-suite-description) 2 | 3 | This rule enforces the suite descriptions to follow the desired format. 4 | 5 | ## Rule Details 6 | 7 | By default, the regular expression is not configured and would be required if rule is enabled. 8 | By default, the rule supports "describe", "context" and "suite" suite function names, but it can be configured to look for different suite names via rule configuration. 9 | 10 | Example of a custom rule configuration: 11 | 12 | ```js 13 | rules: { 14 | "mocha/valid-suite-description": ["warning", "^[A-Z]"] 15 | }, 16 | ``` 17 | 18 | where: 19 | 20 | * `warning` is a rule error level (see [Configuring Rules](http://eslint.org/docs/user-guide/configuring#configuring-rules)) 21 | * `^[A-Z]` is a custom regular expression pattern to match suite names against; `^[A-Z]` enforces a suite name to start with an upper-case letter 22 | 23 | The following patterns are considered warnings (with the example rule configuration posted above): 24 | 25 | ```js 26 | // bdd 27 | describe("something to test", function() { }); 28 | context("something to test", function() { }); 29 | 30 | // tdd 31 | suite("something to test", function() { }); 32 | ``` 33 | 34 | These patterns would not be considered warnings: 35 | 36 | ```js 37 | // bdd 38 | describe("Test suite", function() { }); 39 | context("Test suite", function() { }); 40 | 41 | // tdd 42 | suite("Test suite", function() { }); 43 | ``` 44 | 45 | There is also possible to configure a custom list of suite names via the second rule configuration option: 46 | 47 | ```js 48 | rules: { 49 | "mocha/valid-suite-description": ["warning", "^[A-Z]", ["describe", "context", "suite", "mysuitename"]] 50 | }, 51 | ``` 52 | -------------------------------------------------------------------------------- /test/rules/no-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-hooks', rules['no-hooks'], { 8 | 9 | valid: [ 10 | 'describe(function() { it(function() {}); });', 11 | 'describe(function() { it(function() {}); it(function() {}); });', 12 | 'describe(function() { describe(function() { it(function() {}); }); });', 13 | 'foo.before()', 14 | 'foo.after()', 15 | 'foo.beforeEach()', 16 | 'foo.afterEach()', 17 | 'before.foo()', 18 | 'after.foo()', 19 | 'beforeEach.foo()', 20 | 'afterEach.foo()', 21 | 'var before = 2; before + 3;' 22 | ], 23 | 24 | invalid: [ 25 | { 26 | code: 'describe(function() { before(function() {}); });', 27 | errors: [ { message: 'Unexpected use of Mocha `before` hook', column: 23, line: 1 } ] 28 | }, 29 | { 30 | code: 'describe(function() { after(function() {}); });', 31 | errors: [ { message: 'Unexpected use of Mocha `after` hook', column: 23, line: 1 } ] 32 | }, 33 | { 34 | code: 'describe(function() { beforeEach(function() {}); });', 35 | errors: [ { message: 'Unexpected use of Mocha `beforeEach` hook', column: 23, line: 1 } ] 36 | }, 37 | { 38 | code: 'describe(function() { afterEach(function() {}); });', 39 | errors: [ { message: 'Unexpected use of Mocha `afterEach` hook', column: 23, line: 1 } ] 40 | }, 41 | { 42 | code: 'describe(function() { describe(function() { before(function() {}); }); });', 43 | errors: [ { message: 'Unexpected use of Mocha `before` hook', column: 45, line: 1 } ] 44 | } 45 | ] 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /docs/rules/handle-done-callback.md: -------------------------------------------------------------------------------- 1 | # Enforces handling of callbacks for async tests (handle-done-callback) 2 | 3 | Mocha allows you to write asynchronous tests by adding a `done` callback to the parameters of your test function. 4 | It is easy to forget calling this callback after the asynchronous operation is done. 5 | 6 | Example: 7 | 8 | ```js 9 | 10 | it('should work', function (done) { 11 | fetchData(options, function (error, data) { 12 | expect(error).not.to.be.ok; 13 | expect(data).to.deep.equal({ foo: 'bar' }); 14 | // done callback was not called 15 | }); 16 | }); 17 | ``` 18 | 19 | In this example the `done` callback was never called and test will time out. 20 | 21 | ## Rule Details 22 | 23 | This rule checks each `FunctionExpression` or `ArrowFunctionExpression` inside of `it`, `it.only`, `test`, `test.only`, `specify`, `specify.only`, `before`, `after`, `beforeEach` and `afterEach`. 24 | 25 | The following patterns are considered warnings: 26 | 27 | ```js 28 | it('foo', function (done) { }); 29 | 30 | it('foo', function (done) { 31 | asyncFunction(function (err, result) { 32 | expect(err).to.not.exist; 33 | }); 34 | }); 35 | 36 | before(function (done) { 37 | asyncInitialization(function () { 38 | initialized = true; 39 | }); 40 | }); 41 | ``` 42 | 43 | These patterns would not be considered warnings: 44 | 45 | ```js 46 | it('foo', function (done) { done(); }); 47 | 48 | it('foo', function (done) { 49 | asyncFunction(function (err, result) { 50 | expect(err).to.not.exist; 51 | done(); 52 | }); 53 | }); 54 | 55 | before(function (done) { 56 | asyncInitialization(function () { 57 | initialized = true; 58 | done(); 59 | }); 60 | }); 61 | ``` 62 | 63 | ## When Not To Use It 64 | 65 | If you don’t write asynchronous tests you can safely disable this rule. 66 | 67 | ## Further Reading 68 | 69 | * [Asynchronous test code](http://mochajs.org/#asynchronous-code) 70 | -------------------------------------------------------------------------------- /lib/rules/no-exclusive-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getAdditionalTestFunctions = require('../util/settings').getAdditionalTestFunctions, 4 | astUtils = require('../util/ast'); 5 | 6 | module.exports = function (context) { 7 | var mochaTestFunctions = [ 8 | 'it', 9 | 'describe', 10 | 'suite', 11 | 'test', 12 | 'context', 13 | 'specify' 14 | ], 15 | settings = context.settings, 16 | additionalTestFunctions = getAdditionalTestFunctions(settings); 17 | 18 | mochaTestFunctions = mochaTestFunctions.concat(additionalTestFunctions); 19 | 20 | function matchesMochaTestFunction(object) { 21 | var name = astUtils.getNodeName(object); 22 | 23 | return mochaTestFunctions.indexOf(name) !== -1; 24 | } 25 | 26 | function isPropertyNamedOnly(property) { 27 | return property && astUtils.getPropertyName(property) === 'only'; 28 | } 29 | 30 | function isCallToMochasOnlyFunction(callee) { 31 | return callee.type === 'MemberExpression' && 32 | matchesMochaTestFunction(callee.object) && 33 | isPropertyNamedOnly(callee.property); 34 | } 35 | 36 | return { 37 | CallExpression: function (node) { 38 | var callee = node.callee; 39 | 40 | if (callee && isCallToMochasOnlyFunction(callee)) { 41 | context.report({ 42 | node: callee.property, 43 | message: 'Unexpected exclusive mocha test.' 44 | }); 45 | } 46 | } 47 | }; 48 | }; 49 | 50 | module.exports.schema = [ 51 | { 52 | type: 'object', 53 | properties: { 54 | additionalTestFunctions: { 55 | type: 'array', 56 | items: { 57 | type: 'string' 58 | }, 59 | minItems: 1, 60 | uniqueItems: true 61 | } 62 | } 63 | } 64 | ]; 65 | -------------------------------------------------------------------------------- /lib/rules/no-identical-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtil = require('../util/ast'); 4 | 5 | function newLayer() { 6 | return { 7 | describeTitles: [], 8 | testTitles: [] 9 | }; 10 | } 11 | 12 | function handlTestCaseTitles(context, titles, node, title) { 13 | if (astUtil.isTestCase(node)) { 14 | if (titles.indexOf(title) !== -1) { 15 | context.report({ 16 | node: node, 17 | message: 'Test title is used multiple times in the same test suite.' 18 | }); 19 | } 20 | titles.push(title); 21 | } 22 | } 23 | 24 | function handlTestSuiteTitles(context, titles, node, title) { 25 | if (!astUtil.isDescribe(node)) { 26 | return; 27 | } 28 | if (titles.indexOf(title) !== -1) { 29 | context.report({ 30 | node: node, 31 | message: 'Test suite title is used multiple times.' 32 | }); 33 | } 34 | titles.push(title); 35 | } 36 | 37 | function isFirstArgLiteral(node) { 38 | return node.arguments && node.arguments[0] && node.arguments[0].type === 'Literal'; 39 | } 40 | 41 | module.exports = function (context) { 42 | var titleLayers = [ 43 | newLayer() 44 | ]; 45 | return { 46 | CallExpression: function (node) { 47 | var currentLayer = titleLayers[titleLayers.length - 1], 48 | title; 49 | if (astUtil.isDescribe(node)) { 50 | titleLayers.push(newLayer()); 51 | } 52 | if (!isFirstArgLiteral(node)) { 53 | return; 54 | } 55 | 56 | title = node.arguments[0].value; 57 | handlTestCaseTitles(context, currentLayer.testTitles, node, title); 58 | handlTestSuiteTitles(context, currentLayer.describeTitles, node, title); 59 | }, 60 | 'CallExpression:exit': function (node) { 61 | if (astUtil.isDescribe(node)) { 62 | titleLayers.pop(); 63 | } 64 | } 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /test/rules/valid-suite-description.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('valid-suite-description', rules['valid-suite-description'], { 8 | valid: [ 9 | { 10 | options: [ '^[A-Z]' ], 11 | code: 'describe("This is a test", function () { });' 12 | }, 13 | { 14 | options: [ '^[A-Z]' ], 15 | code: 'context("This is a test", function () { });' 16 | }, 17 | { 18 | options: [ '^[A-Z]' ], 19 | code: 'suite("This is a test", function () { });' 20 | }, 21 | { 22 | options: [ '^[A-Z]', [ 'someFunction' ] ], 23 | code: 'describe("this is a test", function () { });' 24 | }, 25 | { 26 | options: [ '^[A-Z]', [ 'someFunction' ] ], 27 | code: 'someFunction("Should do something", function () { });' 28 | }, 29 | 'someOtherFunction();' 30 | ], 31 | 32 | invalid: [ 33 | { 34 | options: [ '^[A-Z]' ], 35 | code: 'describe("this is a test", function () { });', 36 | errors: [ 37 | { message: 'Invalid "describe()" description found.' } 38 | ] 39 | }, 40 | { 41 | options: [ '^[A-Z]' ], 42 | code: 'context("this is a test", function () { });', 43 | errors: [ 44 | { message: 'Invalid "context()" description found.' } 45 | ] 46 | }, 47 | { 48 | options: [ '^[A-Z]' ], 49 | code: 'suite("this is a test", function () { });', 50 | errors: [ 51 | { message: 'Invalid "suite()" description found.' } 52 | ] 53 | }, 54 | { 55 | options: [ '^[A-Z]', [ 'customFunction' ] ], 56 | code: 'customFunction("this is a test", function () { });', 57 | errors: [ 58 | { message: 'Invalid "customFunction()" description found.' } 59 | ] 60 | } 61 | ] 62 | }); 63 | -------------------------------------------------------------------------------- /docs/rules/no-sibling-hooks.md: -------------------------------------------------------------------------------- 1 | # Disallow duplicate uses of a hook at the same level inside a describe (no-sibling-hooks) 2 | 3 | Mocha proposes hooks that allow code to be run before or after every or all tests. This helps define a common setup or teardown process for every test. 4 | It is possible to declare a hook multiple times inside the same test suite, but it can be confusing. It is better to have one hook handle the whole of the setup or teardown logic of the test suite. 5 | 6 | ## Rule Details 7 | 8 | This rule looks for every call to `before`, `after`, `beforeEach` and `afterEach` and reports them if they are called at least twice inside of a test suite at the same level. 9 | 10 | The following patterns are considered warnings: 11 | 12 | ```js 13 | describe('foo', function () { 14 | var mockUser; 15 | var mockLocation; 16 | 17 | before(function () { // Is allowed this time 18 | mockUser = {age: 50}; 19 | }); 20 | 21 | before(function () { // Duplicate! Is not allowed this time 22 | mockLocation = {city: 'New York'}; 23 | }); 24 | 25 | // Same for the other hooks 26 | after(function () {}); 27 | after(function () {}); // Duplicate! 28 | 29 | beforeEach(function () {}); 30 | beforeEach(function () {}); // Duplicate! 31 | 32 | afterEach(function () {}); 33 | afterEach(function () {}); // Duplicate! 34 | }); 35 | ``` 36 | 37 | These patterns would not be considered warnings: 38 | 39 | ```js 40 | describe('foo', function () { 41 | var mockUser; 42 | var mockLocation; 43 | 44 | before(function () { // Is allowed this time 45 | mockUser = {age: 50}; 46 | mockLocation = {city: 'New York'}; 47 | }); 48 | 49 | describe('bar', function () { 50 | before(function () { // Is allowed because it's nested in a new describe 51 | // ... 52 | }); 53 | }); 54 | }); 55 | ``` 56 | 57 | ## When Not To Use It 58 | 59 | * If you use another library which exposes a similar API as mocha (e.g. `before`, `after`), you should turn this rule off, because it would raise warnings. 60 | * If you turned `no-hooks` on, you should turn this rule off, because it would raise several warnings for the same root cause. 61 | -------------------------------------------------------------------------------- /lib/rules/no-hooks-for-single-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var astUtil = require('../util/ast'); 4 | 5 | function newDescribeLayer(describeNode) { 6 | return { 7 | describeNode: describeNode, 8 | hookNodes: [], 9 | testCount: 0 10 | }; 11 | } 12 | 13 | module.exports = function (context) { 14 | var options = context.options[0] || {}, 15 | allowedHooks = options.allow || [], 16 | layers = []; 17 | 18 | function popLayer(node) { 19 | var layer = layers[layers.length - 1]; 20 | if (layer.describeNode === node) { 21 | if (layer.testCount <= 1) { 22 | layer.hookNodes 23 | .filter(function (hookNode) { 24 | return allowedHooks.indexOf(hookNode.name) === -1; 25 | }) 26 | .forEach(function (hookNode) { 27 | context.report({ 28 | node: hookNode, 29 | message: 'Unexpected use of Mocha `' + hookNode.name + '` hook for a single test case' 30 | }); 31 | }); 32 | } 33 | layers.pop(); 34 | } 35 | } 36 | 37 | return { 38 | Program: function (node) { 39 | layers.push(newDescribeLayer(node)); 40 | }, 41 | 42 | CallExpression: function (node) { 43 | if (astUtil.isDescribe(node)) { 44 | layers[layers.length - 1].testCount += 1; 45 | layers.push(newDescribeLayer(node)); 46 | return; 47 | } 48 | 49 | if (astUtil.isTestCase(node)) { 50 | layers[layers.length - 1].testCount += 1; 51 | } 52 | 53 | if (astUtil.isHookIdentifier(node.callee)) { 54 | layers[layers.length - 1].hookNodes.push(node.callee); 55 | } 56 | }, 57 | 58 | 'CallExpression:exit': popLayer, 59 | 'Program:exit': popLayer 60 | }; 61 | }; 62 | 63 | module.exports.schema = [ 64 | { 65 | type: 'object', 66 | properties: { 67 | allow: { 68 | type: 'array', 69 | items: { 70 | type: 'string' 71 | } 72 | } 73 | } 74 | } 75 | ]; 76 | -------------------------------------------------------------------------------- /lib/rules/no-synchronous-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var R = require('ramda'); 4 | 5 | module.exports = function (context) { 6 | var possibleAsyncFunctionNames = [ 7 | 'it', 8 | 'it.only', 9 | 'test', 10 | 'test.only', 11 | 'specify', 12 | 'specify.only', 13 | 'before', 14 | 'after', 15 | 'beforeEach', 16 | 'afterEach' 17 | ]; 18 | 19 | function getCalleeName(callee) { 20 | if (callee.type === 'MemberExpression') { 21 | return callee.object.name + '.' + callee.property.name; 22 | } 23 | 24 | return callee.name; 25 | } 26 | 27 | function hasParentMochaFunctionCall(functionExpression) { 28 | var name; 29 | 30 | if (functionExpression.parent && functionExpression.parent.type === 'CallExpression') { 31 | name = getCalleeName(functionExpression.parent.callee); 32 | return possibleAsyncFunctionNames.indexOf(name) > -1; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | function hasAsyncCallback(functionExpression) { 39 | return functionExpression.params.length === 1; 40 | } 41 | 42 | function findPromiseReturnStatement(nodes) { 43 | return R.find(function (node) { 44 | return node.type === 'ReturnStatement' && node.argument && node.argument.type !== 'Literal'; 45 | }, nodes); 46 | } 47 | 48 | function checkPromiseReturn(functionExpression) { 49 | var bodyStatement = functionExpression.body, 50 | returnStatement = null; 51 | 52 | if (bodyStatement.type === 'BlockStatement') { 53 | returnStatement = findPromiseReturnStatement(functionExpression.body.body); 54 | } else if (bodyStatement.type === 'CallExpression') { 55 | // allow arrow statements calling a promise with implicit return. 56 | returnStatement = bodyStatement; 57 | } 58 | 59 | if (!returnStatement) { 60 | context.report(functionExpression, 'Unexpected synchronous test.'); 61 | } 62 | } 63 | 64 | function check(node) { 65 | if (hasParentMochaFunctionCall(node) && !hasAsyncCallback(node)) { 66 | checkPromiseReturn(node); 67 | } 68 | } 69 | 70 | return { 71 | FunctionExpression: check, 72 | ArrowFunctionExpression: check 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('chai').expect, 4 | fs = require('fs'), 5 | path = require('path'), 6 | rulesDir = path.join(__dirname, '../lib/rules/'), 7 | documentationDir = path.join(__dirname, '../docs/rules/'), 8 | plugin = require('..'); 9 | 10 | describe('eslint-plugin-mocha', function () { 11 | var ruleFiles; 12 | 13 | before(function (done) { 14 | fs.readdir(rulesDir, function (error, files) { 15 | ruleFiles = files; 16 | done(error); 17 | }); 18 | }); 19 | 20 | it('should expose all rules', function () { 21 | ruleFiles.forEach(function (file) { 22 | var ruleName = path.basename(file, '.js'); 23 | 24 | expect(plugin).to.have.deep.property('rules.' + ruleName) 25 | .that.equals(require(rulesDir + ruleName)); 26 | }); 27 | }); 28 | 29 | describe('documentation', function () { 30 | var documentationFiles, 31 | documentationIndex; 32 | 33 | before(function (done) { 34 | fs.readdir(documentationDir, function (readDirError, files) { 35 | if (readDirError) { 36 | done(readDirError); 37 | return; 38 | } 39 | 40 | documentationFiles = files.filter(function (file) { 41 | return file !== 'README.md'; 42 | }); 43 | 44 | fs.readFile(documentationDir + 'README.md', function (error, data) { 45 | documentationIndex = data.toString(); 46 | done(error); 47 | }); 48 | }); 49 | }); 50 | 51 | it('should have each rule documented', function () { 52 | ruleFiles.forEach(function (file) { 53 | var ruleName = path.basename(file, '.js'), 54 | expectedDocumentationFileName = ruleName + '.md'; 55 | 56 | expect(documentationFiles).to.contain(expectedDocumentationFileName); 57 | }); 58 | }); 59 | 60 | it('should be linked in the documentation index', function () { 61 | documentationFiles.forEach(function (file) { 62 | var ruleName = path.basename(file, '.md'), 63 | expectedLink = '* [' + ruleName + '](' + file + ')'; 64 | 65 | expect(documentationIndex).to.contain(expectedLink); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /lib/rules/handle-done-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var R = require('ramda'); 4 | 5 | module.exports = function (context) { 6 | var possibleAsyncFunctionNames = [ 7 | 'it', 8 | 'it.only', 9 | 'test', 10 | 'test.only', 11 | 'specify', 12 | 'specify.only', 13 | 'before', 14 | 'after', 15 | 'beforeEach', 16 | 'afterEach' 17 | ]; 18 | 19 | function getCalleeName(callee) { 20 | if (callee.type === 'MemberExpression') { 21 | return callee.object.name + '.' + callee.property.name; 22 | } 23 | 24 | return callee.name; 25 | } 26 | 27 | function hasParentMochaFunctionCall(functionExpression) { 28 | var name; 29 | 30 | if (functionExpression.parent && functionExpression.parent.type === 'CallExpression') { 31 | name = getCalleeName(functionExpression.parent.callee); 32 | return possibleAsyncFunctionNames.indexOf(name) > -1; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | function isAsyncFunction(functionExpression) { 39 | return functionExpression.params.length === 1; 40 | } 41 | 42 | function findParamInScope(paramName, scope) { 43 | return R.find(function (variable) { 44 | return variable.name === paramName && variable.defs[0].type === 'Parameter'; 45 | }, scope.variables); 46 | } 47 | 48 | function isReferenceHandled(reference) { 49 | var parent = context.getNodeByRangeIndex(reference.identifier.range[0]).parent; 50 | 51 | return parent.type === 'CallExpression'; 52 | } 53 | 54 | function hasHandledReferences(references) { 55 | return references.some(isReferenceHandled); 56 | } 57 | 58 | function checkAsyncMochaFunction(functionExpression) { 59 | var scope = context.getScope(), 60 | callback = functionExpression.params[0], 61 | callbackName = callback.name, 62 | callbackVariable = findParamInScope(callbackName, scope); 63 | 64 | if (callbackVariable && !hasHandledReferences(callbackVariable.references)) { 65 | context.report(callback, 'Expected "{{name}}" callback to be handled.', { name: callbackName }); 66 | } 67 | } 68 | 69 | function check(node) { 70 | if (hasParentMochaFunctionCall(node) && isAsyncFunction(node)) { 71 | checkAsyncMochaFunction(node); 72 | } 73 | } 74 | 75 | return { 76 | FunctionExpression: check, 77 | ArrowFunctionExpression: check 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /docs/rules/no-hooks-for-single-case.md: -------------------------------------------------------------------------------- 1 | # Disallow hooks for a single test or test suite (no-hooks-for-single-case) 2 | 3 | Mocha proposes hooks that allow code to be run before or after every or all tests. This helps define a common setup or teardown process for every test. These hooks are not useful when there is only one test case, as it would then make more sense to move the hooks' operations in the test directly. 4 | 5 | ## Rule Details 6 | 7 | This rule looks for every call to `before`, `after`, `beforeEach` and `afterEach` and reports them if they are called when there is less than two tests and/or tests suites in the same test suite. 8 | 9 | The following patterns are considered warnings: 10 | 11 | ```js 12 | describe('foo', function () { 13 | before(function () { /* ... */ }); // Not allowed as there is only a test suite next to this hook. 14 | 15 | describe('bar', function() { 16 | /* ... */ 17 | }); 18 | }); 19 | 20 | describe('foo', function () { 21 | after(function () { /* ... */ }); // Not allowed as there is only a test case next to this hook. 22 | 23 | it('should XYZ', function() { 24 | /* ... */ 25 | }); 26 | }); 27 | 28 | describe('foo', function () { 29 | beforeEach(function () { /* ... */ }); // Not allowed as there is no test suites or cases next to this hook. 30 | }); 31 | ``` 32 | 33 | These patterns would not be considered warnings: 34 | 35 | ```js 36 | describe('foo', function () { 37 | before(function () { /* ... */ }); 38 | 39 | it('should XYZ', function() { 40 | /* ... */ 41 | }); 42 | 43 | it('should ABC', function() { 44 | /* ... */ 45 | }); 46 | }); 47 | 48 | describe('foo', function () { 49 | before(function () { /* ... */ }); 50 | 51 | it('should XYZ', function() { 52 | /* ... */ 53 | }); 54 | 55 | describe('bar', function() { 56 | /* ... */ 57 | }); 58 | }); 59 | ``` 60 | 61 | # Options 62 | 63 | This rule supports the following options: 64 | 65 | * `allow`: An array containing the names of hooks to allow. This might be used to allow writing `after` hooks to run clean-up code. Defaults to an empty array. 66 | 67 | ```json 68 | { 69 | "rules": { 70 | "mocha/no-hooks-for-single-case": ["error", {"allow": ["after"]}] 71 | } 72 | } 73 | ``` 74 | 75 | ## When Not To Use It 76 | 77 | * If you use another library which exposes a similar API as mocha (e.g. `before`, `after`), you should turn this rule off, because it would raise warnings. 78 | * If you turned `no-hooks` on, you should turn this rule off, because it would raise several warnings for the same root cause. 79 | -------------------------------------------------------------------------------- /lib/rules/no-return-and-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var R = require('ramda'), 4 | findReturnStatement = R.find(R.propEq('type', 'ReturnStatement')), 5 | possibleAsyncFunctionNames = [ 6 | 'it', 7 | 'it.only', 8 | 'test', 9 | 'test.only', 10 | 'specify', 11 | 'specify.only', 12 | 'before', 13 | 'after', 14 | 'beforeEach', 15 | 'afterEach' 16 | ]; 17 | 18 | function getCalleeName(callee) { 19 | if (callee.type === 'MemberExpression') { 20 | return callee.object.name + '.' + callee.property.name; 21 | } 22 | 23 | return callee.name; 24 | } 25 | 26 | function hasParentMochaFunctionCall(functionExpression) { 27 | var name; 28 | 29 | if (functionExpression.parent && functionExpression.parent.type === 'CallExpression') { 30 | name = getCalleeName(functionExpression.parent.callee); 31 | return possibleAsyncFunctionNames.indexOf(name) > -1; 32 | } 33 | 34 | return false; 35 | } 36 | 37 | function reportIfShortArrowFunction(context, node) { 38 | if (node.body.type !== 'BlockStatement') { 39 | context.report({ 40 | node: node.body, 41 | message: 'Confusing implicit return in a test with callback' 42 | }); 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | function isAllowedReturnStatement(node, doneName) { 49 | var argument = node.argument; 50 | if (argument === null || argument.type === 'Literal') { 51 | return true; 52 | } 53 | if (argument.type === 'Identifier' && argument.name === 'undefined') { 54 | return true; 55 | } 56 | return argument.type === 'CallExpression' && 57 | argument.callee.type === 'Identifier' && 58 | argument.callee.name === doneName; 59 | } 60 | 61 | function reportIfFunctionWithBlock(context, node, doneName) { 62 | var returnStatement = findReturnStatement(node.body.body); 63 | if (returnStatement && !isAllowedReturnStatement(returnStatement, doneName)) { 64 | context.report({ 65 | node: returnStatement, 66 | message: 'Unexpected use of `return` in a test with callback' 67 | }); 68 | } 69 | } 70 | 71 | module.exports = function (context) { 72 | function check(node) { 73 | if (node.params.length === 0 || !hasParentMochaFunctionCall(node)) { 74 | return; 75 | } 76 | 77 | if (!reportIfShortArrowFunction(context, node)) { 78 | reportIfFunctionWithBlock(context, node, node.params[0].name); 79 | } 80 | } 81 | 82 | return { 83 | FunctionExpression: check, 84 | ArrowFunctionExpression: check 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /docs/rules/no-exclusive-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow Exclusive Tests (no-exclusive-tests) 2 | 3 | Mocha has a feature that allows you to run tests exclusively by appending `.only` to a test-suite or a test-case. 4 | This feature is really helpful to debug a failing test, so you don’t have to execute all of your tests. 5 | After you have fixed your test and before committing the changes you have to remove `.only` to ensure all tests are executed on your build system. 6 | 7 | This rule reminds you to remove `.only` from your tests by raising a warning whenever you are using the exclusivity feature. 8 | 9 | ## Rule Details 10 | 11 | This rule looks for every `describe.only`, `it.only`, `suite.only`, `test.only`, `context.only` and `specify.only`occurrences within the source code. 12 | Of course there are some edge-cases which can’t be detected by this rule e.g.: 13 | 14 | ```js 15 | var describeOnly = describe.only; 16 | describeOnly.apply(describe); 17 | ``` 18 | 19 | The following patterns are considered warnings: 20 | 21 | ```js 22 | // bdd 23 | describe.only("foo", function () {}); 24 | it.only("foo", function () {}); 25 | describe["only"]("bar", function () {}); 26 | it["only"]("bar", function () {}); 27 | 28 | // tdd 29 | suite.only("foo", function () {}); 30 | test.only("foo", function () {}); 31 | suite["only"]("bar", function () {}); 32 | test["only"]("bar", function () {}); 33 | 34 | ``` 35 | 36 | These patterns would not be considered warnings: 37 | 38 | ```js 39 | // bdd 40 | describe("foo", function () {}); 41 | it("foo", function () {}); 42 | describe.skip("bar", function () {}); 43 | it.skip("bar", function () {}); 44 | 45 | // tdd 46 | suite("foo", function () {}); 47 | test("foo", function () {}); 48 | suite.skip("bar", function () {}); 49 | test.skip("bar", function () {}); 50 | ``` 51 | 52 | # Options 53 | 54 | This rule supports the following shared configuration options: 55 | 56 | * `additionalTestFunctions`: An array of extra test functions to protect. This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha) 57 | 58 | ```json 59 | { 60 | "rules": { 61 | "mocha/no-exclusive-tests": "error" 62 | }, 63 | "settings": { 64 | "mocha/additionalTestFunctions": [ 65 | "describeModule" 66 | ] 67 | } 68 | } 69 | ``` 70 | 71 | ## When Not To Use It 72 | 73 | * If you really want to execute only one test-suite or test-case because all other tests should not be executed, turn this rule off. 74 | * If you use another library which exposes a similar API as mocha (e.g. `describe.only`), you should turn this rule off, because it would raise warnings. 75 | 76 | ## Further Reading 77 | 78 | * [Exclusive Tests](http://mochajs.org/#exclusive-tests) 79 | -------------------------------------------------------------------------------- /docs/rules/no-skipped-tests.md: -------------------------------------------------------------------------------- 1 | # Disallow Skipped Tests (no-skipped-tests) 2 | 3 | Mocha has a feature that allows you to skip tests by appending `.skip` to a test-suite or a test-case, or by prepending it with an `x` (e.g., `xdescribe(...)` instead of `describe(...)`). 4 | Sometimes tests are skipped as part of a debugging process, and aren't intended to be committed. This rule reminds you to remove `.skip` or the `x` prefix from your tests. 5 | 6 | **Fixable:** Problems detected by this rule are automatically fixable using the `--fix` flag on the command line. 7 | 8 | ## Rule Details 9 | 10 | This rule looks for `describe.skip`, `it.skip`, `suite.skip`, `test.skip`, `context.skip`, `specify.skip`, `xdescribe`, `xit`, `xcontext` and `xspecify` occurrences within the source code. 11 | 12 | The following patterns are considered warnings: 13 | 14 | ```js 15 | // bdd 16 | describe.skip("foo", function () {}); 17 | it.skip("foo", function () {}); 18 | describe["skip"]("bar", function () {}); 19 | it["skip"]("bar", function () {}); 20 | xdescribe("baz", function() {}); 21 | xit("baz", function() {}); 22 | 23 | // tdd 24 | suite.skip("foo", function () {}); 25 | test.skip("foo", function () {}); 26 | suite["skip"]("bar", function () {}); 27 | test["skip"]("bar", function () {}); 28 | 29 | ``` 30 | 31 | These patterns would not be considered warnings: 32 | 33 | ```js 34 | // bdd 35 | describe("foo", function () {}); 36 | it("foo", function () {}); 37 | describe.only("bar", function () {}); 38 | it.only("bar", function () {}); 39 | 40 | // tdd 41 | suite("foo", function () {}); 42 | test("foo", function () {}); 43 | suite.only("bar", function () {}); 44 | test.only("bar", function () {}); 45 | ``` 46 | 47 | # Options 48 | 49 | This rule supports the following shared configuration options: 50 | 51 | * `additionalTestFunctions`: An array of extra test functions to protect. This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha) 52 | * `additionalXFunctions`: An array of extra x-function to protect 53 | 54 | ```json 55 | { 56 | "rules": { 57 | "mocha/no-skipped-tests": "error" 58 | }, 59 | "settings": { 60 | "mocha/additionalTestFunctions": [ 61 | "describeModule" 62 | ], 63 | "mocha/additionalXFunctions": [ 64 | "xdescribeModule" 65 | ] 66 | } 67 | } 68 | ``` 69 | ## When Not To Use It 70 | 71 | * If you really want to commit skipped tests to your repo, turn this rule off. 72 | * If you use another library which exposes a similar API to mocha (e.g. `describe.skip` or `xdescribe`), you should turn this rule off, because it would raise warnings. 73 | 74 | ## Further Reading 75 | 76 | * [Exclusive Tests](http://mochajs.org/#inclusive-tests) 77 | -------------------------------------------------------------------------------- /test/rules/no-top-level-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-top-level-hooks', rules['no-top-level-hooks'], { 8 | 9 | valid: [ 10 | 'describe(function() { before(function() {}); });', 11 | 'describe(function() { after(function() {}); });', 12 | 'describe(function() { beforeEach(function() {}); });', 13 | 'describe(function() { afterEach(function() {}); });', 14 | 'describe(function() { it(function() {}); });', 15 | 'describe(function() { describe(function() { before(function() {}); }); });', 16 | 'foo.before()', 17 | 'foo.after()', 18 | 'foo.beforeEach()', 19 | 'foo.afterEach()', 20 | 'before.foo()', 21 | 'after.foo()', 22 | 'beforeEach.foo()', 23 | 'afterEach.foo()', 24 | 'var before = 2; before + 3;' 25 | ], 26 | 27 | invalid: [ 28 | { 29 | code: 'before(function() {});', 30 | errors: [ { 31 | message: 'Unexpected use of Mocha `before` hook outside of a test suite', 32 | column: 1, 33 | line: 1 34 | } ] 35 | }, 36 | { 37 | code: 'after(function() {});', 38 | errors: [ { 39 | message: 'Unexpected use of Mocha `after` hook outside of a test suite', 40 | column: 1, 41 | line: 1 42 | } ] 43 | }, 44 | { 45 | code: 'beforeEach(function() {});', 46 | errors: [ { 47 | message: 'Unexpected use of Mocha `beforeEach` hook outside of a test suite', 48 | column: 1, 49 | line: 1 50 | } ] 51 | }, 52 | { 53 | code: 'afterEach(function() {});', 54 | errors: [ { 55 | message: 'Unexpected use of Mocha `afterEach` hook outside of a test suite', 56 | column: 1, 57 | line: 1 58 | } ] 59 | }, 60 | { 61 | code: 'describe(function() {}); before(function() {});', 62 | errors: [ { 63 | message: 'Unexpected use of Mocha `before` hook outside of a test suite', 64 | column: 26, 65 | line: 1 66 | } ] 67 | }, 68 | { 69 | code: 'before(function() {}); describe(function() {});', 70 | errors: [ { 71 | message: 'Unexpected use of Mocha `before` hook outside of a test suite', 72 | column: 1, 73 | line: 1 74 | } ] 75 | } 76 | ] 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /test/rules/valid-test-description.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('valid-test-description', rules['valid-test-description'], { 8 | valid: [ 9 | 'it("should respond to GET", function() { });', 10 | 'it("should do something");', 11 | 'specify("should respond to GET", function() { });', 12 | 'specify("should do something");', 13 | 'test("should respond to GET", function() { });', 14 | 'test("should do something");', 15 | 'it();', 16 | 'specify();', 17 | 'test();', 18 | { 19 | options: [ 'test' ], 20 | code: 'it("this is a test", function () { });' 21 | }, 22 | { 23 | options: [ 'test' ], 24 | code: 'test("this is a test", function () { });' 25 | }, 26 | { 27 | options: [ '^should', [ 'someFunction' ] ], 28 | code: 'it("this is a test", function () { });' 29 | }, 30 | { 31 | options: [ '^should', [ 'someFunction' ] ], 32 | code: 'someFunction("should do something", function () { });' 33 | }, 34 | 'someOtherFunction();' 35 | ], 36 | 37 | invalid: [ 38 | { 39 | code: 'it("does something", function() { });', 40 | errors: [ 41 | { message: 'Invalid "it()" description found.' } 42 | ] 43 | }, 44 | { 45 | code: 'specify("does something", function() { });', 46 | errors: [ 47 | { message: 'Invalid "specify()" description found.' } 48 | ] 49 | }, 50 | { 51 | code: 'test("does something", function() { });', 52 | errors: [ 53 | { message: 'Invalid "test()" description found.' } 54 | ] 55 | }, 56 | { 57 | options: [ 'required' ], 58 | code: 'it("this is a test", function () { });', 59 | errors: [ 60 | { message: 'Invalid "it()" description found.' } 61 | ] 62 | }, 63 | { 64 | options: [ 'required' ], 65 | code: 'specify("this is a test", function () { });', 66 | errors: [ 67 | { message: 'Invalid "specify()" description found.' } 68 | ] 69 | }, 70 | { 71 | options: [ 'required' ], 72 | code: 'test("this is a test", function () { });', 73 | errors: [ 74 | { message: 'Invalid "test()" description found.' } 75 | ] 76 | }, 77 | { 78 | options: [ 'required', [ 'customFunction' ] ], 79 | code: 'customFunction("this is a test", function () { });', 80 | errors: [ 81 | { message: 'Invalid "customFunction()" description found.' } 82 | ] 83 | } 84 | ] 85 | }); 86 | -------------------------------------------------------------------------------- /test/rules/no-synchronous-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-synchronous-tests', rules['no-synchronous-tests'], { 8 | valid: [ 9 | 'it();', 10 | 'it("");', 11 | 'it("", function () { return promise(); });', 12 | 'it("", function () { return promise(); });', 13 | 'it("", function () { var promise = myFn(); return promise; });', 14 | 'var someFn = function(){ }; it("", someFn);', 15 | 'it("", function (done) { });', 16 | 'it("", function (done) { done(); });', 17 | 'it("", function (callback) { callback(); });', 18 | 'it("", function (done) { if (a) { done(); } });', 19 | 'it("", function (done) { function foo() { done(); } });', 20 | 'it("", function (done) { setTimeout(done, 300); });', 21 | 'it("", function (done) { done(new Error("foo")); });', 22 | 'it("", function (done) { promise.then(done).catch(done); });', 23 | 'it.only("", function (done) { done(); });', 24 | 'test("", function (done) { done(); });', 25 | 'test.only("", function (done) { done(); });', 26 | 'before(function (done) { done(); });', 27 | 'after(function (done) { done(); });', 28 | 'beforeEach(function (done) { done(); });', 29 | 'afterEach(function (done) { done(); });', 30 | 'ignoredFunction(function () { });', 31 | 'var foo = function () { };', 32 | { 33 | code: 'it("", (done) => { done(); });', 34 | parserOptions: { ecmaVersion: 6 } 35 | }, 36 | { 37 | code: 'it("", () => { return promise(); });', 38 | parserOptions: { ecmaVersion: 6 } 39 | }, 40 | { 41 | code: 'it("", () => promise() );', 42 | parserOptions: { ecmaVersion: 6 } 43 | }, 44 | { 45 | code: 'it("", () => promise.then() );', 46 | parserOptions: { ecmaVersion: 6 } 47 | } 48 | ], 49 | 50 | invalid: [ 51 | { 52 | code: 'it("", function () {});', 53 | errors: [ { message: 'Unexpected synchronous test.', column: 8, line: 1 } ] 54 | }, 55 | { 56 | code: 'it("", function () { callback(); });', 57 | errors: [ { message: 'Unexpected synchronous test.', column: 8, line: 1 } ] 58 | }, 59 | { 60 | code: 'it(function () { return; });', 61 | errors: [ { message: 'Unexpected synchronous test.', column: 4, line: 1 } ] 62 | }, 63 | { 64 | code: 'it("", function () { return "a string" });', 65 | errors: [ { message: 'Unexpected synchronous test.', column: 8, line: 1 } ] 66 | }, 67 | { 68 | code: 'it("", () => "not-a-promise" );', 69 | parserOptions: { ecmaVersion: 6 }, 70 | errors: [ { message: 'Unexpected synchronous test.', column: 8, line: 1 } ] 71 | }, 72 | { 73 | code: 'specify("", function () {});', 74 | errors: [ { message: 'Unexpected synchronous test.', column: 13, line: 1 } ] 75 | }, 76 | { 77 | code: 'specify.only("", function () {});', 78 | errors: [ { message: 'Unexpected synchronous test.', column: 18, line: 1 } ] 79 | } 80 | 81 | ] 82 | }); 83 | -------------------------------------------------------------------------------- /lib/rules/no-mocha-arrows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @fileoverview Disallow arrow functions as arguments to Mocha globals 5 | * @author Paul Melnikow 6 | */ 7 | 8 | var R = require('ramda'), 9 | mochaFunctionNames = [ 10 | 'describe', 11 | 'describe.skip', 12 | 'xdescribe', 13 | 'suite', 14 | 'context', 15 | 'xcontext', 16 | 'specify', 17 | 'xspecify', 18 | 'it', 19 | 'it.only', 20 | 'xit', 21 | 'test', 22 | 'test.only', 23 | 'specify', 24 | 'specify.only', 25 | 'before', 26 | 'after', 27 | 'beforeEach', 28 | 'afterEach' 29 | ]; 30 | 31 | module.exports = function (context) { 32 | function getCalleeName(callee) { 33 | if (callee.type === 'MemberExpression') { 34 | return callee.object.name + '.' + callee.property.name; 35 | } 36 | 37 | return callee.name; 38 | } 39 | 40 | function isLikelyMochaGlobal(scope, name) { 41 | return !R.find(R.propEq('name', name), scope.variables); 42 | } 43 | 44 | function fixArrowFunction(fixer, fn) { 45 | var sourceCode = context.getSourceCode(), 46 | paramsLeftParen = sourceCode.getFirstToken(fn), 47 | paramsRightParen = sourceCode.getTokenBefore(sourceCode.getTokenBefore(fn.body)), 48 | paramsFullText = 49 | sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]), 50 | functionKeyword = 'function', 51 | bodyText; 52 | 53 | if (fn.async) { 54 | // When 'async' specified, take care about the keyword. 55 | functionKeyword = 'async function'; 56 | // Strip 'async (...)' to ' (...)' 57 | paramsFullText = paramsFullText.slice(5); 58 | } 59 | 60 | if (fn.params.length > 0) { 61 | paramsFullText = '(' + sourceCode.text.slice(fn.params[0].start, R.last(fn.params).end) + ')'; 62 | } 63 | 64 | if (fn.body.type === 'BlockStatement') { 65 | // When it((...) => { ... }), 66 | // simply replace '(...) => ' with 'function () ' 67 | return fixer.replaceTextRange( 68 | [ fn.start, fn.body.start ], 69 | functionKeyword + paramsFullText + ' ' 70 | ); 71 | } 72 | 73 | bodyText = sourceCode.text.slice(fn.body.range[0], fn.body.range[1]); 74 | return fixer.replaceTextRange( 75 | [ fn.start, fn.end ], 76 | functionKeyword + paramsFullText + ' { return ' + bodyText + '; }' 77 | ); 78 | } 79 | 80 | return { 81 | CallExpression: function (node) { 82 | var name = getCalleeName(node.callee), 83 | fnArg; 84 | 85 | if (name && mochaFunctionNames.indexOf(name) > -1) { 86 | fnArg = node.arguments.slice(-1)[0]; 87 | if (fnArg && fnArg.type === 'ArrowFunctionExpression') { 88 | if (isLikelyMochaGlobal(context.getScope(), name)) { 89 | context.report({ 90 | node: node, 91 | message: 'Do not pass arrow functions to ' + name + '()', 92 | fix: function (fixer) { 93 | return fixArrowFunction(fixer, fnArg); 94 | } 95 | }); 96 | } 97 | } 98 | } 99 | } 100 | }; 101 | }; 102 | -------------------------------------------------------------------------------- /lib/rules/no-skipped-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getAdditionalTestFunctions = require('../util/settings').getAdditionalTestFunctions, 4 | getAdditionalXFunctions = require('../util/settings').getAdditionalXFunctions, 5 | mochaTestFunctions, 6 | mochaXFunctions; 7 | 8 | function matchesMochaTestFunction(object) { 9 | return object && mochaTestFunctions.indexOf(object.name) !== -1; 10 | } 11 | 12 | function isPropertyNamedSkip(property) { 13 | return property && (property.name === 'skip' || property.value === 'skip'); 14 | } 15 | 16 | function isCallToMochasSkipFunction(callee) { 17 | return callee.type === 'MemberExpression' && 18 | matchesMochaTestFunction(callee.object) && 19 | isPropertyNamedSkip(callee.property); 20 | } 21 | 22 | function createSkipAutofixFunction(callee) { 23 | var endRangeOfMemberExpression = callee.range[1], 24 | endRangeOfMemberExpressionObject = callee.object.range[1], 25 | rangeToRemove = [ endRangeOfMemberExpressionObject, endRangeOfMemberExpression ]; 26 | 27 | return function removeSkipProperty(fixer) { 28 | return fixer.removeRange(rangeToRemove); 29 | }; 30 | } 31 | 32 | function createXAutofixFunction(callee) { 33 | var rangeToRemove = [ callee.range[0], callee.range[0] + 1 ]; 34 | 35 | return function removeXPrefix(fixer) { 36 | return fixer.removeRange(rangeToRemove); 37 | }; 38 | } 39 | 40 | function isMochaXFunction(name) { 41 | return mochaXFunctions.indexOf(name) !== -1; 42 | } 43 | 44 | function isCallToMochaXFunction(callee) { 45 | return callee.type === 'Identifier' && isMochaXFunction(callee.name); 46 | } 47 | 48 | module.exports = function (context) { 49 | var settings = context.settings, 50 | additionalTestFunctions = getAdditionalTestFunctions(settings), 51 | additionalXFunctions = getAdditionalXFunctions(settings); 52 | 53 | mochaTestFunctions = [ 54 | 'it', 55 | 'describe', 56 | 'suite', 57 | 'test', 58 | 'context', 59 | 'specify' 60 | ].concat(additionalTestFunctions); 61 | mochaXFunctions = [ 62 | 'xit', 63 | 'xdescribe', 64 | 'xcontext', 65 | 'xspecify' 66 | ].concat(additionalXFunctions); 67 | 68 | return { 69 | CallExpression: function (node) { 70 | var callee = node.callee; 71 | 72 | if (callee && isCallToMochasSkipFunction(callee)) { 73 | context.report({ 74 | node: callee.property, 75 | message: 'Unexpected skipped mocha test.', 76 | fix: createSkipAutofixFunction(callee) 77 | }); 78 | } else if (callee && isCallToMochaXFunction(callee)) { 79 | context.report({ 80 | node: callee, 81 | message: 'Unexpected skipped mocha test.', 82 | fix: createXAutofixFunction(callee) 83 | }); 84 | } 85 | } 86 | }; 87 | }; 88 | 89 | module.exports.schema = [ 90 | { 91 | type: 'object', 92 | properties: { 93 | additionalTestFunctions: { 94 | type: 'array', 95 | items: { 96 | type: 'string' 97 | }, 98 | minItems: 1, 99 | uniqueItems: true 100 | }, 101 | additionalXFunctions: { 102 | type: 'array', 103 | items: { 104 | type: 'string' 105 | }, 106 | minItems: 1, 107 | uniqueItems: true 108 | } 109 | } 110 | } 111 | ]; 112 | -------------------------------------------------------------------------------- /test/rules/no-nested-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rule = require('../../lib/rules/no-nested-tests'), 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-nested-tests', rule, { 8 | valid: [ 9 | 'it()', 10 | 'it(); it(); it()', 11 | 'describe("", function () { it(); })', 12 | 'describe("", function () { describe("", function () { it(); }); it(); })' 13 | ], 14 | 15 | invalid: [ 16 | { 17 | code: 'it("", function () { it() });', 18 | errors: [ { 19 | message: 'Unexpected test nested within another test.', 20 | line: 1, 21 | column: 22 22 | } ] 23 | }, 24 | { 25 | code: 'it.only("", function () { it() });', 26 | errors: [ { 27 | message: 'Unexpected test nested within another test.', 28 | line: 1, 29 | column: 27 30 | } ] 31 | }, 32 | { 33 | code: 'it.skip("", function () { it() });', 34 | errors: [ { 35 | message: 'Unexpected test nested within another test.', 36 | line: 1, 37 | column: 27 38 | } ] 39 | }, 40 | { 41 | code: 'test("", function () { it() });', 42 | errors: [ { 43 | message: 'Unexpected test nested within another test.', 44 | line: 1, 45 | column: 24 46 | } ] 47 | }, 48 | { 49 | code: 'specify("", function () { it() });', 50 | errors: [ { 51 | message: 'Unexpected test nested within another test.', 52 | line: 1, 53 | column: 27 54 | } ] 55 | }, 56 | { 57 | code: 'it("", function () { describe() });', 58 | errors: [ { 59 | message: 'Unexpected suite nested within a test.', 60 | line: 1, 61 | column: 22 62 | } ] 63 | }, 64 | { 65 | code: 'it("", function () { context() });', 66 | errors: [ { 67 | message: 'Unexpected suite nested within a test.', 68 | line: 1, 69 | column: 22 70 | } ] 71 | }, 72 | { 73 | code: 'it("", function () { suite() });', 74 | errors: [ { 75 | message: 'Unexpected suite nested within a test.', 76 | line: 1, 77 | column: 22 78 | } ] 79 | }, 80 | { 81 | code: 'it("", function () { describe.skip() });', 82 | errors: [ { 83 | message: 'Unexpected suite nested within a test.', 84 | line: 1, 85 | column: 22 86 | } ] 87 | }, 88 | { 89 | code: 'it("", function () { describe("", function () { it(); it(); }); });', 90 | errors: [ 91 | { 92 | message: 'Unexpected suite nested within a test.', 93 | line: 1, 94 | column: 22 95 | }, 96 | { 97 | message: 'Unexpected test nested within another test.', 98 | line: 1, 99 | column: 49 100 | }, 101 | { 102 | message: 'Unexpected test nested within another test.', 103 | line: 1, 104 | column: 55 105 | } 106 | ] 107 | } 108 | ] 109 | }); 110 | -------------------------------------------------------------------------------- /test/rules/no-mocha-arrows.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester({ 6 | parserOptions: { ecmaVersion: 2017 } 7 | }), 8 | expectedErrorMessage = 'Do not pass arrow functions to it()', 9 | errors = [ { message: expectedErrorMessage, column: 1, line: 1 } ]; 10 | 11 | ruleTester.run('no-mocha-arrows', rules['no-mocha-arrows'], { 12 | 13 | valid: [ 14 | 'it()', 15 | 'it(function() { assert(something, false); })', 16 | 'it("should be false", function() { assert(something, false); })', 17 | // In this example, `it` is not a global. 18 | 'function it () {}; it(() => { console.log("okay") })', 19 | 'it.only()', 20 | 'it(function(done) { assert(something, false); done(); })', 21 | 'it(function*() { assert(something, false) })', 22 | 'it(async function () { assert(something, false) })' 23 | ], 24 | 25 | invalid: [ 26 | { 27 | code: 'it(() => { assert(something, false); })', 28 | errors: errors, 29 | output: 'it(function() { assert(something, false); })' 30 | }, 31 | { 32 | code: 'it(() => { assert(something, false); })', 33 | globals: [ 'it' ], 34 | errors: errors, 35 | output: 'it(function() { assert(something, false); })' 36 | }, 37 | { 38 | code: 'it(() => assert(something, false))', 39 | errors: errors, 40 | output: 'it(function() { return assert(something, false); })' 41 | }, 42 | { 43 | code: 'it(done => assert(something, false))', 44 | errors: errors, 45 | output: 'it(function(done) { return assert(something, false); })' 46 | }, 47 | { 48 | code: 'it("should be false", () => { assert(something, false); })', 49 | errors: errors, 50 | output: 'it("should be false", function() { assert(something, false); })' 51 | }, 52 | { 53 | code: 'it.only(() => { assert(something, false); })', 54 | errors: [ { message: 'Do not pass arrow functions to it.only()', column: 1, line: 1 } ], 55 | output: 'it.only(function() { assert(something, false); })' 56 | }, 57 | { 58 | code: 'it((done) => { assert(something, false); })', 59 | errors: errors, 60 | output: 'it(function(done) { assert(something, false); })' 61 | }, 62 | { 63 | code: 'it(done => { assert(something, false); })', 64 | errors: errors, 65 | output: 'it(function(done) { assert(something, false); })' 66 | }, 67 | { 68 | code: 'it("should be false", () => {\n assert(something, false);\n})', 69 | errors: errors, 70 | output: 'it("should be false", function() {\n assert(something, false);\n})' 71 | }, 72 | { 73 | code: 'it(async () => { assert(something, false) })', 74 | errors: errors, 75 | output: 'it(async function () { assert(something, false) })' 76 | }, 77 | { 78 | code: 'it(async () => assert(something, false))', 79 | errors: errors, 80 | output: 'it(async function () { return assert(something, false); })' 81 | }, 82 | { 83 | code: 'it(async done => assert(something, false))', 84 | errors: errors, 85 | output: 'it(async function(done) { return assert(something, false); })' 86 | }, 87 | { 88 | code: 'it(async (done) => assert(something, false))', 89 | errors: errors, 90 | output: 'it(async function(done) { return assert(something, false); })' 91 | }, 92 | { 93 | code: 'it(async() => assert(something, false))', 94 | errors: errors, 95 | output: 'it(async function() { return assert(something, false); })' 96 | } 97 | ] 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /test/rules/no-global-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rule = require('../../lib/rules/no-global-tests'), 5 | ruleTester = new RuleTester(), 6 | expectedErrorMessage = 'Unexpected global mocha test.'; 7 | 8 | ruleTester.run('no-global-tests', rule, { 9 | 10 | valid: [ 11 | 'describe();', 12 | 'suite();', 13 | 'context();', 14 | 'describe("", function () { it(); });', 15 | 'describe("", function () { it.only(); });', 16 | 'describe("", function () { it.skip(); });', 17 | 'describe("", function () { test(); });', 18 | 'describe("", function () { test.only(); });', 19 | 'describe("", function () { test.skip(); });', 20 | 'describe.only("", function () { it(); });', 21 | 'describe.skip("", function () { it(); });', 22 | 'suite("", function () { it(); });', 23 | 'suite("", function () { test(); });', 24 | 'suite.only("", function () { it(); });', 25 | 'suite.skip("", function () { it(); });', 26 | 'context("", function () { it(); });', 27 | 'context("", function () { test(); });', 28 | 'context.only("", function () { it(); });', 29 | 'context.skip("", function () { it(); });', 30 | 'something("", function () { describe("", function () { it(); }); });', 31 | 'something("", function () { it(); });', 32 | 'something("", function () { test(); } );', 33 | '[1,2,3].forEach(function () { it(); });', 34 | { 35 | code: 'import foo from "bar"; describe("", () => it());', 36 | parserOptions: { 37 | sourceType: 'module', 38 | ecmaVersion: 6 39 | } 40 | } 41 | ], 42 | 43 | invalid: [ 44 | { 45 | code: 'it();', 46 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 47 | }, 48 | { 49 | code: 'it.only();', 50 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 51 | }, 52 | { 53 | code: 'it["only"]();', 54 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 55 | }, 56 | { 57 | code: 'it.skip();', 58 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 59 | }, 60 | { 61 | code: 'it["skip"]();', 62 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 63 | }, 64 | { 65 | code: 'test();', 66 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 67 | }, 68 | { 69 | code: 'test.only();', 70 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 71 | }, 72 | { 73 | code: 'test["only"]();', 74 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 75 | }, 76 | { 77 | code: 'test.skip();', 78 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 79 | }, 80 | { 81 | code: 'test["skip"]();', 82 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 83 | }, 84 | { 85 | code: 'specify();', 86 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 87 | }, 88 | { 89 | code: 'specify.only();', 90 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 91 | }, 92 | { 93 | code: 'specify["only"]();', 94 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 95 | }, 96 | { 97 | code: 'specify.skip();', 98 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 99 | }, 100 | { 101 | code: 'specify["skip"]();', 102 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] 103 | }, 104 | { 105 | code: 'import foo from "bar"; it("");', 106 | parserOptions: { 107 | sourceType: 'module' 108 | }, 109 | errors: [ { message: expectedErrorMessage, column: 24, line: 1 } ] 110 | } 111 | 112 | ] 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/rules/no-sibling-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-sibling-hooks', rules['no-sibling-hooks'], { 8 | 9 | valid: [ 10 | 'describe(function() { before(function() {}); it(function() {}); });', 11 | 'describe(function() { after(function() {}); it(function() {}); });', 12 | 'describe(function() { beforeEach(function() {}); it(function() {}); });', 13 | 'describe(function() { afterEach(function() {}); it(function() {}); });', 14 | 'describe(function() { before(function() {}); after(function() {}); });', 15 | 'describe(function() { before(function() {}); beforeEach(function() {}); });', 16 | 'describe(function() { beforeEach(function() {}); afterEach(function() {}); });', 17 | 'before(function() {}); beforeEach(function() {});', 18 | 'foo.before(function() {}); foo.before(function() {});', 19 | [ 20 | 'describe(function() {', 21 | ' before(function() {});', 22 | ' describe(function() {', 23 | ' before(function() {});', 24 | ' });', 25 | '});' 26 | ].join('\n'), 27 | [ 28 | 'describe(function() {', 29 | ' describe(function() {', 30 | ' before(function() {});', 31 | ' });', 32 | ' before(function() {});', 33 | '});' 34 | ].join('\n'), 35 | [ 36 | 'describe(function() {', 37 | ' describe.only(function() {', 38 | ' before(function() {});', 39 | ' });', 40 | ' before(function() {});', 41 | '});' 42 | ].join('\n'), 43 | [ 44 | 'describe(function() {', 45 | ' describe.skip(function() {', 46 | ' before(function() {});', 47 | ' });', 48 | ' before(function() {});', 49 | '});' 50 | ].join('\n'), 51 | [ 52 | 'describe(function() {', 53 | ' xdescribe(function() {', 54 | ' before(function() {});', 55 | ' });', 56 | ' before(function() {});', 57 | '});' 58 | ].join('\n'), 59 | [ 60 | 'describe(function() {', 61 | ' context(function() {', 62 | ' before(function() {});', 63 | ' });', 64 | ' before(function() {});', 65 | '});' 66 | ].join('\n'), 67 | [ 68 | 'describe(function() {', 69 | ' xcontext(function() {', 70 | ' before(function() {});', 71 | ' });', 72 | ' before(function() {});', 73 | '});' 74 | ].join('\n') 75 | ], 76 | 77 | invalid: [ 78 | { 79 | code: 'describe(function() { before(function() {}); before(function() {}); });', 80 | errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 46, line: 1 } ] 81 | }, 82 | { 83 | code: 'describe(function() { after(function() {}); after(function() {}); });', 84 | errors: [ { message: 'Unexpected use of duplicate Mocha `after` hook', column: 45, line: 1 } ] 85 | }, 86 | { 87 | code: 'describe(function() { beforeEach(function() {}); beforeEach(function() {}); });', 88 | errors: [ { message: 'Unexpected use of duplicate Mocha `beforeEach` hook', column: 50, line: 1 } ] 89 | }, 90 | { 91 | code: 'describe(function() { afterEach(function() {}); afterEach(function() {}); });', 92 | errors: [ { message: 'Unexpected use of duplicate Mocha `afterEach` hook', column: 49, line: 1 } ] 93 | }, 94 | { 95 | code: [ 96 | 'describe(function() {', 97 | ' before(function() {});', 98 | ' describe(function() {', 99 | ' before(function() {});', 100 | ' before(function() {});', 101 | ' });', 102 | '});' 103 | ].join('\n'), 104 | errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 9, line: 5 } ] 105 | }, 106 | { 107 | code: [ 108 | 'describe(function() {', 109 | ' before(function() {});', 110 | ' describe(function() {', 111 | ' before(function() {});', 112 | ' });', 113 | ' before(function() {});', 114 | '});' 115 | ].join('\n'), 116 | errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 5, line: 6 } ] 117 | } 118 | ] 119 | 120 | }); 121 | -------------------------------------------------------------------------------- /test/rules/handle-done-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('handle-done-callback', rules['handle-done-callback'], { 8 | valid: [ 9 | 'foo(function (done) { });', 10 | 'var foo = function (done) { };', 11 | 'it();', 12 | 'it("");', 13 | 'it("", function () {});', 14 | 'it("", function () { done(); });', 15 | 'it("", function (done) { done(); });', 16 | 'it("", function () { callback(); });', 17 | 'it("", function (callback) { callback(); });', 18 | 'it("", function (done) { if (a) { done(); } });', 19 | 'it("", function (done) { function foo() { done(); } });', 20 | 'it("", function (done) { setTimeout(done, 300); });', 21 | 'it("", function (done) { done(new Error("foo")); });', 22 | 'it("", function (done) { promise.then(done).catch(done); });', 23 | 'it.only("", function (done) { done(); });', 24 | 'test("", function (done) { done(); });', 25 | 'test.only("", function (done) { done(); });', 26 | 'before(function (done) { done(); });', 27 | 'after(function (done) { done(); });', 28 | 'beforeEach(function (done) { done(); });', 29 | 'afterEach(function (done) { done(); });', 30 | 31 | { 32 | code: 'it("", (done) => { done(); });', 33 | parserOptions: { ecmaVersion: 6 } 34 | } 35 | ], 36 | 37 | invalid: [ 38 | { 39 | code: 'it("", function (done) { });', 40 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 41 | }, 42 | { 43 | code: 'it("", function (done) { callback(); });', 44 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 45 | }, 46 | { 47 | code: 'it("", function (callback) { });', 48 | errors: [ { message: 'Expected "callback" callback to be handled.', column: 18, line: 1 } ] 49 | }, 50 | { 51 | code: 'it("", function (done) { asyncFunction(function (error) { expect(error).to.be.null; }); });', 52 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 53 | }, 54 | { 55 | code: 'it.only("", function (done) { });', 56 | errors: [ { message: 'Expected "done" callback to be handled.', column: 23, line: 1 } ] 57 | }, 58 | { 59 | code: 'test("", function (done) { });', 60 | errors: [ { message: 'Expected "done" callback to be handled.', column: 20, line: 1 } ] 61 | }, 62 | { 63 | code: 'test.only("", function (done) { });', 64 | errors: [ { message: 'Expected "done" callback to be handled.', column: 25, line: 1 } ] 65 | }, 66 | { 67 | code: 'specify("", function (done) { });', 68 | errors: [ { message: 'Expected "done" callback to be handled.', column: 23, line: 1 } ] 69 | }, 70 | { 71 | code: 'specify.only("", function (done) { });', 72 | errors: [ { message: 'Expected "done" callback to be handled.', column: 28, line: 1 } ] 73 | }, 74 | { 75 | code: 'before(function (done) { });', 76 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 77 | }, 78 | { 79 | code: 'after(function (done) { });', 80 | errors: [ { message: 'Expected "done" callback to be handled.', column: 17, line: 1 } ] 81 | }, 82 | { 83 | code: 'beforeEach(function (done) { });', 84 | errors: [ { message: 'Expected "done" callback to be handled.', column: 22, line: 1 } ] 85 | }, 86 | { 87 | code: 'afterEach(function (done) { });', 88 | errors: [ { message: 'Expected "done" callback to be handled.', column: 21, line: 1 } ] 89 | }, 90 | { 91 | code: 'it("", (done) => { });', 92 | parserOptions: { ecmaVersion: 6 }, 93 | errors: [ { message: 'Expected "done" callback to be handled.', column: 9, line: 1 } ] 94 | }, 95 | { 96 | code: 'it("", function (done) { return done; });', 97 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 98 | }, 99 | { 100 | code: 'it("", function (done) { done; });', 101 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 102 | }, 103 | { 104 | code: 'it("", function (done) { var foo = done; });', 105 | errors: [ { message: 'Expected "done" callback to be handled.', column: 18, line: 1 } ] 106 | } 107 | ] 108 | }); 109 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | 6 | "parserOptions": { 7 | "ecmaVersion": 5, 8 | "ecmaFeatures": { 9 | "globalReturn": true 10 | } 11 | }, 12 | 13 | "rules": { 14 | "no-alert": 2, 15 | "no-array-constructor": 2, 16 | "no-bitwise": 2, 17 | "no-caller": 2, 18 | "no-catch-shadow": 2, 19 | "no-cond-assign": 2, 20 | "no-console": 2, 21 | "no-constant-condition": 2, 22 | "no-control-regex": 2, 23 | "no-debugger": 2, 24 | "no-delete-var": 2, 25 | "no-div-regex": 2, 26 | "no-dupe-keys": 2, 27 | "no-dupe-args": 2, 28 | "no-else-return": 2, 29 | "no-empty": 2, 30 | "no-empty-character-class": 2, 31 | "no-eq-null": 2, 32 | "no-eval": 2, 33 | "no-ex-assign": 2, 34 | "no-extend-native": 2, 35 | "no-extra-bind": 2, 36 | "no-extra-boolean-cast": 2, 37 | "no-extra-parens": 2, 38 | "no-extra-semi": 2, 39 | "no-fallthrough": 2, 40 | "no-floating-decimal": 2, 41 | "no-func-assign": 2, 42 | "no-implied-eval": 2, 43 | "no-inline-comments": 2, 44 | "no-inner-declarations": [ 2, "functions" ], 45 | "no-invalid-regexp": 2, 46 | "no-irregular-whitespace": 2, 47 | "no-iterator": 2, 48 | "no-label-var": 2, 49 | "no-labels": [2, {"allowLoop": true, "allowSwitch": true}], 50 | "no-lone-blocks": 2, 51 | "no-lonely-if": 2, 52 | "no-loop-func": 2, 53 | "no-mixed-requires": 0, 54 | "no-mixed-spaces-and-tabs": 2, 55 | "no-multi-spaces": 2, 56 | "no-multi-str": 2, 57 | "no-multiple-empty-lines": [ 2, { "max": 1 } ], 58 | "no-native-reassign": 2, 59 | "no-negated-in-lhs": 2, 60 | "no-nested-ternary": 2, 61 | "no-new": 2, 62 | "no-new-func": 2, 63 | "no-new-object": 2, 64 | "no-new-require": 2, 65 | "no-new-wrappers": 2, 66 | "no-obj-calls": 2, 67 | "no-octal": 2, 68 | "no-octal-escape": 2, 69 | "no-path-concat": 2, 70 | "no-plusplus": 2, 71 | "no-process-env": 2, 72 | "no-process-exit": 2, 73 | "no-proto": 2, 74 | "no-redeclare": 2, 75 | "no-regex-spaces": 2, 76 | "no-restricted-modules": 0, 77 | "no-return-assign": 2, 78 | "no-script-url": 2, 79 | "no-self-compare": 2, 80 | "no-sequences": 2, 81 | "no-shadow": 2, 82 | "no-shadow-restricted-names": 2, 83 | "no-spaced-func": 2, 84 | "no-sparse-arrays": 2, 85 | "no-sync": 2, 86 | "no-ternary": 0, 87 | "no-trailing-spaces": 2, 88 | "no-throw-literal": 2, 89 | "no-undef": 2, 90 | "no-undef-init": 2, 91 | "no-undefined": 2, 92 | "no-underscore-dangle": 2, 93 | "no-unreachable": 2, 94 | "no-unused-expressions": 2, 95 | "no-unused-vars": 2, 96 | "no-use-before-define": 2, 97 | "no-void": 2, 98 | "no-warning-comments": [ 2, { "terms": [ "todo", "fixme", "wtf", "falls through" ], "location": "anywhere" } ], 99 | "no-with": 2, 100 | "block-scoped-var": 0, 101 | "brace-style": [ 2, "1tbs" ], 102 | "camelcase": 2, 103 | "comma-dangle": [ 2, "never" ], 104 | "comma-spacing": [ 2, { "before": false, "after": true } ], 105 | "comma-style": [ 2, "last" ], 106 | "complexity": [ 2, 4 ], 107 | "consistent-return": 2, 108 | "consistent-this": [ 2, "self" ], 109 | "curly": [ 2, "all" ], 110 | "default-case": 2, 111 | "dot-notation": 2, 112 | "eol-last": 2, 113 | "eqeqeq": 2, 114 | "func-names": 0, 115 | "func-style": 0, 116 | "generator-star": 0, 117 | "guard-for-in": 2, 118 | "handle-callback-err": [ 2, "^(e$|(e|(.*(_e|E)))rr)" ], 119 | "indent": 0, 120 | "key-spacing": [ 2, { "beforeColon": false, "afterColon": true } ], 121 | "keyword-spacing": 2, 122 | "max-depth": [ 2, 3 ], 123 | "max-len": [ 2, 120, 4 ], 124 | "max-nested-callbacks": [ 2, 3 ], 125 | "max-params": [ 2, 4 ], 126 | "max-statements": [ 2, 10 ], 127 | "new-cap": 2, 128 | "new-parens": 2, 129 | "one-var": 2, 130 | "operator-assignment": [ 2, "always" ], 131 | "padded-blocks": [ 2, "never" ], 132 | "quote-props": [ 2, "as-needed" ], 133 | "quotes": [ 2, "single" ], 134 | "radix": 2, 135 | "semi": 2, 136 | "semi-spacing": [ 2, { "before": false, "after": true } ], 137 | "sort-vars": 0, 138 | "space-before-blocks": [ 2, "always" ], 139 | "space-before-function-paren": [ 2, { "anonymous": "always", "named": "never" } ], 140 | "object-curly-spacing": [ 2, "always" ], 141 | "array-bracket-spacing": [ 2, "always" ], 142 | "space-in-parens": [ 2, "never" ], 143 | "space-infix-ops": 2, 144 | "space-unary-ops": 2, 145 | "spaced-comment": [ 2, "always" ], 146 | "strict": [2, "global"], 147 | "use-isnan": 2, 148 | "valid-jsdoc": 0, 149 | "valid-typeof": 2, 150 | "vars-on-top": 2, 151 | "wrap-iife": [ 2, "outside" ], 152 | "wrap-regex": 2, 153 | "yoda": [ 2, "never" ] 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test/rules/no-skipped-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(), 6 | expectedErrorMessage = 'Unexpected skipped mocha test.'; 7 | 8 | ruleTester.run('no-skipped-tests', rules['no-skipped-tests'], { 9 | 10 | valid: [ 11 | 'describe()', 12 | 'it()', 13 | 'describe.only()', 14 | 'it.only()', 15 | 'suite()', 16 | 'test()', 17 | 'suite.only()', 18 | 'test.only()', 19 | 'context()', 20 | 'context.only()', 21 | 'var appliedOnly = describe.skip; appliedOnly.apply(describe)', 22 | 'var calledOnly = it.skip; calledOnly.call(it)', 23 | 'var dynamicOnly = "skip"; suite[dynamicOnly]()' 24 | ], 25 | 26 | invalid: [ 27 | { 28 | code: 'describe.skip()', 29 | errors: [ { message: expectedErrorMessage, column: 10, line: 1 } ], 30 | output: 'describe()' 31 | }, 32 | { 33 | code: 'describe["skip"]()', 34 | errors: [ { message: expectedErrorMessage, column: 10, line: 1 } ], 35 | output: 'describe()' 36 | }, 37 | { 38 | code: 'xdescribe()', 39 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 40 | output: 'describe()' 41 | }, 42 | { 43 | code: 'it.skip()', 44 | errors: [ { message: expectedErrorMessage, column: 4, line: 1 } ], 45 | output: 'it()' 46 | }, 47 | { 48 | code: 'it["skip"]()', 49 | errors: [ { message: expectedErrorMessage, column: 4, line: 1 } ], 50 | output: 'it()' 51 | }, 52 | { 53 | code: 'xit()', 54 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 55 | output: 'it()' 56 | }, 57 | { 58 | code: 'suite.skip()', 59 | errors: [ { message: expectedErrorMessage, column: 7, line: 1 } ], 60 | output: 'suite()' 61 | }, 62 | { 63 | code: 'suite["skip"]()', 64 | errors: [ { message: expectedErrorMessage, column: 7, line: 1 } ], 65 | output: 'suite()' 66 | }, 67 | { 68 | code: 'test.skip()', 69 | errors: [ { message: expectedErrorMessage, column: 6, line: 1 } ], 70 | output: 'test()' 71 | }, 72 | { 73 | code: 'test["skip"]()', 74 | errors: [ { message: expectedErrorMessage, column: 6, line: 1 } ], 75 | output: 'test()' 76 | }, 77 | { 78 | code: 'context.skip()', 79 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ], 80 | output: 'context()' 81 | }, 82 | { 83 | code: 'context["skip"]()', 84 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ], 85 | output: 'context()' 86 | }, 87 | { 88 | code: 'xcontext()', 89 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 90 | output: 'context()' 91 | }, 92 | { 93 | code: 'specify.skip()', 94 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ], 95 | output: 'specify()' 96 | }, 97 | { 98 | code: 'xspecify()', 99 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 100 | output: 'specify()' 101 | }, 102 | { 103 | code: 'custom.skip()', 104 | settings: { 105 | 'mocha/additionalTestFunctions': [ 'custom' ] 106 | }, 107 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ], 108 | output: 'custom()' 109 | }, 110 | { 111 | code: 'custom["skip"]()', 112 | settings: { 113 | 'mocha/additionalTestFunctions': [ 'custom' ] 114 | }, 115 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ], 116 | output: 'custom()' 117 | }, 118 | { 119 | code: 'xcustom()', 120 | settings: { 121 | 'mocha/additionalXFunctions': [ 'xcustom' ] 122 | }, 123 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 124 | output: 'custom()' 125 | }, 126 | { 127 | code: 'custom.skip()', 128 | settings: { 129 | mocha: { 130 | additionalTestFunctions: [ 'custom' ] 131 | } 132 | }, 133 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ], 134 | output: 'custom()' 135 | }, 136 | { 137 | code: 'custom["skip"]()', 138 | settings: { 139 | mocha: { 140 | additionalTestFunctions: [ 'custom' ] 141 | } 142 | }, 143 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ], 144 | output: 'custom()' 145 | }, 146 | { 147 | code: 'xcustom()', 148 | settings: { 149 | mocha: { 150 | additionalXFunctions: [ 'xcustom' ] 151 | } 152 | }, 153 | errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ], 154 | output: 'custom()' 155 | } 156 | 157 | ] 158 | 159 | }); 160 | 161 | -------------------------------------------------------------------------------- /test/rules/no-return-and-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(), 6 | message = 'Unexpected use of `return` in a test with callback', 7 | es6parserOptions = { 8 | sourceType: 'module', 9 | ecmaVersion: 6 10 | }; 11 | 12 | ruleTester.run('no-return-and-callback', rules['no-return-and-callback'], { 13 | 14 | valid: [ 15 | 'it("title", function(done) { done(); });', 16 | 'it("title", function(done) { foo.then(function() { return done(); }); });', 17 | 'it("title", function(done) { foo(function() { return done(); }); });', 18 | 'it("title", function() { return foo(); });', 19 | 'it.only("title", function(done) { done(); });', 20 | 'it.only("title", function(done) { foo.then(function() { return done(); }); });', 21 | 'it.only("title", function(done) { foo(function() { return done(); }); });', 22 | 'it.only("title", function() { return foo(); });', 23 | 'before("title", function(done) { done(); });', 24 | 'before("title", function(done) { foo.then(function() { return done(); }); });', 25 | 'before("title", function(done) { foo(function() { return done(); }); });', 26 | 'before("title", function() { return foo(); });', 27 | 'after("title", function(done) { done(); });', 28 | 'after("title", function(done) { foo.then(function() { return done(); }); });', 29 | 'after("title", function(done) { foo(function() { return done(); }); });', 30 | 'after("title", function() { return foo(); });', 31 | 'function foo(done) { return foo.then(done); }', 32 | 'var foo = function(done) { return foo.then(done); }', 33 | 'notMocha("title", function(done) { return foo.then(done); })', 34 | { 35 | code: 'it("title", (done) => { done(); });', 36 | parserOptions: es6parserOptions 37 | }, 38 | { 39 | code: 'it("title", (done) => { foo.then(function() { return done(); }); });', 40 | parserOptions: es6parserOptions 41 | }, 42 | { 43 | code: 'it("title", (done) => { foo(function() { return done(); }); });', 44 | parserOptions: es6parserOptions 45 | }, 46 | { 47 | code: 'it("title", () => { return foo(); });', 48 | parserOptions: es6parserOptions 49 | }, 50 | // Allowed return statements 51 | 'it("title", function(done) { return; });', 52 | 'it("title", function(done) { return undefined; });', 53 | 'it("title", function(done) { return null; });', 54 | 'it("title", function(done) { return "3"; });', 55 | 'it("title", function(done) { return done(); });', 56 | 'it("title", function(done) { return done(error); });' 57 | ], 58 | 59 | invalid: [ 60 | { 61 | code: 'it("title", function(done) { return foo.then(done); });', 62 | errors: [ { message: message, column: 30, line: 1 } ] 63 | }, 64 | { 65 | code: 'it("title", function(done) { return foo.then(function() { done(); }).catch(done); });', 66 | errors: [ { message: message, column: 30, line: 1 } ] 67 | }, 68 | { 69 | code: 'it("title", function(done) { var foo = bar(); return foo.then(function() { done(); }); });', 70 | errors: [ { message: message, column: 47, line: 1 } ] 71 | }, 72 | { 73 | code: 'it("title", (done) => { return foo.then(function() { done(); }).catch(done); });', 74 | errors: [ { message: message, column: 25, line: 1 } ], 75 | parserOptions: es6parserOptions 76 | }, 77 | { 78 | code: 'it("title", (done) => foo.then(function() { done(); }));', 79 | errors: [ { message: 'Confusing implicit return in a test with callback', column: 23, line: 1 } ], 80 | parserOptions: es6parserOptions 81 | }, 82 | { 83 | code: 'it.only("title", function(done) { return foo.then(done); });', 84 | errors: [ { message: message, column: 35, line: 1 } ] 85 | }, 86 | { 87 | code: 'before("title", function(done) { return foo.then(done); });', 88 | errors: [ { message: message, column: 34, line: 1 } ] 89 | }, 90 | { 91 | code: 'beforeEach("title", function(done) { return foo.then(done); });', 92 | errors: [ { message: message, column: 38, line: 1 } ] 93 | }, 94 | { 95 | code: 'after("title", function(done) { return foo.then(done); });', 96 | errors: [ { message: message, column: 33, line: 1 } ] 97 | }, 98 | { 99 | code: 'afterEach("title", function(done) { return foo.then(done); });', 100 | errors: [ { message: message, column: 37, line: 1 } ] 101 | }, 102 | { 103 | code: 'afterEach("title", function(done) { return foo; });', 104 | errors: [ { message: message, column: 37, line: 1 } ] 105 | }, 106 | { 107 | code: 'afterEach("title", function(done) { return done; });', 108 | errors: [ { message: message, column: 37, line: 1 } ] 109 | }, 110 | { 111 | code: 'afterEach("title", function(done) { return done.foo(); });', 112 | errors: [ { message: message, column: 37, line: 1 } ] 113 | }, 114 | { 115 | code: 'afterEach("title", function(done) { return foo.done(); });', 116 | errors: [ { message: message, column: 37, line: 1 } ] 117 | }, 118 | { 119 | code: 'afterEach("title", function(end) { return done(); });', 120 | errors: [ { message: message, column: 36, line: 1 } ] 121 | } 122 | ] 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /test/rules/no-exclusive-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(), 6 | expectedErrorMessage = 'Unexpected exclusive mocha test.'; 7 | 8 | ruleTester.run('no-exclusive-tests', rules['no-exclusive-tests'], { 9 | 10 | valid: [ 11 | 'describe()', 12 | 'it()', 13 | 'describe.skip()', 14 | 'it.skip()', 15 | 'suite()', 16 | 'test()', 17 | 'suite.skip()', 18 | 'test.skip()', 19 | 'context()', 20 | 'context.skip()', 21 | 'var appliedOnly = describe.only; appliedOnly.apply(describe)', 22 | 'var calledOnly = it.only; calledOnly.call(it)', 23 | 'var dynamicOnly = "only"; suite[dynamicOnly]()', 24 | 'specify()', 25 | 'specify.skip()', 26 | { 27 | code: 'a.b.c.skip()', 28 | settings: { 29 | mocha: { 30 | additionalTestFunctions: [ 'a.b.c' ] 31 | } 32 | } 33 | }, 34 | { 35 | code: 'a[b].c.skip()', 36 | settings: { 37 | mocha: { 38 | additionalTestFunctions: [ 'a.b.c' ] 39 | } 40 | } 41 | } 42 | ], 43 | 44 | invalid: [ 45 | { 46 | code: 'describe.only()', 47 | errors: [ { message: expectedErrorMessage, column: 10, line: 1 } ] 48 | }, 49 | { 50 | code: 'describe["only"]()', 51 | errors: [ { message: expectedErrorMessage, column: 10, line: 1 } ] 52 | }, 53 | { 54 | code: 'it.only()', 55 | errors: [ { message: expectedErrorMessage, column: 4, line: 1 } ] 56 | }, 57 | { 58 | code: 'it["only"]()', 59 | errors: [ { message: expectedErrorMessage, column: 4, line: 1 } ] 60 | }, 61 | { 62 | code: 'suite.only()', 63 | errors: [ { message: expectedErrorMessage, column: 7, line: 1 } ] 64 | }, 65 | { 66 | code: 'suite["only"]()', 67 | errors: [ { message: expectedErrorMessage, column: 7, line: 1 } ] 68 | }, 69 | { 70 | code: 'test.only()', 71 | errors: [ { message: expectedErrorMessage, column: 6, line: 1 } ] 72 | }, 73 | { 74 | code: 'test["only"]()', 75 | errors: [ { message: expectedErrorMessage, column: 6, line: 1 } ] 76 | }, 77 | { 78 | code: 'context.only()', 79 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 80 | }, 81 | { 82 | code: 'context["only"]()', 83 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 84 | }, 85 | { 86 | code: 'specify.only()', 87 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 88 | }, 89 | { 90 | code: 'specify["only"]()', 91 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 92 | }, 93 | { 94 | code: 'custom.only()', 95 | settings: { 96 | 'mocha/additionalTestFunctions': [ 'custom' ] 97 | }, 98 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ] 99 | }, 100 | { 101 | code: 'custom["only"]()', 102 | settings: { 103 | 'mocha/additionalTestFunctions': [ 'custom' ] 104 | }, 105 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ] 106 | }, 107 | { 108 | code: 'custom.only()', 109 | settings: { 110 | mocha: { 111 | additionalTestFunctions: [ 'custom' ] 112 | } 113 | }, 114 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ] 115 | }, 116 | { 117 | code: 'custom["only"]()', 118 | settings: { 119 | mocha: { 120 | additionalTestFunctions: [ 'custom' ] 121 | } 122 | }, 123 | errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ] 124 | }, 125 | { 126 | code: 'foo.bar.only()', 127 | settings: { 128 | mocha: { 129 | additionalTestFunctions: [ 'foo.bar' ] 130 | } 131 | }, 132 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 133 | }, 134 | { 135 | code: 'foo.bar["only"]()', 136 | settings: { 137 | mocha: { 138 | additionalTestFunctions: [ 'foo.bar' ] 139 | } 140 | }, 141 | errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] 142 | }, 143 | { 144 | code: 'foo["bar"].only()', 145 | settings: { 146 | mocha: { 147 | additionalTestFunctions: [ 'foo.bar' ] 148 | } 149 | }, 150 | errors: [ { message: expectedErrorMessage, column: 12, line: 1 } ] 151 | }, 152 | { 153 | code: 'foo["bar"]["only"]()', 154 | settings: { 155 | mocha: { 156 | additionalTestFunctions: [ 'foo.bar' ] 157 | } 158 | }, 159 | errors: [ { message: expectedErrorMessage, column: 12, line: 1 } ] 160 | }, 161 | { 162 | code: 'a.b.c.only()', 163 | settings: { 164 | mocha: { 165 | additionalTestFunctions: [ 'a.b.c' ] 166 | } 167 | }, 168 | errors: [ { message: expectedErrorMessage, column: 7, line: 1 } ] 169 | } 170 | ] 171 | }); 172 | -------------------------------------------------------------------------------- /test/rules/no-identical-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-identical-title', rules['no-identical-title'], { 8 | 9 | valid: [ 10 | [ 11 | 'describe("describe", function() {', 12 | ' it("it", function() {});', 13 | '});' 14 | ].join('\n'), 15 | [ 16 | 'describe("describe1", function() {', 17 | ' it("it1", function() {});', 18 | ' it("it2", function() {});', 19 | '});' 20 | ].join('\n'), 21 | [ 22 | 'it("it1", function() {});', 23 | 'it("it2", function() {});' 24 | ].join('\n'), 25 | [ 26 | 'it.only("it1", function() {});', 27 | 'it("it2", function() {});' 28 | ].join('\n'), 29 | [ 30 | 'it.only("it1", function() {});', 31 | 'it.only("it2", function() {});' 32 | ].join('\n'), 33 | [ 34 | 'describe("title", function() {});', 35 | 'it("title", function() {});' 36 | ].join('\n'), 37 | [ 38 | 'describe("describe1", function() {', 39 | ' it("it1", function() {});', 40 | ' describe("describe2", function() {', 41 | ' it("it1", function() {});', 42 | ' });', 43 | '});' 44 | ].join('\n'), 45 | [ 46 | 'describe("describe1", function() {', 47 | ' describe("describe2", function() {', 48 | ' it("it1", function() {});', 49 | ' });', 50 | ' it("it1", function() {});', 51 | '});' 52 | ].join('\n'), 53 | [ 54 | 'describe("describe1", function() {', 55 | ' describe("describe2", function() {});', 56 | '});' 57 | ].join('\n'), 58 | [ 59 | 'describe("describe1", function() {', 60 | ' describe("describe2", function() {});', 61 | '});', 62 | 'describe("describe2", function() {});' 63 | ].join('\n'), 64 | [ 65 | 'describe("describe1", function() {});', 66 | 'describe("describe2", function() {});' 67 | ].join('\n'), 68 | [ 69 | 'it("it" + n, function() {});', 70 | 'it("it" + n, function() {});' 71 | ].join('\n'), 72 | { 73 | code: [ 74 | 'it(`it${n}`, function() {});', 75 | 'it(`it${n}`, function() {});' 76 | ].join('\n'), 77 | env: { 78 | es6: true 79 | } 80 | }, 81 | [ 82 | 'describe("title " + foo, function() {', 83 | ' describe("describe1", function() {});', 84 | '});', 85 | 'describe("describe1", function() {});' 86 | ].join('\n'), 87 | [ 88 | 'describe("describe1", function() {', 89 | ' describe("describe2", function() {});', 90 | ' describe("title " + foo, function() {', 91 | ' describe("describe2", function() {});', 92 | ' });', 93 | '});' 94 | ].join('\n') 95 | ], 96 | 97 | invalid: [ 98 | { 99 | code: [ 100 | 'describe("describe1", function() {', 101 | ' it("it1", function() {});', 102 | ' it("it1", function() {});', 103 | '});' 104 | ].join('\n'), 105 | errors: [ { message: 'Test title is used multiple times in the same test suite.', column: 4, line: 3 } ] 106 | }, 107 | { 108 | code: [ 109 | 'it("it1", function() {});', 110 | 'it("it1", function() {});' 111 | ].join('\n'), 112 | errors: [ { message: 'Test title is used multiple times in the same test suite.', column: 1, line: 2 } ] 113 | }, 114 | { 115 | code: [ 116 | 'it.only("it1", function() {});', 117 | 'it("it1", function() {});' 118 | ].join('\n'), 119 | errors: [ { message: 'Test title is used multiple times in the same test suite.', column: 1, line: 2 } ] 120 | }, 121 | { 122 | code: [ 123 | 'it.only("it1", function() {});', 124 | 'it.only("it1", function() {});' 125 | ].join('\n'), 126 | errors: [ { message: 'Test title is used multiple times in the same test suite.', column: 1, line: 2 } ] 127 | }, 128 | { 129 | code: [ 130 | 'it("it1", function() {});', 131 | 'specify("it1", function() {});' 132 | ].join('\n'), 133 | errors: [ { message: 'Test title is used multiple times in the same test suite.', column: 1, line: 2 } ] 134 | }, 135 | { 136 | code: [ 137 | 'describe("describe1", function() {});', 138 | 'describe("describe1", function() {});' 139 | ].join('\n'), 140 | errors: [ { message: 'Test suite title is used multiple times.', column: 1, line: 2 } ] 141 | }, 142 | { 143 | code: [ 144 | 'describe("describe1", function() {});', 145 | 'xdescribe("describe1", function() {});' 146 | ].join('\n'), 147 | errors: [ { message: 'Test suite title is used multiple times.', column: 1, line: 2 } ] 148 | }, 149 | { 150 | code: [ 151 | 'describe("describe1", function() {', 152 | ' describe("describe2", function() {});', 153 | '});', 154 | 'describe("describe1", function() {});' 155 | ].join('\n'), 156 | errors: [ { message: 'Test suite title is used multiple times.', column: 1, line: 4 } ] 157 | } 158 | ] 159 | 160 | }); 161 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 4.8.0 (December 23, 2016) 2 | 3 | ### Enhancements 4 | 5 | * Support MemberExpression for additionalTestFunctions (#114) 6 | * Make no-mocha-arrows rule fixable (#112) 7 | 8 | ### Bug Fixes 9 | 10 | * Fix no-mocha-arrow fixer (#118) 11 | 12 | ### Build-Related 13 | 14 | * Add node 7 as travis build environment (#115) 15 | 16 | ### Documentation 17 | 18 | * Fix rule name in CHANGELOG to match actual rule (#111) 19 | 20 | ## 4.7.0 (October 12, 2016) 21 | 22 | ### Features 23 | 24 | * Add no-nested-tests rule (#109) 25 | 26 | ## 4.6.0 (October 3, 2016) 27 | 28 | ### Documentation 29 | 30 | * Adds rule name to title for `valid-suite-description` documentation. (#107) 31 | * Adds rule name to title for `valid-test-description` documentation. (#106) 32 | 33 | ### Features 34 | 35 | * Add 'max-top-level-suites' rule (#103) (#105) 36 | 37 | ## 4.5.1 (August 30, 2016) 38 | 39 | ### Bug Fixes 40 | 41 | * Fix crash in no-identical-title (fixes #98) (#99) 42 | 43 | ## 4.5.0 (August 29, 2016) 44 | 45 | ### Features 46 | 47 | * Add `no-identical-title` rule (fixes #33) (#97) 48 | 49 | ## 4.4.0 (August 24, 2016) 50 | 51 | ### Features 52 | 53 | * Add `no-hooks-for-single-case` rule (fixes #44) (#95) 54 | * Add rule `no-return-and-callback` (fixes #88) (#94) 55 | * Add `no-top-level-hooks` rule (fixes #37) (#87) 56 | 57 | ### Documentation 58 | 59 | * Fix title in `no-sibling-hooks` documentation file (#92) 60 | 61 | ### Dependency Upgrades 62 | 63 | * Update ramda to version 0.22.1 🚀 (#93) 64 | 65 | ### Build-Related 66 | 67 | * Add editorconfig file (#91) 68 | 69 | ## 4.3.0 (August 1, 2016) 70 | 71 | ### Dependency Upgrades 72 | 73 | * Update mocha to version 3.0.0 🚀 (#86) 74 | 75 | ### Features 76 | 77 | * Add rule `no-sibling-hooks` (fixes #82) (#85) 78 | * Add rule `no-hooks` (fixes #39) (#84) 79 | 80 | ## 4.2.0 (July 26, 2016) 81 | 82 | ### Features 83 | 84 | * Allow custom test functions (#81) 85 | 86 | ## 4.1.0 (July 22, 2016) 87 | 88 | ### Features 89 | 90 | * no-mocha-arrows: New rule (#78) 91 | 92 | ## 4.0.0 (July 4, 2016) 93 | 94 | ### Features 95 | 96 | * feat(rules): add 'valid-suite-description' rule (#74) 97 | * feat(rules): add 'valid-test-description' rule (#68) 98 | 99 | ### Enhancements 100 | 101 | * Add recommended config (#72) 102 | 103 | ### Dependency Upgrades 104 | 105 | * Update eslint to version 3.0.0 🚀 (#70) 106 | 107 | ### Breaking Changes 108 | 109 | * Drop support old node versions (#71) 110 | 111 | ### Documentation 112 | 113 | * Remove fixable from no-exclusive on README (#73) 114 | * [README] Use a more explicit config (#65) 115 | * update to docs to match removed autofix (#64) 116 | 117 | ## 3.0.0 (June 2, 2016) 118 | 119 | ### Breaking Changes 120 | 121 | * Remove autofix on no-exclusive-tests rule. (#63) 122 | 123 | ## 2.2.0 (April 14, 2016) 124 | 125 | ### Features 126 | 127 | * Add rule no-pending-tests (#59) 128 | 129 | ## 2.1.0 (April 11, 2016) 130 | 131 | ### Bug Fixes 132 | 133 | * Support specify alias (#58) 134 | 135 | ### Dependency Upgrades 136 | 137 | * Update ramda to version 0.21.0 🚀 (#56) 138 | * Update ramda to version 0.20.0 🚀 (#53) 139 | 140 | ### Features 141 | 142 | * Add rule no-skipped-tests (#55) 143 | 144 | ## 2.0.0 (February 13, 2016) 145 | 146 | ### Breaking Changes 147 | 148 | * Update to eslint 2.0.0 (#49) 149 | 150 | ## 1.1.0 (November 13, 2015) 151 | 152 | ### Features 153 | 154 | * Implement new rule no-global-tests (#46) 155 | 156 | ### Enhancements 157 | 158 | * Replace lodash with ramda (#45) 159 | 160 | ## 1.0.0 (September 17, 2015) 161 | 162 | ### Enhancements 163 | 164 | * Implement autofix for no-exclusive-tests (#34) 165 | * Improve detection if done callback is handled (#23) 166 | * Add integration tests (#30) 167 | * Instrumment all sources for coverage (#29) 168 | 169 | ### Build-Related 170 | 171 | * Add node 4 to travis-ci build (#42) 172 | 173 | ### Dependency Upgrades 174 | 175 | * Update devDependencies (#43) 176 | * Update eslint (#31) 177 | 178 | ### Documentation 179 | 180 | * Add NPM Downloads badge (#41) 181 | * Badges in README.md should only show master status (#40) 182 | 183 | ## 0.5.1 (August 20, 2015) 184 | 185 | ### Bug Fixes 186 | 187 | * add new rule to index.js and change tests to keep that from happening (#28) 188 | 189 | ## 0.5.0 (August 19, 2015) 190 | 191 | ### Features 192 | 193 | * Add no-synchronous-tests rule (#26) 194 | 195 | ### Dependency Upgrades 196 | 197 | * ESLint 1.x compatibility (#25) 198 | * Update dependencies (#22) 199 | 200 | 201 | ## 0.4.0 (June 26, 2015) 202 | 203 | ### Enhancements 204 | 205 | * add context.only to no-exclusive-tests rule (#21) 206 | 207 | 208 | ## 0.3.0 (June 23, 2015) 209 | 210 | ### Features 211 | 212 | * Add new rule handle-done-callback (#15) 213 | 214 | ### Build-Related 215 | 216 | * Refactor package.json scripts (#17) 217 | * Disable sudo on travis-ci (#10) 218 | * Run travis build on node 0.12 and iojs (#11) 219 | * Ignore log files and .idea folder (#9) 220 | * Add changelog (#8) 221 | 222 | ### Documentation 223 | 224 | * Fix links to mocha website (#16) 225 | * Add install documentation to README (#14) 226 | 227 | ### Dependency Upgrades 228 | 229 | * Update dependencies (#18) 230 | * Update pr-log (#13) 231 | * Update eslint (#12) 232 | * Update dev dependencies (#7) 233 | 234 | 235 | ## 0.2.2 (October 25, 2014) 236 | 237 | ### Bug Fixes 238 | 239 | * Allow all ESLint versions >= 0.8.0 240 | 241 | ## 0.2.1 (October 18, 2014) 242 | 243 | ### Build-Related 244 | 245 | * Add recommended keywords to package.json 246 | 247 | ## 0.2.0 (September 20, 2014) 248 | 249 | ### Enhancements 250 | 251 | * Support mochas tdd interface (fixes #4) 252 | 253 | ### Build-Related 254 | 255 | * Allow minor version updates for eslint 256 | 257 | ### Documentation 258 | 259 | * Docs: remove unnecessary backtick 260 | 261 | ### Dependency Upgrades 262 | 263 | * Update devDependencies. 264 | 265 | 266 | ## 0.1.1 (September 6, 2014) 267 | 268 | ### Build-Related 269 | 270 | * Add .npmignore 271 | 272 | ## 0.1.0 (September 6, 2014) 273 | 274 | Initial release 275 | -------------------------------------------------------------------------------- /test/rules/max-top-level-suites.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('max-top-level-suites', rules['max-top-level-suites'], { 8 | valid: [ 9 | { 10 | code: 'describe("This is a test", function () { });' 11 | }, 12 | { 13 | code: 'context("This is a test", function () { });' 14 | }, 15 | { 16 | code: 'suite("This is a test", function () { });' 17 | }, 18 | { 19 | code: 'describe("This is a test", function () { describe("This is a different test", function () { }) });' 20 | }, 21 | { 22 | code: 'context("This is a test", function () { context("This is a different test", function () { }) });' 23 | }, 24 | { 25 | code: 'suite("This is a test", function () { suite("This is a different test", function () { }) });' 26 | }, 27 | { 28 | options: [ { limit: 2 } ], 29 | code: 'describe("This is a test", function () { });' 30 | }, 31 | { 32 | options: [ { limit: 1 } ], 33 | code: 'someOtherFunction();' 34 | }, 35 | { 36 | options: [ { limit: 0 } ], 37 | code: 'someOtherFunction();' 38 | }, 39 | { 40 | options: [ { } ], 41 | code: 'someOtherFunction();' 42 | }, 43 | 'someOtherFunction();' 44 | ], 45 | 46 | invalid: [ 47 | { 48 | code: 'describe("this is a test", function () { });' + 49 | 'describe("this is a different test", function () { });', 50 | errors: [ 51 | { message: 'The number of top-level suites is more than 1.' } 52 | ] 53 | }, 54 | { 55 | code: 'describe("this is a test", function () { });' + 56 | 'describe("this is a different test", function () { });' + 57 | 'describe("this is an another different test", function () { });', 58 | errors: [ 59 | { message: 'The number of top-level suites is more than 1.' } 60 | ] 61 | }, 62 | { 63 | code: 'context("this is a test", function () { });' + 64 | 'context("this is a different test", function () { });', 65 | errors: [ 66 | { message: 'The number of top-level suites is more than 1.' } 67 | ] 68 | }, 69 | { 70 | code: 'suite("this is a test", function () { });' + 71 | 'suite("this is a different test", function () { });', 72 | errors: [ 73 | { message: 'The number of top-level suites is more than 1.' } 74 | ] 75 | }, 76 | { 77 | code: 'describe("this is a test", function () { }); context("this is a test", function () { });', 78 | errors: [ 79 | { message: 'The number of top-level suites is more than 1.' } 80 | ] 81 | }, 82 | { 83 | code: 'suite("this is a test", function () { }); context("this is a test", function () { });', 84 | errors: [ 85 | { message: 'The number of top-level suites is more than 1.' } 86 | ] 87 | }, 88 | { 89 | code: 'describe("this is a test", function () { });' + 90 | 'someOtherFunction();' + 91 | 'describe("this is a different test", function () { });', 92 | errors: [ 93 | { message: 'The number of top-level suites is more than 1.' } 94 | ] 95 | }, 96 | { 97 | code: 'someOtherFunction();' + 98 | 'describe("this is a test", function () { });' + 99 | 'describe("this is a different test", function () { });', 100 | errors: [ 101 | { message: 'The number of top-level suites is more than 1.' } 102 | ] 103 | }, 104 | { 105 | options: [ { limit: 2 } ], 106 | code: 'describe.skip("this is a test", function () { });' + 107 | 'describe.only("this is a different test", function () { });' + 108 | 'describe("this is a whole different test", function () { });', 109 | errors: [ 110 | { message: 'The number of top-level suites is more than 2.' } 111 | ] 112 | }, 113 | { 114 | options: [ { limit: 1 } ], 115 | code: 'xdescribe("this is a test", function () { });' + 116 | 'describe.only("this is a different test", function () { });' + 117 | 'describe("this is a whole different test", function () { });', 118 | errors: [ 119 | { message: 'The number of top-level suites is more than 1.' } 120 | ] 121 | }, 122 | { 123 | options: [ { limit: 2 } ], 124 | code: 'suite.skip("this is a test", function () { });' + 125 | 'suite.only("this is a different test", function () { });' + 126 | 'suite("this is a whole different test", function () { });', 127 | errors: [ 128 | { message: 'The number of top-level suites is more than 2.' } 129 | ] 130 | }, 131 | { 132 | options: [ { limit: 2 } ], 133 | code: 'context.skip("this is a test", function () { });' + 134 | 'context.only("this is a different test", function () { });' + 135 | 'context("this is a whole different test", function () { });', 136 | errors: [ 137 | { message: 'The number of top-level suites is more than 2.' } 138 | ] 139 | }, 140 | { 141 | options: [ { limit: 0 } ], 142 | code: 'describe("this is a test", function () { });', 143 | errors: [ 144 | { message: 'The number of top-level suites is more than 0.' } 145 | ] 146 | }, 147 | { 148 | options: [ { } ], 149 | code: 'describe("this is a test", function () { });' + 150 | 'describe.only("this is a different test", function () { });', 151 | errors: [ 152 | { message: 'The number of top-level suites is more than 1.' } 153 | ] 154 | } 155 | ] 156 | }); 157 | -------------------------------------------------------------------------------- /test/rules/no-hooks-for-single-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RuleTester = require('eslint').RuleTester, 4 | rules = require('../../').rules, 5 | ruleTester = new RuleTester(); 6 | 7 | ruleTester.run('no-hooks-for-single-case', rules['no-hooks-for-single-case'], { 8 | 9 | valid: [ 10 | [ 11 | 'describe(function() {', 12 | ' it(function() {});', 13 | '});' 14 | ].join('\n'), 15 | [ 16 | 'describe(function() {', 17 | ' before(function() {});', 18 | ' it(function() {});', 19 | ' it(function() {});', 20 | '});' 21 | ].join('\n'), 22 | [ 23 | 'describe(function() {', 24 | ' after(function() {});', 25 | ' it(function() {});', 26 | ' it(function() {});', 27 | '});' 28 | ].join('\n'), 29 | [ 30 | 'describe(function() {', 31 | ' beforeEach(function() {});', 32 | ' it(function() {});', 33 | ' it(function() {});', 34 | '});' 35 | ].join('\n'), 36 | [ 37 | 'describe(function() {', 38 | ' afterEach(function() {});', 39 | ' it(function() {});', 40 | ' it(function() {});', 41 | '});' 42 | ].join('\n'), 43 | [ 44 | 'before(function() {});', 45 | 'it(function() {});', 46 | 'it(function() {});' 47 | ].join('\n'), 48 | [ 49 | 'describe(function() {', 50 | ' before(function() {});', 51 | ' it(function() {});', 52 | ' describe(function() {});', 53 | '});' 54 | ].join('\n'), 55 | [ 56 | 'describe(function() {', 57 | ' before(function() {});', 58 | ' it(function() {});', 59 | ' xdescribe(function() {});', 60 | '});' 61 | ].join('\n'), 62 | [ 63 | 'describe(function() {', 64 | ' before(function() {});', 65 | ' describe(function() {});', 66 | ' describe(function() {});', 67 | '});' 68 | ].join('\n'), 69 | [ 70 | 'describe(function() {', 71 | ' before(function() {});', 72 | ' it.only(function() {});', 73 | ' it(function() {});', 74 | '});' 75 | ].join('\n'), 76 | [ 77 | 'describe(function() {', 78 | ' before(function() {});', 79 | ' it(function() {});', 80 | ' describe(function() {', 81 | ' before(function() {});', 82 | ' it(function() {});', 83 | ' it(function() {});', 84 | ' });', 85 | '});' 86 | ].join('\n'), 87 | { 88 | code: [ 89 | 'describe(function() {', 90 | ' before(function() {});', 91 | ' it(function() {});', 92 | '});' 93 | ].join('\n'), 94 | options: [ { allow: [ 'before' ] } ] 95 | }, 96 | { 97 | code: [ 98 | 'describe(function() {', 99 | ' after(function() {});', 100 | ' it(function() {});', 101 | '});' 102 | ].join('\n'), 103 | options: [ { allow: [ 'after' ] } ] 104 | }, 105 | { 106 | code: [ 107 | 'describe(function() {', 108 | ' beforeEach(function() {});', 109 | ' it(function() {});', 110 | '});' 111 | ].join('\n'), 112 | options: [ { allow: [ 'beforeEach' ] } ] 113 | }, 114 | { 115 | code: [ 116 | 'describe(function() {', 117 | ' afterEach(function() {});', 118 | ' it(function() {});', 119 | '});' 120 | ].join('\n'), 121 | options: [ { allow: [ 'afterEach' ] } ] 122 | }, 123 | { 124 | code: [ 125 | 'describe(function() {', 126 | ' after(function() {});', 127 | ' it(function() {});', 128 | '});' 129 | ].join('\n'), 130 | options: [ { allow: [ 'after', 'afterEach' ] } ] 131 | } 132 | ], 133 | 134 | invalid: [ 135 | { 136 | code: [ 137 | 'describe(function() {', 138 | ' before(function() {});', 139 | '});' 140 | ].join('\n'), 141 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 142 | }, 143 | { 144 | code: [ 145 | 'describe(function() {', 146 | ' before(function() {});', 147 | ' it(function() {});', 148 | '});' 149 | ].join('\n'), 150 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 151 | }, 152 | { 153 | code: [ 154 | 'describe(function() {', 155 | ' it(function() {});', 156 | ' before(function() {});', 157 | '});' 158 | ].join('\n'), 159 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 3 } ] 160 | }, 161 | { 162 | code: [ 163 | 'describe(function() {', 164 | ' after(function() {});', 165 | ' it(function() {});', 166 | '});' 167 | ].join('\n'), 168 | errors: [ { message: 'Unexpected use of Mocha `after` hook for a single test case', column: 5, line: 2 } ] 169 | }, 170 | { 171 | code: [ 172 | 'describe(function() {', 173 | ' beforeEach(function() {});', 174 | ' it(function() {});', 175 | '});' 176 | ].join('\n'), 177 | errors: [ 178 | { message: 'Unexpected use of Mocha `beforeEach` hook for a single test case', column: 5, line: 2 } 179 | ] 180 | }, 181 | { 182 | code: [ 183 | 'describe(function() {', 184 | ' afterEach(function() {});', 185 | ' it(function() {});', 186 | '});' 187 | ].join('\n'), 188 | errors: [ 189 | { message: 'Unexpected use of Mocha `afterEach` hook for a single test case', column: 5, line: 2 } 190 | ] 191 | }, 192 | { 193 | code: [ 194 | 'before(function() {});', 195 | 'it(function() {});' 196 | ].join('\n'), 197 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 1, line: 1 } ] 198 | }, 199 | { 200 | code: [ 201 | 'describe(function() {', 202 | ' before(function() {});', 203 | ' describe(function() {});', 204 | '});' 205 | ].join('\n'), 206 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 207 | }, 208 | { 209 | code: [ 210 | 'describe(function() {', 211 | ' before(function() {});', 212 | ' xdescribe(function() {});', 213 | '});' 214 | ].join('\n'), 215 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 216 | }, 217 | { 218 | code: [ 219 | 'describe(function() {', 220 | ' before(function() {});', 221 | ' it.only(function() {});', 222 | '});' 223 | ].join('\n'), 224 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 225 | }, 226 | { 227 | code: [ 228 | 'describe(function() {', 229 | ' before(function() {});', 230 | ' it(function() {});', 231 | ' describe(function() {', 232 | ' before(function() {});', 233 | ' it(function() {});', 234 | ' });', 235 | '});' 236 | ].join('\n'), 237 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 9, line: 5 } ] 238 | }, 239 | { 240 | code: [ 241 | 'describe(function() {', 242 | ' before(function() {});', 243 | ' describe(function() {', 244 | ' before(function() {});', 245 | ' it(function() {});', 246 | ' it(function() {});', 247 | ' });', 248 | '});' 249 | ].join('\n'), 250 | errors: [ { message: 'Unexpected use of Mocha `before` hook for a single test case', column: 5, line: 2 } ] 251 | }, 252 | { 253 | code: [ 254 | 'describe(function() {', 255 | ' after(function() {});', 256 | ' it(function() {});', 257 | '});' 258 | ].join('\n'), 259 | options: [ { allow: [ 'before' ] } ], 260 | errors: [ { message: 'Unexpected use of Mocha `after` hook for a single test case', column: 5, line: 2 } ] 261 | } 262 | ] 263 | 264 | }); 265 | --------------------------------------------------------------------------------