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.
148 lines
6.0 KiB
148 lines
6.0 KiB
2 years ago
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.MAPPING_TO_USER_EVENT = exports.UserEventMethods = 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-user-event';
|
||
|
exports.UserEventMethods = [
|
||
|
'click',
|
||
|
'dblClick',
|
||
|
'type',
|
||
|
'upload',
|
||
|
'clear',
|
||
|
'selectOptions',
|
||
|
'deselectOptions',
|
||
|
'tab',
|
||
|
'hover',
|
||
|
'unhover',
|
||
|
'paste',
|
||
|
];
|
||
|
exports.MAPPING_TO_USER_EVENT = {
|
||
|
click: ['click', 'type', 'selectOptions', 'deselectOptions'],
|
||
|
change: ['upload', 'type', 'clear', 'selectOptions', 'deselectOptions'],
|
||
|
dblClick: ['dblClick'],
|
||
|
input: ['type', 'upload', 'selectOptions', 'deselectOptions', 'paste'],
|
||
|
keyDown: ['type', 'tab'],
|
||
|
keyPress: ['type'],
|
||
|
keyUp: ['type', 'tab'],
|
||
|
mouseDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
||
|
mouseEnter: ['hover', 'selectOptions', 'deselectOptions'],
|
||
|
mouseLeave: ['unhover'],
|
||
|
mouseMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'],
|
||
|
mouseOut: ['unhover'],
|
||
|
mouseOver: ['hover', 'selectOptions', 'deselectOptions'],
|
||
|
mouseUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
||
|
paste: ['paste'],
|
||
|
pointerDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
||
|
pointerEnter: ['hover', 'selectOptions', 'deselectOptions'],
|
||
|
pointerLeave: ['unhover'],
|
||
|
pointerMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'],
|
||
|
pointerOut: ['unhover'],
|
||
|
pointerOver: ['hover', 'selectOptions', 'deselectOptions'],
|
||
|
pointerUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
||
|
};
|
||
|
function buildErrorMessage(fireEventMethod) {
|
||
|
const userEventMethods = exports.MAPPING_TO_USER_EVENT[fireEventMethod].map((methodName) => `userEvent.${methodName}`);
|
||
|
return userEventMethods.join(', ').replace(/, ([a-zA-Z.]+)$/, ', or $1');
|
||
|
}
|
||
|
const fireEventMappedMethods = Object.keys(exports.MAPPING_TO_USER_EVENT);
|
||
|
exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
|
||
|
name: exports.RULE_NAME,
|
||
|
meta: {
|
||
|
type: 'suggestion',
|
||
|
docs: {
|
||
|
description: 'Suggest using `userEvent` over `fireEvent` for simulating user interactions',
|
||
|
recommendedConfig: {
|
||
|
dom: false,
|
||
|
angular: false,
|
||
|
react: false,
|
||
|
vue: false,
|
||
|
marko: false,
|
||
|
},
|
||
|
},
|
||
|
messages: {
|
||
|
preferUserEvent: 'Prefer using {{userEventMethods}} over fireEvent.{{fireEventMethod}}',
|
||
|
},
|
||
|
schema: [
|
||
|
{
|
||
|
type: 'object',
|
||
|
properties: {
|
||
|
allowedMethods: { type: 'array' },
|
||
|
},
|
||
|
},
|
||
|
],
|
||
|
},
|
||
|
defaultOptions: [{ allowedMethods: [] }],
|
||
|
create(context, [options], helpers) {
|
||
|
const { allowedMethods } = options;
|
||
|
const createEventVariables = {};
|
||
|
const isfireEventMethodAllowed = (methodName) => !fireEventMappedMethods.includes(methodName) ||
|
||
|
allowedMethods.includes(methodName);
|
||
|
const getFireEventMethodName = (callExpressionNode, node) => {
|
||
|
if (!utils_1.ASTUtils.isIdentifier(callExpressionNode.callee) &&
|
||
|
!(0, node_utils_1.isMemberExpression)(callExpressionNode.callee)) {
|
||
|
return node.name;
|
||
|
}
|
||
|
const secondArgument = callExpressionNode.arguments[1];
|
||
|
if (utils_1.ASTUtils.isIdentifier(secondArgument) &&
|
||
|
createEventVariables[secondArgument.name] !== undefined) {
|
||
|
return createEventVariables[secondArgument.name];
|
||
|
}
|
||
|
if (!(0, node_utils_1.isCallExpression)(secondArgument) ||
|
||
|
!helpers.isCreateEventUtil(secondArgument)) {
|
||
|
return node.name;
|
||
|
}
|
||
|
if (utils_1.ASTUtils.isIdentifier(secondArgument.callee)) {
|
||
|
return secondArgument.arguments[0]
|
||
|
.value;
|
||
|
}
|
||
|
return secondArgument.callee
|
||
|
.property.name;
|
||
|
};
|
||
|
return {
|
||
|
'CallExpression Identifier'(node) {
|
||
|
if (!helpers.isFireEventMethod(node)) {
|
||
|
return;
|
||
|
}
|
||
|
const closestCallExpression = (0, node_utils_1.findClosestCallExpressionNode)(node, true);
|
||
|
if (!closestCallExpression) {
|
||
|
return;
|
||
|
}
|
||
|
const fireEventMethodName = getFireEventMethodName(closestCallExpression, node);
|
||
|
if (!fireEventMethodName ||
|
||
|
isfireEventMethodAllowed(fireEventMethodName)) {
|
||
|
return;
|
||
|
}
|
||
|
context.report({
|
||
|
node: closestCallExpression.callee,
|
||
|
messageId: 'preferUserEvent',
|
||
|
data: {
|
||
|
userEventMethods: buildErrorMessage(fireEventMethodName),
|
||
|
fireEventMethod: fireEventMethodName,
|
||
|
},
|
||
|
});
|
||
|
},
|
||
|
VariableDeclarator(node) {
|
||
|
if (!(0, node_utils_1.isCallExpression)(node.init) ||
|
||
|
!helpers.isCreateEventUtil(node.init) ||
|
||
|
!utils_1.ASTUtils.isIdentifier(node.id)) {
|
||
|
return;
|
||
|
}
|
||
|
let fireEventMethodName = '';
|
||
|
if ((0, node_utils_1.isMemberExpression)(node.init.callee) &&
|
||
|
utils_1.ASTUtils.isIdentifier(node.init.callee.property)) {
|
||
|
fireEventMethodName = node.init.callee.property.name;
|
||
|
}
|
||
|
else if (node.init.arguments.length > 0) {
|
||
|
fireEventMethodName = node.init.arguments[0]
|
||
|
.value;
|
||
|
}
|
||
|
if (!isfireEventMethodAllowed(fireEventMethodName)) {
|
||
|
createEventVariables[node.id.name] = fireEventMethodName;
|
||
|
}
|
||
|
},
|
||
|
};
|
||
|
},
|
||
|
});
|