diff --git a/docs/rules/require-top-level-describe.md b/docs/rules/require-top-level-describe.md index 7b314401e..389b8dbdb 100644 --- a/docs/rules/require-top-level-describe.md +++ b/docs/rules/require-top-level-describe.md @@ -47,6 +47,34 @@ describe('test suite', () => { }); ``` +You can also enforce a limit on the number of describes allowed at the top-level +using the `maxNumberOfTopLevelDescribes` option: + +```json +{ + "jest/require-top-level-describe": [ + "error", + { + "maxNumberOfTopLevelDescribes": 2 + } + ] +} +``` + +Examples of **incorrect** code with the above config: + +```js +describe('test suite', () => { + it('test', () => {}); +}); + +describe('test suite', () => {}); + +describe('test suite', () => {}); +``` + +This option defaults to `Infinity`, allowing any number of top-level describes. + ## When Not To Use It Don't use this rule on non-jest test files. diff --git a/src/rules/__tests__/require-top-level-describe.test.ts b/src/rules/__tests__/require-top-level-describe.test.ts index b97d20a0b..0b072532f 100644 --- a/src/rules/__tests__/require-top-level-describe.test.ts +++ b/src/rules/__tests__/require-top-level-describe.test.ts @@ -112,3 +112,72 @@ ruleTester.run('require-top-level-describe', rule, { }, ], }); + +ruleTester.run( + 'require-top-level-describe (enforce number of describes)', + rule, + { + valid: [ + 'describe("test suite", () => { test("my test") });', + 'foo()', + 'describe.each([1, true])("trues", value => { it("an it", () => expect(value).toBe(true) ); });', + dedent` + describe('one', () => {}); + describe('two', () => {}); + describe('three', () => {}); + `, + { + code: dedent` + describe('one', () => { + describe('two', () => {}); + describe('three', () => {}); + }); + `, + options: [{ maxNumberOfTopLevelDescribes: 1 }], + }, + ], + invalid: [ + { + code: dedent` + describe('one', () => {}); + describe('two', () => {}); + describe('three', () => {}); + `, + options: [{ maxNumberOfTopLevelDescribes: 2 }], + errors: [{ messageId: 'tooManyDescribes', line: 3 }], + }, + { + code: dedent` + describe('one', () => { + describe('one (nested)', () => {}); + describe('two (nested)', () => {}); + }); + describe('two', () => { + describe('one (nested)', () => {}); + describe('two (nested)', () => {}); + describe('three (nested)', () => {}); + }); + describe('three', () => { + describe('one (nested)', () => {}); + describe('two (nested)', () => {}); + describe('three (nested)', () => {}); + }); + `, + options: [{ maxNumberOfTopLevelDescribes: 2 }], + errors: [{ messageId: 'tooManyDescribes', line: 10 }], + }, + { + code: dedent` + describe('one', () => {}); + describe('two', () => {}); + describe('three', () => {}); + `, + options: [{ maxNumberOfTopLevelDescribes: 1 }], + errors: [ + { messageId: 'tooManyDescribes', line: 2 }, + { messageId: 'tooManyDescribes', line: 3 }, + ], + }, + ], + }, +); diff --git a/src/rules/require-top-level-describe.ts b/src/rules/require-top-level-describe.ts index 73509c0f3..3377df5ce 100644 --- a/src/rules/require-top-level-describe.ts +++ b/src/rules/require-top-level-describe.ts @@ -1,7 +1,17 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; import { createRule, isDescribeCall, isHook, isTestCaseCall } from './utils'; -export default createRule({ +const messages = { + tooManyDescribes: + 'There should not be more than {{ max }} describe{{ s }} at the top level', + unexpectedTestCase: 'All test cases must be wrapped in a describe block.', + unexpectedHook: 'All hooks must be wrapped in a describe block.', +}; + +export default createRule< + [Partial<{ maxNumberOfTopLevelDescribes: number }>], + keyof typeof messages +>({ name: __filename, meta: { docs: { @@ -10,15 +20,26 @@ export default createRule({ 'Require test cases and hooks to be inside a `describe` block', recommended: false, }, - messages: { - unexpectedTestCase: 'All test cases must be wrapped in a describe block.', - unexpectedHook: 'All hooks must be wrapped in a describe block.', - }, + messages, type: 'suggestion', - schema: [], + schema: [ + { + type: 'object', + properties: { + maxNumberOfTopLevelDescribes: { + type: 'number', + minimum: 1, + }, + }, + additionalProperties: false, + }, + ], }, - defaultOptions: [], + defaultOptions: [{}], create(context) { + const { maxNumberOfTopLevelDescribes = Infinity } = + context.options[0] ?? {}; + let numberOfTopLevelDescribeBlocks = 0; let numberOfDescribeBlocks = 0; return { @@ -26,6 +47,20 @@ export default createRule({ if (isDescribeCall(node)) { numberOfDescribeBlocks++; + if (numberOfDescribeBlocks === 1) { + numberOfTopLevelDescribeBlocks++; + if (numberOfTopLevelDescribeBlocks > maxNumberOfTopLevelDescribes) { + context.report({ + node, + messageId: 'tooManyDescribes', + data: { + max: maxNumberOfTopLevelDescribes, + s: maxNumberOfTopLevelDescribes === 1 ? '' : 's', + }, + }); + } + } + return; }