Skip to content

Commit

Permalink
feat: create max-nested-describe rule (#845)
Browse files Browse the repository at this point in the history
* feat: create max-nested-describe rule

* feat: support only object schema

* refactor: simplify call expression type check

* test: max 0 option

* test: update number of rules

* test: update snapshot

* test: describe modifiers

* docs: fix examples syntax

* docs: fix syntax consistency

* test: fix missing brackets

* refactor: add spacing around message placeholders

* feat: increase default max to 5

* docs: update with new default max of 5

* test: add cases for 5 max
  • Loading branch information
dominictwlee committed Jul 21, 2021
1 parent 19e3a6e commit 8067405
Show file tree
Hide file tree
Showing 6 changed files with 437 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -135,6 +135,7 @@ installations requiring long-term consistency.
| [consistent-test-it](docs/rules/consistent-test-it.md) | Have control over `test` and `it` usages | | ![fixable][] |
| [expect-expect](docs/rules/expect-expect.md) | Enforce assertion to be made in a test body | ![recommended][] | |
| [lowercase-name](docs/rules/lowercase-name.md) | Enforce lowercase test names | | ![fixable][] |
| [max-nested-describe](docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls | | |
| [no-alias-methods](docs/rules/no-alias-methods.md) | Disallow alias methods | ![style][] | ![fixable][] |
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | ![recommended][] | |
| [no-conditional-expect](docs/rules/no-conditional-expect.md) | Prevent calling `expect` conditionally | ![recommended][] | |
Expand Down
131 changes: 131 additions & 0 deletions docs/rules/max-nested-describe.md
@@ -0,0 +1,131 @@
# Enforces a maximum depth to nested describe calls (`max-nested-describe`)

While it's useful to be able to group your tests together within the same file
using `describe()`, having too many levels of nesting throughout your tests make
them difficult to read.

## Rule Details

This rule enforces a maximum depth to nested `describe()` calls to improve code
clarity in your tests.

The following patterns are considered warnings (with the default option of
`{ "max": 5 } `):

```js
describe('foo', () => {
describe('bar', () => {
describe('baz', () => {
describe('qux', () => {
describe('quxx', () => {
describe('too many', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
});
});
});

describe('foo', function () {
describe('bar', function () {
describe('baz', function () {
describe('qux', function () {
describe('quxx', function () {
describe('too many', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
});
});
});
```

The following patterns are **not** considered warnings (with the default option
of `{ "max": 5 } `):

```js
describe('foo', () => {
describe('bar', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});

describe('qux', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});

describe('foo2', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});

describe('foo', function () {
describe('bar', function () {
describe('baz', function () {
describe('qux', function () {
describe('this is the limit', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
});
});
```

## Options

```json
{
"jest/max-nested-describe": [
"error",
{
"max": 5
}
]
}
```

### `max`

Enforces a maximum depth for nested `describe()`.

This has a default value of `5`.

Examples of patterns **not** considered warnings with options set to
`{ "max": 2 }`:

```js
describe('foo', () => {
describe('bar', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});

describe('foo2', function()) {
describe('bar2', function() {
it('should get something', function() {
expect(getSomething()).toBe('Something');
});

it('should get else', function() {
expect(getSomething()).toBe('Something');
});
});
});

```
1 change: 1 addition & 0 deletions src/__tests__/__snapshots__/rules.test.ts.snap
Expand Up @@ -13,6 +13,7 @@ Object {
"jest/consistent-test-it": "error",
"jest/expect-expect": "error",
"jest/lowercase-name": "error",
"jest/max-nested-describe": "error",
"jest/no-alias-methods": "error",
"jest/no-commented-out-tests": "error",
"jest/no-conditional-expect": "error",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.ts
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import { resolve } from 'path';
import plugin from '../';

const numberOfRules = 45;
const numberOfRules = 46;
const ruleNames = Object.keys(plugin.rules);
const deprecatedRules = Object.entries(plugin.rules)
.filter(([, rule]) => rule.meta.deprecated)
Expand Down
223 changes: 223 additions & 0 deletions src/rules/__tests__/max-nested-describe.test.ts
@@ -0,0 +1,223 @@
import { TSESLint } from '@typescript-eslint/experimental-utils';
import dedent from 'dedent';
import resolveFrom from 'resolve-from';
import rule from '../max-nested-describe';

const ruleTester = new TSESLint.RuleTester({
parser: resolveFrom(require.resolve('eslint'), 'espree'),
parserOptions: {
ecmaVersion: 2017,
},
});

ruleTester.run('max-nested-describe', rule, {
valid: [
dedent`
describe('foo', function() {
describe('bar', function () {
describe('baz', function () {
describe('qux', function () {
describe('qux', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
})
})
})
})
});
`,
dedent`
describe('foo', function() {
describe('bar', function () {
describe('baz', function () {
describe('qux', function () {
describe('qux', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
fdescribe('qux', () => {
it('something', async () => {
expect('something').toBe('something');
});
});
})
})
})
});
`,
dedent`
describe('foo', () => {
describe('bar', () => {
it('hello', async () => {
expect('hello').toBe('hello');
});
});
});
xdescribe('foo', function() {
describe('bar', function() {
it('something', async () => {
expect('something').toBe('something');
});
});
});
`,
{
code: dedent`
describe('foo', () => {
describe.only('bar', () => {
describe.skip('baz', () => {
it('something', async () => {
expect('something').toBe('something');
});
});
});
});
`,
options: [{ max: 3 }],
},
{
code: dedent`
it('something', async () => {
expect('something').toBe('something');
});
`,
options: [{ max: 0 }],
},
dedent`
describe('foo', () => {
describe.each(['hello', 'world'])("%s", (a) => {});
});
`,
dedent`
describe('foo', () => {
describe.each\`
foo | bar
${1} | ${2}
\`('$foo $bar', ({ foo, bar }) => {});
});
`,
],
invalid: [
{
code: dedent`
describe('foo', function() {
describe('bar', function () {
describe('baz', function () {
describe('qux', function () {
describe('quxx', function () {
describe('over limit', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
});
});
});
`,
errors: [{ messageId: 'exceededMaxDepth', line: 6, column: 11 }],
},
{
code: dedent`
describe('foo', () => {
describe('bar', () => {
describe('baz', () => {
describe('baz1', () => {
describe('baz2', () => {
describe('baz3', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
describe('baz4', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
});
describe('qux', function () {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
})
});
`,
errors: [
{ messageId: 'exceededMaxDepth', line: 6, column: 11 },
{ messageId: 'exceededMaxDepth', line: 12, column: 11 },
],
},
{
code: dedent`
fdescribe('foo', () => {
describe.only('bar', () => {
describe.skip('baz', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
describe('baz', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
});
});
xdescribe('qux', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
`,
options: [{ max: 2 }],
errors: [
{ messageId: 'exceededMaxDepth', line: 3, column: 5 },
{ messageId: 'exceededMaxDepth', line: 9, column: 5 },
],
},
{
code: dedent`
describe('qux', () => {
it('should get something', () => {
expect(getSomething()).toBe('Something');
});
});
`,
options: [{ max: 0 }],
errors: [{ messageId: 'exceededMaxDepth', line: 1, column: 1 }],
},
{
code: dedent`
describe('foo', () => {
describe.each(['hello', 'world'])("%s", (a) => {});
});
`,
options: [{ max: 1 }],
errors: [{ messageId: 'exceededMaxDepth', line: 2, column: 3 }],
},
{
code: dedent`
describe('foo', () => {
describe.each\`
foo | bar
${1} | ${2}
\`('$foo $bar', ({ foo, bar }) => {});
});
`,
options: [{ max: 1 }],
errors: [{ messageId: 'exceededMaxDepth', line: 2, column: 3 }],
},
],
});

0 comments on commit 8067405

Please sign in to comment.