Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rules): add require-top-level-describe rule #407

Merged
merged 5 commits into from Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/rules/require-top-level-describe.md
@@ -0,0 +1,47 @@
# Require top-level describe block (require-top-level-describe)

Jest allows you to organise your test files the way you want it. However, the
more your codebase grows, the more it becomes hard to navigate in your test
files. This rule makes sure that you provide at least a top-level describe block
in your test file.

## Rule Details

This rule triggers a warning if a test case (`test` and `it`) or a hook
(`beforeAll`, `beforeEach`, `afterEach`, `afterAll`) is not located in a
top-level describe block.

The following patterns are considered warnings:

```js
// Above a describe block
test('my test', () => {});
describe('test suite', () => {
it('test', () => {});
});

// Below a describe block
describe('test suite', () => {});
test('my test', () => {});
```

The following patterns are **not** considered warnings:

```js
// In a describe block
describe('test suite', () => {
test('my test', () => {});
});

// In a nested describe block
describe('test suite', () => {
test('my test', () => {});
describe('another test suite', () => {
test('my other test', () => {});
});
});
```

## When Not To Use It

Don't use this rule on non-jest test files.
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.ts
Expand Up @@ -3,7 +3,7 @@ import { resolve } from 'path';
import plugin from '../';

const ruleNames = Object.keys(plugin.rules);
const numberOfRules = 38;
const numberOfRules = 39;

describe('rules', () => {
it('should have a corresponding doc for each rule', () => {
Expand Down
58 changes: 58 additions & 0 deletions src/rules/__tests__/require-top-level-describe.test.ts
@@ -0,0 +1,58 @@
import { TSESLint } from '@typescript-eslint/experimental-utils';
import rule from '../require-top-level-describe';

const ruleTester = new TSESLint.RuleTester({
parserOptions: {
ecmaVersion: 2015,
},
});

ruleTester.run('no-standalone-hook', rule, {
valid: [
'describe("test suite", () => { test("my test") });',
'describe("test suite", () => { it("my test") });',
`
describe("test suite", () => {
beforeEach("a", () => {});
describe("b", () => {});
test("c", () => {})
});
`,
'describe("test suite", () => { beforeAll("my beforeAll") });',
'describe("test suite", () => { afterEach("my afterEach") });',
'describe("test suite", () => { afterAll("my afterAll") });',
`
describe("test suite", () => {
it("my test", () => {})
describe("another test suite", () => {
});
test("my other test", () => {})
});
`,
],
invalid: [
{
code: `
test("my test", () => {})
describe("test suite", () => {});
`,
errors: [{ messageId: 'unexpectedMethod' }],
},
{
code: `
test("my test", () => {})
describe("test suite", () => {
it("test", () => {})
});
`,
errors: [{ messageId: 'unexpectedMethod' }],
},
{
code: `
describe("test suite", () => {});
afterAll("my test", () => {})
`,
errors: [{ messageId: 'unexpectedMethod' }],
},
],
});
37 changes: 37 additions & 0 deletions src/rules/require-top-level-describe.ts
@@ -0,0 +1,37 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';

import { createRule, isDescribe, isHook, isTestCase } from './utils';

export default createRule({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description:
'Prevents test cases and hooks to be outside of a describe block',
recommended: false,
},
messages: {
unexpectedMethod: 'There must be a top-level describe block.',
thomaslombart marked this conversation as resolved.
Show resolved Hide resolved
},
type: 'suggestion',
schema: [],
},
defaultOptions: [],
create(context) {
let numberOfDescribeBlock = 0;
thomaslombart marked this conversation as resolved.
Show resolved Hide resolved
return {
CallExpression(node) {
isDescribe(node) && numberOfDescribeBlock++;
thomaslombart marked this conversation as resolved.
Show resolved Hide resolved
if ((isTestCase(node) || isHook(node)) && numberOfDescribeBlock === 0) {
thomaslombart marked this conversation as resolved.
Show resolved Hide resolved
context.report({ node, messageId: 'unexpectedMethod' });
}
},
'CallExpression:exit'(node: TSESTree.CallExpression) {
if (isDescribe(node)) {
numberOfDescribeBlock--;
}
},
};
},
});