prefer-user-event.js 6.04 KB
"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;
                }
            },
        };
    },
});