Skip to content

Commit

Permalink
fix: improve support for it.each involving tagged template literals (#…
Browse files Browse the repository at this point in the history
…701)

* fix(no-disabled-tests): fix bug with it.each

* fix(no-test-prefixes): fix bug with it.each

* fix(consistent-test-it): fix bug with it.each

* fix(no-standalone-expect): coverage back to 100%

* fix: code review changes
  • Loading branch information
k-yle committed Nov 12, 2020
1 parent 8e906ad commit 2341814
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 6 deletions.
72 changes: 72 additions & 0 deletions src/rules/__tests__/consistent-test-it.test.ts
Expand Up @@ -32,6 +32,14 @@ ruleTester.run('consistent-test-it with fn=test', rule, {
code: 'xtest("foo")',
options: [{ fn: TestCaseName.test }],
},
{
code: 'test.each([])("foo")',
options: [{ fn: TestCaseName.test }],
},
{
code: 'test.each``("foo")',
options: [{ fn: TestCaseName.test }],
},
{
code: 'describe("suite", () => { test("foo") })',
options: [{ fn: TestCaseName.test }],
Expand Down Expand Up @@ -122,6 +130,34 @@ ruleTester.run('consistent-test-it with fn=test', rule, {
},
],
},
{
code: 'it.each([])("foo")',
output: 'test.each([])("foo")',
options: [{ fn: TestCaseName.test }],
errors: [
{
messageId: 'consistentMethod',
data: {
testKeyword: TestCaseName.test,
oppositeTestKeyword: TestCaseName.it,
},
},
],
},
{
code: 'it.each``("foo")',
output: 'test.each``("foo")',
options: [{ fn: TestCaseName.test }],
errors: [
{
messageId: 'consistentMethod',
data: {
testKeyword: TestCaseName.test,
oppositeTestKeyword: TestCaseName.it,
},
},
],
},
{
code: 'describe("suite", () => { it("foo") })',
output: 'describe("suite", () => { test("foo") })',
Expand Down Expand Up @@ -165,6 +201,14 @@ ruleTester.run('consistent-test-it with fn=it', rule, {
code: 'it.concurrent("foo")',
options: [{ fn: TestCaseName.it }],
},
{
code: 'it.each([])("foo")',
options: [{ fn: TestCaseName.it }],
},
{
code: 'it.each``("foo")',
options: [{ fn: TestCaseName.it }],
},
{
code: 'describe("suite", () => { it("foo") })',
options: [{ fn: TestCaseName.it }],
Expand Down Expand Up @@ -241,6 +285,34 @@ ruleTester.run('consistent-test-it with fn=it', rule, {
},
],
},
{
code: 'test.each([])("foo")',
output: 'it.each([])("foo")',
options: [{ fn: TestCaseName.it }],
errors: [
{
messageId: 'consistentMethod',
data: {
testKeyword: TestCaseName.it,
oppositeTestKeyword: TestCaseName.test,
},
},
],
},
{
code: 'test.each``("foo")',
output: 'it.each``("foo")',
options: [{ fn: TestCaseName.it }],
errors: [
{
messageId: 'consistentMethod',
data: {
testKeyword: TestCaseName.it,
oppositeTestKeyword: TestCaseName.test,
},
},
],
},
{
code: 'describe("suite", () => { test("foo") })',
output: 'describe("suite", () => { it("foo") })',
Expand Down
33 changes: 33 additions & 0 deletions src/rules/__tests__/no-disabled-tests.test.ts
Expand Up @@ -16,6 +16,7 @@ ruleTester.run('no-disabled-tests', rule, {
'it("foo", function () {})',
'describe.only("foo", function () {})',
'it.only("foo", function () {})',
'it.each("foo", () => {})',
'it.concurrent("foo", function () {})',
'test("foo", function () {})',
'test.only("foo", function () {})',
Expand Down Expand Up @@ -87,6 +88,22 @@ ruleTester.run('no-disabled-tests', rule, {
code: 'test.skip("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'it.skip.each``("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'test.skip.each``("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'it.skip.each([])("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'test.skip.each([])("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'test.concurrent.skip("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
Expand All @@ -107,6 +124,22 @@ ruleTester.run('no-disabled-tests', rule, {
code: 'xtest("foo", function () {})',
errors: [{ messageId: 'disabledTest', column: 1, line: 1 }],
},
{
code: 'xit.each``("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'xtest.each``("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'xit.each([])("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'xtest.each([])("foo", function () {})',
errors: [{ messageId: 'skippedTest', column: 1, line: 1 }],
},
{
code: 'it("has title but no callback")',
errors: [{ messageId: 'missingFunction', column: 1, line: 1 }],
Expand Down
4 changes: 4 additions & 0 deletions src/rules/__tests__/no-standalone-expect.test.ts
Expand Up @@ -57,6 +57,10 @@ ruleTester.run('no-standalone-expect', rule, {
},
],
invalid: [
{
code: "(() => {})('testing', () => expect(true))",
errors: [{ endColumn: 41, column: 29, messageId: 'unexpectedExpect' }],
},
{
code: `
describe('scenario', () => {
Expand Down
60 changes: 60 additions & 0 deletions src/rules/__tests__/no-test-prefixes.test.ts
Expand Up @@ -12,8 +12,18 @@ ruleTester.run('no-test-prefixes', rule, {
'test.concurrent("foo", function () {})',
'describe.only("foo", function () {})',
'it.only("foo", function () {})',
'it.each()("foo", function () {})',
{
code: 'it.each``("foo", function () {})',
parserOptions: { ecmaVersion: 6 },
},
'it.concurrent.only("foo", function () {})',
'test.only("foo", function () {})',
'test.each()("foo", function () {})',
{
code: 'test.each``("foo", function () {})',
parserOptions: { ecmaVersion: 6 },
},
'test.concurrent.only("foo", function () {})',
'describe.skip("foo", function () {})',
'it.skip("foo", function () {})',
Expand Down Expand Up @@ -96,5 +106,55 @@ ruleTester.run('no-test-prefixes', rule, {
},
],
},
{
code: 'xit.each``("foo", function () {})',
output: 'it.skip.each``("foo", function () {})',
parserOptions: { ecmaVersion: 6 },
errors: [
{
messageId: 'usePreferredName',
data: { preferredNodeName: 'it.skip.each' },
column: 1,
line: 1,
},
],
},
{
code: 'xtest.each``("foo", function () {})',
output: 'test.skip.each``("foo", function () {})',
parserOptions: { ecmaVersion: 6 },
errors: [
{
messageId: 'usePreferredName',
data: { preferredNodeName: 'test.skip.each' },
column: 1,
line: 1,
},
],
},
{
code: 'xit.each([])("foo", function () {})',
output: 'it.skip.each([])("foo", function () {})',
errors: [
{
messageId: 'usePreferredName',
data: { preferredNodeName: 'it.skip.each' },
column: 1,
line: 1,
},
],
},
{
code: 'xtest.each([])("foo", function () {})',
output: 'test.skip.each([])("foo", function () {})',
errors: [
{
messageId: 'usePreferredName',
data: { preferredNodeName: 'test.skip.each' },
column: 1,
line: 1,
},
],
},
],
});
9 changes: 7 additions & 2 deletions src/rules/consistent-test-it.ts
Expand Up @@ -82,6 +82,11 @@ export default createRule<
describeNestingLevel++;
}

const funcNode =
node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression
? node.callee.tag
: node.callee;

if (
isTestCase(node) &&
describeNestingLevel === 0 &&
Expand All @@ -93,7 +98,7 @@ export default createRule<
messageId: 'consistentMethod',
node: node.callee,
data: { testKeyword, oppositeTestKeyword },
fix: buildFixer(node.callee, nodeName, testKeyword),
fix: buildFixer(funcNode, nodeName, testKeyword),
});
}

Expand All @@ -110,7 +115,7 @@ export default createRule<
messageId: 'consistentMethodWithinDescribe',
node: node.callee,
data: { testKeywordWithinDescribe, oppositeTestKeyword },
fix: buildFixer(node.callee, nodeName, testKeywordWithinDescribe),
fix: buildFixer(funcNode, nodeName, testKeywordWithinDescribe),
});
}
},
Expand Down
7 changes: 7 additions & 0 deletions src/rules/no-disabled-tests.ts
Expand Up @@ -39,6 +39,9 @@ export default createRule({
CallExpression(node) {
const functionName = getNodeName(node.callee);

// prevent duplicate warnings for it.each()()
if (node.callee.type === 'CallExpression') return;

switch (functionName) {
case 'describe.skip':
context.report({ messageId: 'skippedTestSuite', node });
Expand All @@ -48,6 +51,10 @@ export default createRule({
case 'it.concurrent.skip':
case 'test.skip':
case 'test.concurrent.skip':
case 'it.skip.each':
case 'test.skip.each':
case 'xit.each':
case 'xtest.each':
context.report({ messageId: 'skippedTest', node });
break;
}
Expand Down
14 changes: 11 additions & 3 deletions src/rules/no-test-prefixes.ts
@@ -1,3 +1,4 @@
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
import { createRule, getNodeName, isDescribe, isTestCase } from './utils';

export default createRule({
Expand Down Expand Up @@ -27,12 +28,17 @@ export default createRule({

if (!preferredNodeName) return;

const funcNode =
node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression
? node.callee.tag
: node.callee;

context.report({
messageId: 'usePreferredName',
node: node.callee,
data: { preferredNodeName },
fix(fixer) {
return [fixer.replaceText(node.callee, preferredNodeName)];
return [fixer.replaceText(funcNode, preferredNodeName)];
},
});
},
Expand All @@ -43,12 +49,14 @@ export default createRule({
function getPreferredNodeName(nodeName: string) {
const firstChar = nodeName.charAt(0);

const suffix = nodeName.endsWith('.each') ? '.each' : '';

if (firstChar === 'f') {
return `${nodeName.slice(1)}.only`;
return `${nodeName.slice(1).replace('.each', '')}.only${suffix}`;
}

if (firstChar === 'x') {
return `${nodeName.slice(1)}.skip`;
return `${nodeName.slice(1).replace('.each', '')}.skip${suffix}`;
}

return null;
Expand Down
16 changes: 15 additions & 1 deletion src/rules/utils.ts
Expand Up @@ -580,10 +580,16 @@ export interface JestFunctionCallExpressionWithIdentifierCallee<
callee: JestFunctionIdentifier<FunctionName>;
}

interface JestFunctionCallExpressionWithTaggedTemplateCallee
extends TSESTree.CallExpression {
callee: TSESTree.TaggedTemplateExpression;
}

export type JestFunctionCallExpression<
FunctionName extends JestFunctionName = JestFunctionName
> =
| JestFunctionCallExpressionWithMemberExpressionCallee<FunctionName>
| JestFunctionCallExpressionWithTaggedTemplateCallee
| JestFunctionCallExpressionWithIdentifierCallee<FunctionName>;

const joinNames = (a: string | null, b: string | null): string | null =>
Expand All @@ -592,7 +598,8 @@ const joinNames = (a: string | null, b: string | null): string | null =>
export function getNodeName(
node:
| JestFunctionMemberExpression<JestFunctionName>
| JestFunctionIdentifier<JestFunctionName>,
| JestFunctionIdentifier<JestFunctionName>
| TSESTree.TaggedTemplateExpression,
): string;
export function getNodeName(node: TSESTree.Node): string | null;
export function getNodeName(node: TSESTree.Node): string | null {
Expand All @@ -601,6 +608,8 @@ export function getNodeName(node: TSESTree.Node): string | null {
}

switch (node.type) {
case AST_NODE_TYPES.TaggedTemplateExpression:
return getNodeName(node.tag);
case AST_NODE_TYPES.MemberExpression:
return joinNames(getNodeName(node.object), getNodeName(node.property));
case AST_NODE_TYPES.NewExpression:
Expand Down Expand Up @@ -651,6 +660,11 @@ export const isTestCase = (
): node is JestFunctionCallExpression<TestCaseName> =>
(node.callee.type === AST_NODE_TYPES.Identifier &&
TestCaseName.hasOwnProperty(node.callee.name)) ||
// e.g. it.each``()
(node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression &&
node.callee.tag.type === AST_NODE_TYPES.MemberExpression &&
isSupportedAccessor(node.callee.tag.property, TestCaseProperty.each)) ||
// e.g. it.concurrent.{skip,only}
(node.callee.type === AST_NODE_TYPES.MemberExpression &&
node.callee.property.type === AST_NODE_TYPES.Identifier &&
TestCaseProperty.hasOwnProperty(node.callee.property.name) &&
Expand Down

0 comments on commit 2341814

Please sign in to comment.