prefer-screen-queries.js 5.6 KB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
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-screen-queries';
const ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING = [
    'container',
    'baseElement',
];
function usesContainerOrBaseElement(node) {
    const secondArgument = node.arguments[1];
    return ((0, node_utils_1.isObjectExpression)(secondArgument) &&
        secondArgument.properties.some((property) => (0, node_utils_1.isProperty)(property) &&
            utils_1.ASTUtils.isIdentifier(property.key) &&
            ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING.includes(property.key.name)));
}
exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
    name: exports.RULE_NAME,
    meta: {
        type: 'suggestion',
        docs: {
            description: 'Suggest using `screen` while querying',
            recommendedConfig: {
                dom: 'error',
                angular: 'error',
                react: 'error',
                vue: 'error',
                marko: 'error',
            },
        },
        messages: {
            preferScreenQueries: 'Avoid destructuring queries from `render` result, use `screen.{{ name }}` instead',
        },
        schema: [],
    },
    defaultOptions: [],
    create(context, _, helpers) {
        const renderWrapperNames = [];
        function detectRenderWrapper(node) {
            const innerFunction = (0, node_utils_1.getInnermostReturningFunction)(context, node);
            if (innerFunction) {
                renderWrapperNames.push((0, node_utils_1.getFunctionName)(innerFunction));
            }
        }
        function isReportableRender(node) {
            return (helpers.isRenderUtil(node) || renderWrapperNames.includes(node.name));
        }
        function reportInvalidUsage(node) {
            context.report({
                node,
                messageId: 'preferScreenQueries',
                data: {
                    name: node.name,
                },
            });
        }
        function saveSafeDestructuredQueries(node) {
            if ((0, node_utils_1.isObjectPattern)(node.id)) {
                for (const property of node.id.properties) {
                    if ((0, node_utils_1.isProperty)(property) &&
                        utils_1.ASTUtils.isIdentifier(property.key) &&
                        helpers.isBuiltInQuery(property.key)) {
                        safeDestructuredQueries.push(property.key.name);
                    }
                }
            }
        }
        function isIdentifierAllowed(name) {
            return ['screen', ...withinDeclaredVariables].includes(name);
        }
        const safeDestructuredQueries = [];
        const withinDeclaredVariables = [];
        return {
            VariableDeclarator(node) {
                if (!(0, node_utils_1.isCallExpression)(node.init) ||
                    !utils_1.ASTUtils.isIdentifier(node.init.callee)) {
                    return;
                }
                const isComingFromValidRender = isReportableRender(node.init.callee);
                if (!isComingFromValidRender) {
                    saveSafeDestructuredQueries(node);
                }
                const isWithinFunction = node.init.callee.name === 'within';
                const usesRenderOptions = isComingFromValidRender && usesContainerOrBaseElement(node.init);
                if (!isWithinFunction && !usesRenderOptions) {
                    return;
                }
                if ((0, node_utils_1.isObjectPattern)(node.id)) {
                    saveSafeDestructuredQueries(node);
                }
                else if (utils_1.ASTUtils.isIdentifier(node.id)) {
                    withinDeclaredVariables.push(node.id.name);
                }
            },
            CallExpression(node) {
                const identifierNode = (0, node_utils_1.getDeepestIdentifierNode)(node);
                if (!identifierNode) {
                    return;
                }
                if (helpers.isRenderUtil(identifierNode)) {
                    detectRenderWrapper(identifierNode);
                }
                if (!helpers.isBuiltInQuery(identifierNode)) {
                    return;
                }
                if (!(0, node_utils_1.isMemberExpression)(identifierNode.parent)) {
                    const isSafeDestructuredQuery = safeDestructuredQueries.some((queryName) => queryName === identifierNode.name);
                    if (isSafeDestructuredQuery) {
                        return;
                    }
                    reportInvalidUsage(identifierNode);
                    return;
                }
                const memberExpressionNode = identifierNode.parent;
                if ((0, node_utils_1.isCallExpression)(memberExpressionNode.object) &&
                    utils_1.ASTUtils.isIdentifier(memberExpressionNode.object.callee) &&
                    memberExpressionNode.object.callee.name !== 'within' &&
                    isReportableRender(memberExpressionNode.object.callee) &&
                    !usesContainerOrBaseElement(memberExpressionNode.object)) {
                    reportInvalidUsage(identifierNode);
                    return;
                }
                if (utils_1.ASTUtils.isIdentifier(memberExpressionNode.object) &&
                    !isIdentifierAllowed(memberExpressionNode.object.name)) {
                    reportInvalidUsage(identifierNode);
                }
            },
        };
    },
});