You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
15 KiB
289 lines
15 KiB
2 years ago
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.getFindByQueryVariant = exports.WAIT_METHODS = exports.RULE_NAME = void 0;
|
||
|
const utils_1 = require("@typescript-eslint/utils");
|
||
|
const create_testing_library_rule_1 = require("../create-testing-library-rule");
|
||
|
const node_utils_1 = require("../node-utils");
|
||
|
exports.RULE_NAME = 'prefer-find-by';
|
||
|
exports.WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'];
|
||
|
function getFindByQueryVariant(queryMethod) {
|
||
|
return queryMethod.includes('All') ? 'findAllBy' : 'findBy';
|
||
|
}
|
||
|
exports.getFindByQueryVariant = getFindByQueryVariant;
|
||
|
function findRenderDefinitionDeclaration(scope, query) {
|
||
|
var _a;
|
||
|
if (!scope) {
|
||
|
return null;
|
||
|
}
|
||
|
const variable = scope.variables.find((v) => v.name === query);
|
||
|
if (variable) {
|
||
|
return ((_a = variable.defs
|
||
|
.map(({ name }) => name)
|
||
|
.filter(utils_1.ASTUtils.isIdentifier)
|
||
|
.find(({ name }) => name === query)) !== null && _a !== void 0 ? _a : null);
|
||
|
}
|
||
|
return findRenderDefinitionDeclaration(scope.upper, query);
|
||
|
}
|
||
|
exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
|
||
|
name: exports.RULE_NAME,
|
||
|
meta: {
|
||
|
type: 'suggestion',
|
||
|
docs: {
|
||
|
description: 'Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements',
|
||
|
recommendedConfig: {
|
||
|
dom: 'error',
|
||
|
angular: 'error',
|
||
|
react: 'error',
|
||
|
vue: 'error',
|
||
|
marko: 'error',
|
||
|
},
|
||
|
},
|
||
|
messages: {
|
||
|
preferFindBy: 'Prefer `{{queryVariant}}{{queryMethod}}` query over using `{{waitForMethodName}}` + `{{prevQuery}}`',
|
||
|
},
|
||
|
fixable: 'code',
|
||
|
schema: [],
|
||
|
},
|
||
|
defaultOptions: [],
|
||
|
create(context, _, helpers) {
|
||
|
const sourceCode = context.getSourceCode();
|
||
|
function reportInvalidUsage(node, replacementParams) {
|
||
|
const { queryMethod, queryVariant, prevQuery, waitForMethodName, fix } = replacementParams;
|
||
|
context.report({
|
||
|
node,
|
||
|
messageId: 'preferFindBy',
|
||
|
data: {
|
||
|
queryVariant,
|
||
|
queryMethod,
|
||
|
prevQuery,
|
||
|
waitForMethodName,
|
||
|
},
|
||
|
fix,
|
||
|
});
|
||
|
}
|
||
|
function getWrongQueryNameInAssertion(node) {
|
||
|
if (!(0, node_utils_1.isCallExpression)(node.body) ||
|
||
|
!(0, node_utils_1.isMemberExpression)(node.body.callee)) {
|
||
|
return null;
|
||
|
}
|
||
|
if ((0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.arguments[0]) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee)) {
|
||
|
return node.body.callee.object.arguments[0].callee.name;
|
||
|
}
|
||
|
if (!utils_1.ASTUtils.isIdentifier(node.body.callee.property)) {
|
||
|
return null;
|
||
|
}
|
||
|
if ((0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.arguments[0].callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee.property)) {
|
||
|
return node.body.callee.object.arguments[0].callee.property.name;
|
||
|
}
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.object.arguments[0].callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.object.arguments[0].callee.property)) {
|
||
|
return node.body.callee.object.object.arguments[0].callee.property.name;
|
||
|
}
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object.arguments[0]) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.object.arguments[0].callee)) {
|
||
|
return node.body.callee.object.object.arguments[0].callee.name;
|
||
|
}
|
||
|
return node.body.callee.property.name;
|
||
|
}
|
||
|
function getWrongQueryName(node) {
|
||
|
if (!(0, node_utils_1.isCallExpression)(node.body)) {
|
||
|
return null;
|
||
|
}
|
||
|
if (utils_1.ASTUtils.isIdentifier(node.body.callee) &&
|
||
|
helpers.isSyncQuery(node.body.callee)) {
|
||
|
return node.body.callee.name;
|
||
|
}
|
||
|
return getWrongQueryNameInAssertion(node);
|
||
|
}
|
||
|
function getCaller(node) {
|
||
|
if (!(0, node_utils_1.isCallExpression)(node.body) ||
|
||
|
!(0, node_utils_1.isMemberExpression)(node.body.callee)) {
|
||
|
return null;
|
||
|
}
|
||
|
if (utils_1.ASTUtils.isIdentifier(node.body.callee.object)) {
|
||
|
return node.body.callee.object.name;
|
||
|
}
|
||
|
if ((0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.callee) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.arguments[0].callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee.object)) {
|
||
|
return node.body.callee.object.arguments[0].callee.object.name;
|
||
|
}
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.object.arguments[0].callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.object.arguments[0].callee.object)) {
|
||
|
return node.body.callee.object.object.arguments[0].callee.object.name;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
function isSyncQuery(node) {
|
||
|
if (!(0, node_utils_1.isCallExpression)(node.body)) {
|
||
|
return false;
|
||
|
}
|
||
|
const isQuery = utils_1.ASTUtils.isIdentifier(node.body.callee) &&
|
||
|
helpers.isSyncQuery(node.body.callee);
|
||
|
const isWrappedInPresenceAssert = (0, node_utils_1.isMemberExpression)(node.body.callee) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.arguments[0]) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee) &&
|
||
|
helpers.isSyncQuery(node.body.callee.object.arguments[0].callee) &&
|
||
|
helpers.isPresenceAssert(node.body.callee);
|
||
|
const isWrappedInNegatedPresenceAssert = (0, node_utils_1.isMemberExpression)(node.body.callee) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object.arguments[0]) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.object.arguments[0].callee) &&
|
||
|
helpers.isSyncQuery(node.body.callee.object.object.arguments[0].callee) &&
|
||
|
helpers.isPresenceAssert(node.body.callee.object);
|
||
|
return (isQuery || isWrappedInPresenceAssert || isWrappedInNegatedPresenceAssert);
|
||
|
}
|
||
|
function isScreenSyncQuery(node) {
|
||
|
if (!(0, node_utils_1.isArrowFunctionExpression)(node) || !(0, node_utils_1.isCallExpression)(node.body)) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!(0, node_utils_1.isMemberExpression)(node.body.callee) ||
|
||
|
!utils_1.ASTUtils.isIdentifier(node.body.callee.property)) {
|
||
|
return false;
|
||
|
}
|
||
|
if (!utils_1.ASTUtils.isIdentifier(node.body.callee.object) &&
|
||
|
!(0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
!(0, node_utils_1.isMemberExpression)(node.body.callee.object)) {
|
||
|
return false;
|
||
|
}
|
||
|
const isWrappedInPresenceAssert = helpers.isPresenceAssert(node.body.callee) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.arguments[0].callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.body.callee.object.arguments[0].callee.object);
|
||
|
const isWrappedInNegatedPresenceAssert = (0, node_utils_1.isMemberExpression)(node.body.callee.object) &&
|
||
|
helpers.isPresenceAssert(node.body.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.body.callee.object.object.arguments[0]) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.body.callee.object.object.arguments[0].callee);
|
||
|
return (helpers.isSyncQuery(node.body.callee.property) ||
|
||
|
isWrappedInPresenceAssert ||
|
||
|
isWrappedInNegatedPresenceAssert);
|
||
|
}
|
||
|
function getQueryArguments(node) {
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.callee) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.callee.object.arguments[0])) {
|
||
|
return node.callee.object.arguments[0].arguments;
|
||
|
}
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.callee) &&
|
||
|
(0, node_utils_1.isMemberExpression)(node.callee.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.callee.object.object) &&
|
||
|
(0, node_utils_1.isCallExpression)(node.callee.object.object.arguments[0])) {
|
||
|
return node.callee.object.object.arguments[0].arguments;
|
||
|
}
|
||
|
return node.arguments;
|
||
|
}
|
||
|
return {
|
||
|
'AwaitExpression > CallExpression'(node) {
|
||
|
if (!utils_1.ASTUtils.isIdentifier(node.callee) ||
|
||
|
!helpers.isAsyncUtil(node.callee, exports.WAIT_METHODS)) {
|
||
|
return;
|
||
|
}
|
||
|
const argument = node.arguments[0];
|
||
|
if (!(0, node_utils_1.isArrowFunctionExpression)(argument) ||
|
||
|
!(0, node_utils_1.isCallExpression)(argument.body)) {
|
||
|
return;
|
||
|
}
|
||
|
const waitForMethodName = node.callee.name;
|
||
|
if (isScreenSyncQuery(argument)) {
|
||
|
const caller = getCaller(argument);
|
||
|
if (!caller) {
|
||
|
return;
|
||
|
}
|
||
|
const fullQueryMethod = getWrongQueryName(argument);
|
||
|
if (!fullQueryMethod) {
|
||
|
return;
|
||
|
}
|
||
|
const queryVariant = getFindByQueryVariant(fullQueryMethod);
|
||
|
const callArguments = getQueryArguments(argument.body);
|
||
|
const queryMethod = fullQueryMethod.split('By')[1];
|
||
|
if (!queryMethod) {
|
||
|
return;
|
||
|
}
|
||
|
reportInvalidUsage(node, {
|
||
|
queryMethod,
|
||
|
queryVariant,
|
||
|
prevQuery: fullQueryMethod,
|
||
|
waitForMethodName,
|
||
|
fix(fixer) {
|
||
|
const property = argument.body
|
||
|
.callee.property;
|
||
|
if (helpers.isCustomQuery(property)) {
|
||
|
return null;
|
||
|
}
|
||
|
const newCode = `${caller}.${queryVariant}${queryMethod}(${callArguments
|
||
|
.map((callArgNode) => sourceCode.getText(callArgNode))
|
||
|
.join(', ')})`;
|
||
|
return fixer.replaceText(node, newCode);
|
||
|
},
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
if (!isSyncQuery(argument)) {
|
||
|
return;
|
||
|
}
|
||
|
const fullQueryMethod = getWrongQueryName(argument);
|
||
|
if (!fullQueryMethod) {
|
||
|
return;
|
||
|
}
|
||
|
const queryMethod = fullQueryMethod.split('By')[1];
|
||
|
const queryVariant = getFindByQueryVariant(fullQueryMethod);
|
||
|
const callArguments = getQueryArguments(argument.body);
|
||
|
reportInvalidUsage(node, {
|
||
|
queryMethod,
|
||
|
queryVariant,
|
||
|
prevQuery: fullQueryMethod,
|
||
|
waitForMethodName,
|
||
|
fix(fixer) {
|
||
|
if (helpers.isCustomQuery(argument.body
|
||
|
.callee)) {
|
||
|
return null;
|
||
|
}
|
||
|
const findByMethod = `${queryVariant}${queryMethod}`;
|
||
|
const allFixes = [];
|
||
|
const newCode = `${findByMethod}(${callArguments
|
||
|
.map((callArgNode) => sourceCode.getText(callArgNode))
|
||
|
.join(', ')})`;
|
||
|
allFixes.push(fixer.replaceText(node, newCode));
|
||
|
const definition = findRenderDefinitionDeclaration(context.getScope(), fullQueryMethod);
|
||
|
if (!definition) {
|
||
|
return allFixes;
|
||
|
}
|
||
|
if (definition.parent &&
|
||
|
(0, node_utils_1.isObjectPattern)(definition.parent.parent)) {
|
||
|
const allVariableDeclarations = definition.parent.parent;
|
||
|
if (allVariableDeclarations.properties.some((p) => (0, node_utils_1.isProperty)(p) &&
|
||
|
utils_1.ASTUtils.isIdentifier(p.key) &&
|
||
|
p.key.name === findByMethod)) {
|
||
|
return allFixes;
|
||
|
}
|
||
|
const textDestructuring = sourceCode.getText(allVariableDeclarations);
|
||
|
const text = textDestructuring.replace(/(\s*})$/, `, ${findByMethod}$1`);
|
||
|
allFixes.push(fixer.replaceText(allVariableDeclarations, text));
|
||
|
}
|
||
|
return allFixes;
|
||
|
},
|
||
|
});
|
||
|
},
|
||
|
};
|
||
|
},
|
||
|
});
|