Skip to content

Commit 3eb69ad

Browse files
committed
feat(rules): add support for function declaration as test case
Add support for the following test file structure. ```js test('my test', myTest) function myTest() { expect(true).toBe(true) } ``` Methods that are directly referenced will be ananalyzed for the following rules `expect-expect` `no-if` `no-test-return-statement`, `no-try-expect`
1 parent 9a1a62f commit 3eb69ad

9 files changed

+162
-20
lines changed

src/rules/__tests__/expect-expect.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ruleTester.run('expect-expect', rule, {
1717
'it("should pass", () => expect(true).toBeDefined())',
1818
'test("should pass", () => expect(true).toBeDefined())',
1919
'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))',
20+
'it("should pass", myTest); function myTest() { expect(true).toBeDefined() }',
2021
{
2122
code:
2223
'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })',
@@ -50,6 +51,15 @@ ruleTester.run('expect-expect', rule, {
5051
},
5152
],
5253
},
54+
{
55+
code: 'it("should fail", myTest); function myTest() {}',
56+
errors: [
57+
{
58+
messageId: 'noAssertions',
59+
type: AST_NODE_TYPES.CallExpression,
60+
},
61+
],
62+
},
5363
{
5464
code: 'test("should fail", () => {});',
5565
errors: [

src/rules/__tests__/no-if.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ ruleTester.run('no-if', rule, {
1717
{
1818
code: `it('foo', () => {})`,
1919
},
20+
{
21+
code: `it('foo', () => {}); function myTest() { if('bar') {} }`,
22+
},
2023
{
2124
code: `foo('bar', () => {
2225
if(baz) {}
@@ -272,6 +275,14 @@ ruleTester.run('no-if', rule, {
272275
},
273276
],
274277
},
278+
{
279+
code: `it('foo', myTest); function myTest() { if ('bar') {} }`,
280+
errors: [
281+
{
282+
messageId: 'noIf',
283+
},
284+
],
285+
},
275286
{
276287
code: `describe('foo', () => {
277288
it('bar', () => {

src/rules/__tests__/no-test-return-statement.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ ruleTester.run('no-test-prefixes', rule, {
2323
expect(1).toBe(1);
2424
});
2525
`,
26+
`
27+
it("one", myTest);
28+
function myTest() {
29+
expect(1).toBe(1);
30+
}
31+
`,
2632
],
2733
invalid: [
2834
{
@@ -41,5 +47,14 @@ ruleTester.run('no-test-prefixes', rule, {
4147
`,
4248
errors: [{ messageId: 'noReturnValue', column: 9, line: 3 }],
4349
},
50+
{
51+
code: `
52+
it("one", myTest);
53+
function myTest () {
54+
return expect(1).toBe(1);
55+
}
56+
`,
57+
errors: [{ messageId: 'noReturnValue', column: 11, line: 4 }],
58+
},
4459
],
4560
});

src/rules/__tests__/no-try-expect.test.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@ ruleTester.run('no-try-catch', rule, {
1414
`it('foo', () => {
1515
expect('foo').toEqual('foo');
1616
})`,
17+
`it('foo', () => {})
18+
function myTest() {
19+
try {
20+
} catch {
21+
}
22+
}`,
1723
`it('foo', () => {
1824
expect('bar').toEqual('bar');
1925
});
2026
try {
21-
2227
} catch {
2328
expect('foo').toEqual('foo');
2429
}`,
2530
`it.skip('foo');
2631
try {
27-
2832
} catch {
2933
expect('foo').toEqual('foo');
3034
}`,
@@ -44,6 +48,22 @@ ruleTester.run('no-try-catch', rule, {
4448
},
4549
],
4650
},
51+
{
52+
code: `it('foo', myTest)
53+
function myTest() {
54+
try {
55+
56+
} catch (err) {
57+
expect(err).toMatch('Error');
58+
}
59+
}
60+
`,
61+
errors: [
62+
{
63+
messageId: 'noTryExpect',
64+
},
65+
],
66+
},
4767
{
4868
code: `it('foo', async () => {
4969
await wrapper('production', async () => {

src/rules/expect-expect.ts

+32-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import {
77
AST_NODE_TYPES,
88
TSESTree,
99
} from '@typescript-eslint/experimental-utils';
10-
import { TestCaseName, createRule, getNodeName } from './utils';
10+
import {
11+
TestCaseName,
12+
createRule,
13+
getFunctionDeclarationTestCallExpressions,
14+
getNodeName,
15+
} from './utils';
1116

1217
export default createRule<
1318
[Partial<{ assertFunctionNames: readonly string[] }>],
@@ -39,7 +44,31 @@ export default createRule<
3944
},
4045
defaultOptions: [{ assertFunctionNames: ['expect'] }],
4146
create(context, [{ assertFunctionNames = ['expect'] }]) {
42-
const unchecked: TSESTree.CallExpression[] = [];
47+
const unchecked: Array<
48+
TSESTree.CallExpression | TSESTree.FunctionDeclaration
49+
> = [];
50+
51+
function checkCallExpressionUsed(nodes: TSESTree.Node[]) {
52+
for (const node of nodes) {
53+
const index =
54+
node.type === AST_NODE_TYPES.CallExpression
55+
? unchecked.indexOf(node)
56+
: -1;
57+
58+
if (node.type === AST_NODE_TYPES.FunctionDeclaration) {
59+
const nodes = getFunctionDeclarationTestCallExpressions(
60+
context,
61+
node,
62+
);
63+
checkCallExpressionUsed(nodes);
64+
}
65+
66+
if (index !== -1) {
67+
unchecked.splice(index, 1);
68+
break;
69+
}
70+
}
71+
}
4372

4473
return {
4574
CallExpression(node) {
@@ -48,17 +77,7 @@ export default createRule<
4877
unchecked.push(node);
4978
} else if (name && assertFunctionNames.includes(name)) {
5079
// Return early in case of nested `it` statements.
51-
for (const ancestor of context.getAncestors()) {
52-
const index =
53-
ancestor.type === AST_NODE_TYPES.CallExpression
54-
? unchecked.indexOf(ancestor)
55-
: -1;
56-
57-
if (index !== -1) {
58-
unchecked.splice(index, 1);
59-
break;
60-
}
61-
}
80+
checkCallExpressionUsed(context.getAncestors());
6281
}
6382
},
6483
'Program:exit'() {

src/rules/no-if.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { TestCaseName, createRule, getNodeName, isTestCase } from './utils';
1+
import {
2+
TestCaseName,
3+
createRule,
4+
getNodeName,
5+
isFunctionDeclarationTestCase,
6+
isTestCase,
7+
} from './utils';
28
import {
39
AST_NODE_TYPES,
410
TSESTree,
@@ -63,8 +69,8 @@ export default createRule({
6369
FunctionExpression() {
6470
stack.push(false);
6571
},
66-
FunctionDeclaration() {
67-
stack.push(false);
72+
FunctionDeclaration(node) {
73+
stack.push(isFunctionDeclarationTestCase(context, node));
6874
},
6975
ArrowFunctionExpression(node) {
7076
stack.push(isTestArrowFunction(node));

src/rules/no-test-return-statement.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { createRule, isFunction, isTestCase } from './utils';
1+
import {
2+
createRule,
3+
isFunction,
4+
isFunctionDeclarationTestCase,
5+
isTestCase,
6+
} from './utils';
27
import { TSESTree } from '@typescript-eslint/experimental-utils';
38

49
const RETURN_STATEMENT = 'ReturnStatement';
@@ -41,6 +46,16 @@ export default createRule({
4146
const returnStmt = body.find(t => t.type === RETURN_STATEMENT);
4247
if (!returnStmt) return;
4348

49+
context.report({ messageId: 'noReturnValue', node: returnStmt });
50+
},
51+
FunctionDeclaration(node) {
52+
if (!isFunctionDeclarationTestCase(context, node)) return;
53+
54+
const returnStmt = node.body.body.find(
55+
t => t.type === RETURN_STATEMENT,
56+
);
57+
if (!returnStmt) return;
58+
4459
context.report({ messageId: 'noReturnValue', node: returnStmt });
4560
},
4661
};

src/rules/no-try-expect.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { createRule, isExpectCall, isTestCase } from './utils';
2+
import {
3+
createRule,
4+
isExpectCall,
5+
isFunctionDeclarationTestCase,
6+
isTestCase,
7+
} from './utils';
38

49
export default createRule({
510
name: __filename,
@@ -39,6 +44,11 @@ export default createRule({
3944
});
4045
}
4146
},
47+
FunctionDeclaration(node) {
48+
if (isFunctionDeclarationTestCase(context, node)) {
49+
isTest = true;
50+
}
51+
},
4252
CatchClause() {
4353
if (isTest) {
4454
++catchDepth;
@@ -54,6 +64,11 @@ export default createRule({
5464
isTest = false;
5565
}
5666
},
67+
'FunctionDeclaration:exit'(node) {
68+
if (isFunctionDeclarationTestCase(context, node)) {
69+
isTest = false;
70+
}
71+
},
5772
};
5873
},
5974
});

src/rules/utils.ts

+31
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,37 @@ export const isHook = (
631631
);
632632
};
633633

634+
export const isFunctionDeclarationTestCase = (
635+
context: TSESLint.RuleContext<string, any>,
636+
node: TSESTree.FunctionDeclaration,
637+
): boolean => {
638+
return getFunctionDeclarationTestCallExpressions(context, node).length > 0;
639+
};
640+
641+
export const getFunctionDeclarationTestCallExpressions = (
642+
context: TSESLint.RuleContext<string, any>,
643+
node: TSESTree.FunctionDeclaration,
644+
): Array<JestFunctionCallExpression<TestCaseName>> => {
645+
const variables = context.getDeclaredVariables(node);
646+
return variables.reduce<Array<JestFunctionCallExpression<TestCaseName>>>(
647+
(acc, { references }) => {
648+
const callExpressions = references
649+
.map(({ identifier }) => identifier.parent)
650+
.filter<TSESTree.CallExpression>(isCallExpression)
651+
.filter(isTestCase);
652+
653+
return [...acc, ...callExpressions];
654+
},
655+
[],
656+
);
657+
};
658+
659+
const isCallExpression = (
660+
node?: TSESTree.Node,
661+
): node is TSESTree.CallExpression => {
662+
return !!node && node.type === AST_NODE_TYPES.CallExpression;
663+
};
664+
634665
export const isTestCase = (
635666
node: TSESTree.CallExpression,
636667
): node is JestFunctionCallExpression<TestCaseName> => {

0 commit comments

Comments
 (0)