Skip to content

Commit 36fdcc5

Browse files
authored
feat(lowercase-name): support ignoreTopLevelDescribe option (#611)
closes #247
1 parent 7628266 commit 36fdcc5

File tree

3 files changed

+154
-2
lines changed

3 files changed

+154
-2
lines changed

docs/rules/lowercase-name.md

+18
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,21 @@ Example of **correct** code for the `{ "allowedPrefixes": ["GET"] }` option:
8686

8787
describe('GET /live');
8888
```
89+
90+
### `ignoreTopLevelDescribe`
91+
92+
This option can be set to allow only the top-level `describe` blocks to have a
93+
title starting with an upper-case letter.
94+
95+
Example of **correct** code for the `{ "ignoreTopLevelDescribe": true }` option:
96+
97+
```js
98+
/* eslint jest/lowercase-name: ["error", { "ignoreTopLevelDescribe": true }] */
99+
describe('MyClass', () => {
100+
describe('#myMethod', () => {
101+
it('does things', () => {
102+
//
103+
});
104+
});
105+
});
106+
```

src/rules/__tests__/lowercase-name.test.ts

+113
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TSESLint } from '@typescript-eslint/experimental-utils';
2+
import dedent from 'dedent';
23
import resolveFrom from 'resolve-from';
34
import rule from '../lowercase-name';
45
import { DescribeAlias, TestCaseName } from '../utils';
@@ -251,3 +252,115 @@ ruleTester.run('lowercase-name with allowedPrefixes', rule, {
251252
],
252253
invalid: [],
253254
});
255+
256+
ruleTester.run('lowercase-name with ignoreTopLevelDescribe', rule, {
257+
valid: [
258+
{
259+
code: 'describe("MyClass", () => {});',
260+
options: [{ ignoreTopLevelDescribe: true }],
261+
},
262+
{
263+
code: dedent`
264+
describe('MyClass', () => {
265+
describe('#myMethod', () => {
266+
it('does things', () => {
267+
//
268+
});
269+
});
270+
});
271+
`,
272+
options: [{ ignoreTopLevelDescribe: true }],
273+
},
274+
],
275+
invalid: [
276+
{
277+
code: 'it("Works!", () => {});',
278+
options: [{ ignoreTopLevelDescribe: true }],
279+
output: 'it("works!", () => {});',
280+
errors: [
281+
{
282+
messageId: 'unexpectedLowercase',
283+
data: { method: TestCaseName.it },
284+
column: 4,
285+
line: 1,
286+
},
287+
],
288+
},
289+
{
290+
code: dedent`
291+
describe('MyClass', () => {
292+
describe('MyMethod', () => {
293+
it('Does things', () => {
294+
//
295+
});
296+
});
297+
});
298+
`,
299+
options: [{ ignoreTopLevelDescribe: true }],
300+
output: dedent`
301+
describe('MyClass', () => {
302+
describe('myMethod', () => {
303+
it('does things', () => {
304+
//
305+
});
306+
});
307+
});
308+
`,
309+
errors: [
310+
{
311+
messageId: 'unexpectedLowercase',
312+
data: { method: DescribeAlias.describe },
313+
column: 12,
314+
line: 2,
315+
},
316+
{
317+
messageId: 'unexpectedLowercase',
318+
data: { method: TestCaseName.it },
319+
column: 8,
320+
line: 3,
321+
},
322+
],
323+
},
324+
{
325+
code: dedent`
326+
describe('MyClass', () => {
327+
describe('MyMethod', () => {
328+
it('Does things', () => {
329+
//
330+
});
331+
});
332+
});
333+
`,
334+
options: [{ ignoreTopLevelDescribe: false }],
335+
output: dedent`
336+
describe('myClass', () => {
337+
describe('myMethod', () => {
338+
it('does things', () => {
339+
//
340+
});
341+
});
342+
});
343+
`,
344+
errors: [
345+
{
346+
messageId: 'unexpectedLowercase',
347+
data: { method: DescribeAlias.describe },
348+
column: 10,
349+
line: 1,
350+
},
351+
{
352+
messageId: 'unexpectedLowercase',
353+
data: { method: DescribeAlias.describe },
354+
column: 12,
355+
line: 2,
356+
},
357+
{
358+
messageId: 'unexpectedLowercase',
359+
data: { method: TestCaseName.it },
360+
column: 8,
361+
line: 3,
362+
},
363+
],
364+
},
365+
],
366+
});

src/rules/lowercase-name.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default createRule<
6262
Partial<{
6363
ignore: readonly IgnorableFunctionExpressions[];
6464
allowedPrefixes: readonly string[];
65+
ignoreTopLevelDescribe: boolean;
6566
}>,
6667
],
6768
'unexpectedLowercase'
@@ -98,18 +99,38 @@ export default createRule<
9899
items: { type: 'string' },
99100
additionalItems: false,
100101
},
102+
ignoreTopLevelDescribe: {
103+
type: 'boolean',
104+
default: false,
105+
},
101106
},
102107
additionalProperties: false,
103108
},
104109
],
105110
} as const,
106-
defaultOptions: [{ ignore: [], allowedPrefixes: [] }],
107-
create(context, [{ ignore = [], allowedPrefixes = [] }]) {
111+
defaultOptions: [
112+
{ ignore: [], allowedPrefixes: [], ignoreTopLevelDescribe: false },
113+
],
114+
create(
115+
context,
116+
[{ ignore = [], allowedPrefixes = [], ignoreTopLevelDescribe }],
117+
) {
118+
let numberOfDescribeBlocks = 0;
119+
108120
return {
109121
CallExpression(node: TSESTree.CallExpression) {
110122
if (!isJestFunctionWithLiteralArg(node)) {
111123
return;
112124
}
125+
126+
if (isDescribe(node)) {
127+
numberOfDescribeBlocks++;
128+
129+
if (ignoreTopLevelDescribe && numberOfDescribeBlocks === 1) {
130+
return;
131+
}
132+
}
133+
113134
const erroneousMethod = jestFunctionName(node, allowedPrefixes);
114135

115136
if (erroneousMethod && !ignore.includes(node.callee.name)) {

0 commit comments

Comments
 (0)