diff --git a/README.md b/README.md index 39a7c4a..70c6156 100644 --- a/README.md +++ b/README.md @@ -29,23 +29,51 @@ Then add a reference to this plugin and selected rules in your eslint config: This plugin supports the following settings, which are used by multiple rules: -* `additionalCustomNames`: This allows rules to check additional function names when looking for suites or test cases. This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha) -**Example:** - -```json -{ - "rules": { - "mocha/no-skipped-tests": "error", - "mocha/no-exclusive-tests": "error" - }, - "settings": { - "mocha/additionalCustomNames": [ - { "name": "describeModule", "type": "suite", "interfaces": [ "BDD" ] }, - { "name": "testModule", "type": "testCase", "interfaces": [ "TDD" ] } - ] +* `additionalCustomNames`: This allows rules to check additional function names when looking for suites or test cases. This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha) or [`mocha-each`](https://github.com/ryym/mocha-each). + + **Example:** + + ```json + { + "rules": { + "mocha/no-skipped-tests": "error", + "mocha/no-exclusive-tests": "error" + }, + "settings": { + "mocha/additionalCustomNames": [ + { "name": "describeModule", "type": "suite", "interfaces": [ "BDD" ] }, + { "name": "testModule", "type": "testCase", "interfaces": [ "TDD" ] } + ] + } } -} -``` + ``` + + The `name` property can be in any of the following forms: + * A plain name e.g. `describeModule`, which allows: + + ```javascript + describeModule("example", function() { ... }); + ``` + + * A dotted name, e.g. `describe.modifier`, which allows: + + ```javascript + describe.modifier("example", function() { ... }); + ``` + + * A name with parentheses, e.g. `forEach().describe`, which allows: + + ```javascript + forEach([ 1, 2, 3 ]) + .describe("example", function(n) { ... }); + ``` + + * Any combination of the above, e.g. `forEach().describeModule.modifier`, which allows: + + ```javascript + forEach([ 1, 2, 3 ]) + .describeModule.modifier("example", function(n) { ... }); + ``` ## Configs diff --git a/lib/util/ast.js b/lib/util/ast.js index 29b88ec..fecc57a 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -31,6 +31,9 @@ function getNodeName(node) { if (node.type === 'ThisExpression') { return 'this'; } + if (node.type === 'CallExpression') { + return `${getNodeName(node.callee)}()`; + } if (node.type === 'MemberExpression') { return `${getNodeName(node.object)}.${getPropertyName(node.property)}`; } diff --git a/test/rules/max-top-level-suites.js b/test/rules/max-top-level-suites.js index fe41c39..7656772 100644 --- a/test/rules/max-top-level-suites.js +++ b/test/rules/max-top-level-suites.js @@ -68,6 +68,40 @@ ruleTester.run('max-top-level-suites', rules['max-top-level-suites'], { sourceType: 'module', ecmaVersion: 2015 } + }, + { + code: [ + 'describe.foo("", function () {', + ' describe("", function () {});', + ' describe("", function () {});', + '});' + ].join('\n'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2015 + }, + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } + }, + { + code: [ + 'describe.foo("bar")("", function () {', + ' describe("", function () {});', + ' describe("", function () {});', + '});' + ].join('\n'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2015 + }, + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } } ], @@ -211,6 +245,34 @@ ruleTester.run('max-top-level-suites', rules['max-top-level-suites'], { errors: [ { message: 'The number of top-level suites is more than 1.' } ] + }, { + code: 'describe.foo("bar")("this is a test", function () { });' + + 'context.foo("this is a different test", function () { });', + settings: { + mocha: { + additionalCustomNames: [ + { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'context.foo', type: 'suite', interfaces: [ 'BDD' ] } + ] + } + }, + errors: [ + { message: 'The number of top-level suites is more than 1.' } + ] + }, { + code: 'forEach([ 1, 2, 3 ]).describe("this is a test", function () { });' + + 'context.foo("this is a different test", function () { });', + settings: { + mocha: { + additionalCustomNames: [ + { name: 'forEach().describe', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'context.foo', type: 'suite', interfaces: [ 'BDD' ] } + ] + } + }, + errors: [ + { message: 'The number of top-level suites is more than 1.' } + ] } ] }); diff --git a/test/rules/no-async-describe.js b/test/rules/no-async-describe.js index bcb16c2..c22728d 100644 --- a/test/rules/no-async-describe.js +++ b/test/rules/no-async-describe.js @@ -72,6 +72,40 @@ ruleTester.run('no-async-describe', rule, { line: 1, column: 19 } ] + }, + { + code: 'describe.foo("bar")("hello", async () => {})', + output: 'describe.foo("bar")("hello", () => {})', + settings: { + mocha: { + additionalCustomNames: [ + { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] } + ] + } + }, + parserOptions: { ecmaVersion: 8 }, + errors: [ { + message: 'Unexpected async function in describe.foo()()', + line: 1, + column: 30 + } ] + }, + { + code: 'forEach([ 1, 2, 3 ]).describe.foo("hello", async () => {})', + output: 'forEach([ 1, 2, 3 ]).describe.foo("hello", () => {})', + settings: { + mocha: { + additionalCustomNames: [ + { name: 'forEach().describe.foo', type: 'suite', interfaces: [ 'BDD' ] } + ] + } + }, + parserOptions: { ecmaVersion: 8 }, + errors: [ { + message: 'Unexpected async function in forEach().describe.foo()', + line: 1, + column: 44 + } ] } ] }); diff --git a/test/rules/no-exclusive-tests.js b/test/rules/no-exclusive-tests.js index e36bb58..96c0ce2 100644 --- a/test/rules/no-exclusive-tests.js +++ b/test/rules/no-exclusive-tests.js @@ -38,6 +38,14 @@ ruleTester.run('no-exclusive-tests', rules['no-exclusive-tests'], { additionalCustomNames: [ { name: 'a.b.c', type: 'testCase', interfaces: [ 'BDD' ] } ] } } + }, + { + code: 'a.b().c.skip()', + settings: { + mocha: { + additionalCustomNames: [ { name: 'a.b().c', type: 'testCase', interfaces: [ 'BDD' ] } ] + } + } } ], @@ -175,6 +183,15 @@ ruleTester.run('no-exclusive-tests', rules['no-exclusive-tests'], { } }, errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ] + }, + { + code: 'foo("bar").it.only()', + settings: { + mocha: { + additionalCustomNames: [ { name: 'foo().it', type: 'testCase', interfaces: [ 'BDD' ] } ] + } + }, + errors: [ { message: expectedErrorMessage, column: 15, line: 1 } ] } ] }); diff --git a/test/rules/no-sibling-hooks.js b/test/rules/no-sibling-hooks.js index 3c62fa0..14ac2d1 100644 --- a/test/rules/no-sibling-hooks.js +++ b/test/rules/no-sibling-hooks.js @@ -98,6 +98,72 @@ ruleTester.run('no-sibling-hooks', rules['no-sibling-hooks'], { additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ] } } + }, { + code: [ + 'describe(function() {', + ' before(function() {});', + ' describe.foo(function() {', + ' before(function() {});', + ' });', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } + }, { + code: [ + 'describe(function() {', + ' before(function() {});', + ' describe.foo()(function() {', + ' before(function() {});', + ' });', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } + }, { + code: [ + 'describe(function() {', + ' before(function() {});', + ' forEach([ 1, 2, 3 ]).describe(function() {', + ' before(function() {});', + ' });', + ' forEach([ 1, 2, 3 ]).context(function() {', + ' before(function() {});', + ' });', + ' forEach([ 1, 2, 3 ]).describe.only(function() {', + ' before(function() {});', + ' });', + ' forEach([ 1, 2, 3 ]).describe.skip(function() {', + ' before(function() {});', + ' });', + ' forEach([ 1, 2, 3 ]).describe.foo(function() {', + ' before(function() {});', + ' });', + ' forEach([ 1, 2, 3 ]).describe.bar([ 9, 8, 7 ])(function() {', + ' before(function() {});', + ' });', + ' deep.forEach({ a: [ 1, 2, 3 ], b: [ 4, 5, 6 ] }).describe(function() {', + ' before(function() {});', + ' });', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ + { name: 'forEach().describe', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'forEach().context', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'forEach().describe.foo', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'forEach().describe.bar()', type: 'suite', interfaces: [ 'BDD' ] }, + { name: 'deep.forEach().describe', type: 'suite', interfaces: [ 'BDD' ] } + ] + } + } } ], @@ -172,6 +238,54 @@ ruleTester.run('no-sibling-hooks', rules['no-sibling-hooks'], { } }, errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 5, line: 6 } ] + }, { + code: [ + 'describe.foo(function() {', + ' before(function() {});', + ' describe.foo(function() {', + ' before(function() {});', + ' });', + ' before(function() {});', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo', type: 'suite', interfaces: [ 'BDD' ] } ] + } + }, + errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 5, line: 6 } ] + }, { + code: [ + 'describe.foo()(function() {', + ' before(function() {});', + ' describe.foo()(function() {', + ' before(function() {});', + ' });', + ' before(function() {});', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] } ] + } + }, + errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 5, line: 6 } ] + }, { + code: [ + 'forEach([ 1, 2, 3 ]).describe(function() {', + ' before(function() {});', + ' forEach([ 4, 5, 6 ]).describe(function() {', + ' before(function() {});', + ' });', + ' before(function() {});', + '});' + ].join('\n'), + settings: { + mocha: { + additionalCustomNames: [ { name: 'forEach().describe', type: 'suite', interfaces: [ 'BDD' ] } ] + } + }, + errors: [ { message: 'Unexpected use of duplicate Mocha `before` hook', column: 5, line: 6 } ] } ] diff --git a/test/rules/no-skipped-tests.js b/test/rules/no-skipped-tests.js index 7655c5e..2f755b5 100644 --- a/test/rules/no-skipped-tests.js +++ b/test/rules/no-skipped-tests.js @@ -139,6 +139,15 @@ ruleTester.run('no-skipped-tests', rules['no-skipped-tests'], { } }, errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ] + }, + { + code: 'custom("bar").it.skip()', + settings: { + mocha: { + additionalCustomNames: [ { name: 'custom().it', type: 'testCase', interfaces: [ 'BDD' ] } ] + } + }, + errors: [ { message: expectedErrorMessage, column: 18, line: 1 } ] } ] diff --git a/test/rules/no-top-level-hooks.js b/test/rules/no-top-level-hooks.js index b1d76f2..5fed652 100644 --- a/test/rules/no-top-level-hooks.js +++ b/test/rules/no-top-level-hooks.js @@ -34,6 +34,27 @@ ruleTester.run('no-top-level-hooks', rules['no-top-level-hooks'], { additionalCustomNames: [ { name: 'foo', type: 'suite', interfaces: [ 'BDD' ] } ] } } + }, { + code: 'describe.foo(function() { before(function() {}); });', + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } + }, { + code: 'describe.foo()(function() { before(function() {}); });', + settings: { + mocha: { + additionalCustomNames: [ { name: 'describe.foo()', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } + }, { + code: 'forEach([ 1, 2, 3 ]).describe(function() { before(function() {}); });', + settings: { + mocha: { + additionalCustomNames: [ { name: 'forEach().describe', type: 'suite', interfaces: [ 'BDD' ] } ] + } + } } ], invalid: [ @@ -92,6 +113,30 @@ ruleTester.run('no-top-level-hooks', rules['no-top-level-hooks'], { column: 21, line: 1 } ] + }, + { + code: 'describe()(function() {}); before(function() {});', + errors: [ { + message: 'Unexpected use of Mocha `before` hook outside of a test suite', + column: 28, + line: 1 + } ] + }, + { + code: 'describe.foo()(function() {}); before(function() {});', + errors: [ { + message: 'Unexpected use of Mocha `before` hook outside of a test suite', + column: 32, + line: 1 + } ] + }, + { + code: 'forEach([ 1, 2, 3 ]).describe(function() {}); before(function() {});', + errors: [ { + message: 'Unexpected use of Mocha `before` hook outside of a test suite', + column: 47, + line: 1 + } ] } ]