| 1 | "use strict";
|
|---|
| 2 | Object.defineProperty(exports, "__esModule", { value: true });
|
|---|
| 3 | exports.MAPPING_TO_USER_EVENT = exports.UserEventMethods = exports.RULE_NAME = void 0;
|
|---|
| 4 | const utils_1 = require("@typescript-eslint/utils");
|
|---|
| 5 | const create_testing_library_rule_1 = require("../create-testing-library-rule");
|
|---|
| 6 | const node_utils_1 = require("../node-utils");
|
|---|
| 7 | exports.RULE_NAME = 'prefer-user-event';
|
|---|
| 8 | exports.UserEventMethods = [
|
|---|
| 9 | 'click',
|
|---|
| 10 | 'dblClick',
|
|---|
| 11 | 'type',
|
|---|
| 12 | 'upload',
|
|---|
| 13 | 'clear',
|
|---|
| 14 | 'selectOptions',
|
|---|
| 15 | 'deselectOptions',
|
|---|
| 16 | 'tab',
|
|---|
| 17 | 'hover',
|
|---|
| 18 | 'unhover',
|
|---|
| 19 | 'paste',
|
|---|
| 20 | ];
|
|---|
| 21 | exports.MAPPING_TO_USER_EVENT = {
|
|---|
| 22 | click: ['click', 'type', 'selectOptions', 'deselectOptions'],
|
|---|
| 23 | change: ['upload', 'type', 'clear', 'selectOptions', 'deselectOptions'],
|
|---|
| 24 | dblClick: ['dblClick'],
|
|---|
| 25 | input: ['type', 'upload', 'selectOptions', 'deselectOptions', 'paste'],
|
|---|
| 26 | keyDown: ['type', 'tab'],
|
|---|
| 27 | keyPress: ['type'],
|
|---|
| 28 | keyUp: ['type', 'tab'],
|
|---|
| 29 | mouseDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
|---|
| 30 | mouseEnter: ['hover', 'selectOptions', 'deselectOptions'],
|
|---|
| 31 | mouseLeave: ['unhover'],
|
|---|
| 32 | mouseMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'],
|
|---|
| 33 | mouseOut: ['unhover'],
|
|---|
| 34 | mouseOver: ['hover', 'selectOptions', 'deselectOptions'],
|
|---|
| 35 | mouseUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
|---|
| 36 | paste: ['paste'],
|
|---|
| 37 | pointerDown: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
|---|
| 38 | pointerEnter: ['hover', 'selectOptions', 'deselectOptions'],
|
|---|
| 39 | pointerLeave: ['unhover'],
|
|---|
| 40 | pointerMove: ['hover', 'unhover', 'selectOptions', 'deselectOptions'],
|
|---|
| 41 | pointerOut: ['unhover'],
|
|---|
| 42 | pointerOver: ['hover', 'selectOptions', 'deselectOptions'],
|
|---|
| 43 | pointerUp: ['click', 'dblClick', 'selectOptions', 'deselectOptions'],
|
|---|
| 44 | };
|
|---|
| 45 | function buildErrorMessage(fireEventMethod) {
|
|---|
| 46 | const userEventMethods = exports.MAPPING_TO_USER_EVENT[fireEventMethod].map((methodName) => `userEvent.${methodName}`);
|
|---|
| 47 | return userEventMethods.join(', ').replace(/, ([a-zA-Z.]+)$/, ', or $1');
|
|---|
| 48 | }
|
|---|
| 49 | const fireEventMappedMethods = Object.keys(exports.MAPPING_TO_USER_EVENT);
|
|---|
| 50 | exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
|
|---|
| 51 | name: exports.RULE_NAME,
|
|---|
| 52 | meta: {
|
|---|
| 53 | type: 'suggestion',
|
|---|
| 54 | docs: {
|
|---|
| 55 | description: 'Suggest using `userEvent` over `fireEvent` for simulating user interactions',
|
|---|
| 56 | recommendedConfig: {
|
|---|
| 57 | dom: false,
|
|---|
| 58 | angular: false,
|
|---|
| 59 | react: false,
|
|---|
| 60 | vue: false,
|
|---|
| 61 | marko: false,
|
|---|
| 62 | },
|
|---|
| 63 | },
|
|---|
| 64 | messages: {
|
|---|
| 65 | preferUserEvent: 'Prefer using {{userEventMethods}} over fireEvent.{{fireEventMethod}}',
|
|---|
| 66 | },
|
|---|
| 67 | schema: [
|
|---|
| 68 | {
|
|---|
| 69 | type: 'object',
|
|---|
| 70 | properties: {
|
|---|
| 71 | allowedMethods: { type: 'array' },
|
|---|
| 72 | },
|
|---|
| 73 | },
|
|---|
| 74 | ],
|
|---|
| 75 | },
|
|---|
| 76 | defaultOptions: [{ allowedMethods: [] }],
|
|---|
| 77 | create(context, [options], helpers) {
|
|---|
| 78 | const { allowedMethods } = options;
|
|---|
| 79 | const createEventVariables = {};
|
|---|
| 80 | const isfireEventMethodAllowed = (methodName) => !fireEventMappedMethods.includes(methodName) ||
|
|---|
| 81 | allowedMethods.includes(methodName);
|
|---|
| 82 | const getFireEventMethodName = (callExpressionNode, node) => {
|
|---|
| 83 | if (!utils_1.ASTUtils.isIdentifier(callExpressionNode.callee) &&
|
|---|
| 84 | !(0, node_utils_1.isMemberExpression)(callExpressionNode.callee)) {
|
|---|
| 85 | return node.name;
|
|---|
| 86 | }
|
|---|
| 87 | const secondArgument = callExpressionNode.arguments[1];
|
|---|
| 88 | if (utils_1.ASTUtils.isIdentifier(secondArgument) &&
|
|---|
| 89 | createEventVariables[secondArgument.name] !== undefined) {
|
|---|
| 90 | return createEventVariables[secondArgument.name];
|
|---|
| 91 | }
|
|---|
| 92 | if (!(0, node_utils_1.isCallExpression)(secondArgument) ||
|
|---|
| 93 | !helpers.isCreateEventUtil(secondArgument)) {
|
|---|
| 94 | return node.name;
|
|---|
| 95 | }
|
|---|
| 96 | if (utils_1.ASTUtils.isIdentifier(secondArgument.callee)) {
|
|---|
| 97 | return secondArgument.arguments[0]
|
|---|
| 98 | .value;
|
|---|
| 99 | }
|
|---|
| 100 | return secondArgument.callee
|
|---|
| 101 | .property.name;
|
|---|
| 102 | };
|
|---|
| 103 | return {
|
|---|
| 104 | 'CallExpression Identifier'(node) {
|
|---|
| 105 | if (!helpers.isFireEventMethod(node)) {
|
|---|
| 106 | return;
|
|---|
| 107 | }
|
|---|
| 108 | const closestCallExpression = (0, node_utils_1.findClosestCallExpressionNode)(node, true);
|
|---|
| 109 | if (!closestCallExpression) {
|
|---|
| 110 | return;
|
|---|
| 111 | }
|
|---|
| 112 | const fireEventMethodName = getFireEventMethodName(closestCallExpression, node);
|
|---|
| 113 | if (!fireEventMethodName ||
|
|---|
| 114 | isfireEventMethodAllowed(fireEventMethodName)) {
|
|---|
| 115 | return;
|
|---|
| 116 | }
|
|---|
| 117 | context.report({
|
|---|
| 118 | node: closestCallExpression.callee,
|
|---|
| 119 | messageId: 'preferUserEvent',
|
|---|
| 120 | data: {
|
|---|
| 121 | userEventMethods: buildErrorMessage(fireEventMethodName),
|
|---|
| 122 | fireEventMethod: fireEventMethodName,
|
|---|
| 123 | },
|
|---|
| 124 | });
|
|---|
| 125 | },
|
|---|
| 126 | VariableDeclarator(node) {
|
|---|
| 127 | if (!(0, node_utils_1.isCallExpression)(node.init) ||
|
|---|
| 128 | !helpers.isCreateEventUtil(node.init) ||
|
|---|
| 129 | !utils_1.ASTUtils.isIdentifier(node.id)) {
|
|---|
| 130 | return;
|
|---|
| 131 | }
|
|---|
| 132 | let fireEventMethodName = '';
|
|---|
| 133 | if ((0, node_utils_1.isMemberExpression)(node.init.callee) &&
|
|---|
| 134 | utils_1.ASTUtils.isIdentifier(node.init.callee.property)) {
|
|---|
| 135 | fireEventMethodName = node.init.callee.property.name;
|
|---|
| 136 | }
|
|---|
| 137 | else if (node.init.arguments.length > 0) {
|
|---|
| 138 | fireEventMethodName = node.init.arguments[0]
|
|---|
| 139 | .value;
|
|---|
| 140 | }
|
|---|
| 141 | if (!isfireEventMethodAllowed(fireEventMethodName)) {
|
|---|
| 142 | createEventVariables[node.id.name] = fireEventMethodName;
|
|---|
| 143 | }
|
|---|
| 144 | },
|
|---|
| 145 | };
|
|---|
| 146 | },
|
|---|
| 147 | });
|
|---|