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 support for function declaration as test case #504

Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions src/rules/__tests__/expect-expect.test.ts
Expand Up @@ -17,6 +17,7 @@ ruleTester.run('expect-expect', rule, {
'it("should pass", () => expect(true).toBeDefined())',
'test("should pass", () => expect(true).toBeDefined())',
'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))',
'it("should pass", myTest); function myTest() { expect(true).toBeDefined() }',
{
code:
'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })',
Expand Down Expand Up @@ -50,6 +51,15 @@ ruleTester.run('expect-expect', rule, {
},
],
},
{
code: 'it("should fail", myTest); function myTest() {}',
errors: [
{
messageId: 'noAssertions',
type: AST_NODE_TYPES.CallExpression,
},
],
},
{
code: 'test("should fail", () => {});',
errors: [
Expand Down
11 changes: 11 additions & 0 deletions src/rules/__tests__/no-if.test.ts
Expand Up @@ -17,6 +17,9 @@ ruleTester.run('no-if', rule, {
{
code: `it('foo', () => {})`,
},
{
code: `it('foo', () => {}); function myTest() { if('bar') {} }`,
},
{
code: `foo('bar', () => {
if(baz) {}
Expand Down Expand Up @@ -302,6 +305,14 @@ ruleTester.run('no-if', rule, {
},
],
},
{
code: `it('foo', myTest); function myTest() { if ('bar') {} }`,
errors: [
{
messageId: 'noIf',
},
],
},
{
code: `describe('foo', () => {
it('bar', () => {
Expand Down
19 changes: 19 additions & 0 deletions src/rules/__tests__/no-test-return-statement.test.ts
Expand Up @@ -23,6 +23,16 @@ ruleTester.run('no-test-prefixes', rule, {
expect(1).toBe(1);
});
`,
`
it("one", myTest);
function myTest() {
expect(1).toBe(1);
}
`,
`
it("one", () => expect(1).toBe(1));
function myHelper() {}
`,
],
invalid: [
{
Expand All @@ -41,5 +51,14 @@ ruleTester.run('no-test-prefixes', rule, {
`,
errors: [{ messageId: 'noReturnValue', column: 9, line: 3 }],
},
{
code: `
it("one", myTest);
function myTest () {
return expect(1).toBe(1);
}
`,
errors: [{ messageId: 'noReturnValue', column: 11, line: 4 }],
},
],
});
24 changes: 22 additions & 2 deletions src/rules/__tests__/no-try-expect.test.ts
Expand Up @@ -14,17 +14,21 @@ ruleTester.run('no-try-catch', rule, {
`it('foo', () => {
expect('foo').toEqual('foo');
})`,
`it('foo', () => {})
function myTest() {
try {
} catch {
}
}`,
`it('foo', () => {
expect('bar').toEqual('bar');
});
try {

} catch {
expect('foo').toEqual('foo');
}`,
`it.skip('foo');
try {

} catch {
expect('foo').toEqual('foo');
}`,
Expand All @@ -50,6 +54,22 @@ ruleTester.run('no-try-catch', rule, {
},
],
},
{
code: `it('foo', myTest)
function myTest() {
try {

} catch (err) {
expect(err).toMatch('Error');
}
}
`,
errors: [
{
messageId: 'noTryExpect',
},
],
},
{
code: `it('foo', async () => {
await wrapper('production', async () => {
Expand Down
42 changes: 30 additions & 12 deletions src/rules/expect-expect.ts
Expand Up @@ -7,7 +7,12 @@ import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { TestCaseName, createRule, getNodeName } from './utils';
import {
TestCaseName,
createRule,
getNodeName,
getTestCallExpressionsFromDeclaredVariables,
} from './utils';

export default createRule<
[Partial<{ assertFunctionNames: readonly string[] }>],
Expand Down Expand Up @@ -41,24 +46,37 @@ export default createRule<
create(context, [{ assertFunctionNames = ['expect'] }]) {
const unchecked: TSESTree.CallExpression[] = [];

function checkCallExpressionUsed(nodes: TSESTree.Node[]) {
for (const node of nodes) {
const index =
node.type === AST_NODE_TYPES.CallExpression
? unchecked.indexOf(node)
: -1;

if (node.type === AST_NODE_TYPES.FunctionDeclaration) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
declaredVariables,
);

checkCallExpressionUsed(testCallExpressions);
}

if (index !== -1) {
unchecked.splice(index, 1);
break;
}
}
}

return {
CallExpression(node) {
const name = getNodeName(node.callee);
if (name === TestCaseName.it || name === TestCaseName.test) {
unchecked.push(node);
} else if (name && assertFunctionNames.includes(name)) {
// Return early in case of nested `it` statements.
for (const ancestor of context.getAncestors()) {
const index =
ancestor.type === AST_NODE_TYPES.CallExpression
? unchecked.indexOf(ancestor)
: -1;

if (index !== -1) {
unchecked.splice(index, 1);
break;
}
}
checkCallExpressionUsed(context.getAncestors());
}
},
'Program:exit'() {
Expand Down
17 changes: 14 additions & 3 deletions src/rules/no-if.ts
Expand Up @@ -2,7 +2,13 @@ import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { TestCaseName, createRule, getNodeName, isTestCase } from './utils';
import {
TestCaseName,
createRule,
getNodeName,
getTestCallExpressionsFromDeclaredVariables,
isTestCase,
} from './utils';

const testCaseNames = new Set<string | null>([
...Object.keys(TestCaseName),
Expand Down Expand Up @@ -68,8 +74,13 @@ export default createRule({
FunctionExpression() {
stack.push(false);
},
FunctionDeclaration() {
stack.push(false);
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
declaredVariables,
);

stack.push(testCallExpressions.length > 0);
},
ArrowFunctionExpression(node) {
stack.push(isTestArrowFunction(node));
Expand Down
21 changes: 20 additions & 1 deletion src/rules/no-test-return-statement.ts
Expand Up @@ -2,7 +2,12 @@ import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { createRule, isFunction, isTestCase } from './utils';
import {
createRule,
getTestCallExpressionsFromDeclaredVariables,
isFunction,
isTestCase,
} from './utils';

const getBody = (args: TSESTree.Expression[]) => {
const [, secondArg] = args;
Expand Down Expand Up @@ -43,6 +48,20 @@ export default createRule({
);
if (!returnStmt) return;

context.report({ messageId: 'noReturnValue', node: returnStmt });
},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
declaredVariables,
);
if (testCallExpressions.length === 0) return;

const returnStmt = node.body.body.find(
t => t.type === AST_NODE_TYPES.ReturnStatement,
);
if (!returnStmt) return;

context.report({ messageId: 'noReturnValue', node: returnStmt });
},
};
Expand Down
27 changes: 26 additions & 1 deletion src/rules/no-try-expect.ts
@@ -1,5 +1,10 @@
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { createRule, isExpectCall, isTestCase } from './utils';
import {
createRule,
getTestCallExpressionsFromDeclaredVariables,
isExpectCall,
isTestCase,
} from './utils';

export default createRule({
name: __filename,
Expand Down Expand Up @@ -39,6 +44,16 @@ export default createRule({
});
}
},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
declaredVariables,
);

if (testCallExpressions.length > 0) {
isTest = true;
}
},
CatchClause() {
if (isTest) {
++catchDepth;
Expand All @@ -54,6 +69,16 @@ export default createRule({
isTest = false;
}
},
'FunctionDeclaration:exit'(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
declaredVariables,
);

if (testCallExpressions.length > 0) {
isTest = false;
}
},
};
},
});
21 changes: 21 additions & 0 deletions src/rules/utils.ts
Expand Up @@ -633,6 +633,27 @@ export const isHook = (
node.callee.type === AST_NODE_TYPES.Identifier &&
HookName.hasOwnProperty(node.callee.name);

export const getTestCallExpressionsFromDeclaredVariables = (
declaredVaiables: TSESLint.Scope.Variable[],
): Array<JestFunctionCallExpression<TestCaseName>> => {
return declaredVaiables.reduce<
Array<JestFunctionCallExpression<TestCaseName>>
>(
(acc, { references }) =>
acc.concat(
references
.map(({ identifier }) => identifier.parent)
.filter(
(node): node is JestFunctionCallExpression<TestCaseName> =>
!!node &&
node.type === AST_NODE_TYPES.CallExpression &&
isTestCase(node),
),
),
[],
);
};

export const isTestCase = (
node: TSESTree.CallExpression,
): node is JestFunctionCallExpression<TestCaseName> =>
Expand Down