prefer-screen-queries.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. 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-screen-queries';
  8. const ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING = [
  9. 'container',
  10. 'baseElement',
  11. ];
  12. function usesContainerOrBaseElement(node) {
  13. const secondArgument = node.arguments[1];
  14. return ((0, node_utils_1.isObjectExpression)(secondArgument) &&
  15. secondArgument.properties.some((property) => (0, node_utils_1.isProperty)(property) &&
  16. utils_1.ASTUtils.isIdentifier(property.key) &&
  17. ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING.includes(property.key.name)));
  18. }
  19. exports.default = (0, create_testing_library_rule_1.createTestingLibraryRule)({
  20. name: exports.RULE_NAME,
  21. meta: {
  22. type: 'suggestion',
  23. docs: {
  24. description: 'Suggest using `screen` while querying',
  25. recommendedConfig: {
  26. dom: 'error',
  27. angular: 'error',
  28. react: 'error',
  29. vue: 'error',
  30. marko: 'error',
  31. },
  32. },
  33. messages: {
  34. preferScreenQueries: 'Avoid destructuring queries from `render` result, use `screen.{{ name }}` instead',
  35. },
  36. schema: [],
  37. },
  38. defaultOptions: [],
  39. create(context, _, helpers) {
  40. const renderWrapperNames = [];
  41. function detectRenderWrapper(node) {
  42. const innerFunction = (0, node_utils_1.getInnermostReturningFunction)(context, node);
  43. if (innerFunction) {
  44. renderWrapperNames.push((0, node_utils_1.getFunctionName)(innerFunction));
  45. }
  46. }
  47. function isReportableRender(node) {
  48. return (helpers.isRenderUtil(node) || renderWrapperNames.includes(node.name));
  49. }
  50. function reportInvalidUsage(node) {
  51. context.report({
  52. node,
  53. messageId: 'preferScreenQueries',
  54. data: {
  55. name: node.name,
  56. },
  57. });
  58. }
  59. function saveSafeDestructuredQueries(node) {
  60. if ((0, node_utils_1.isObjectPattern)(node.id)) {
  61. for (const property of node.id.properties) {
  62. if ((0, node_utils_1.isProperty)(property) &&
  63. utils_1.ASTUtils.isIdentifier(property.key) &&
  64. helpers.isBuiltInQuery(property.key)) {
  65. safeDestructuredQueries.push(property.key.name);
  66. }
  67. }
  68. }
  69. }
  70. function isIdentifierAllowed(name) {
  71. return ['screen', ...withinDeclaredVariables].includes(name);
  72. }
  73. const safeDestructuredQueries = [];
  74. const withinDeclaredVariables = [];
  75. return {
  76. VariableDeclarator(node) {
  77. if (!(0, node_utils_1.isCallExpression)(node.init) ||
  78. !utils_1.ASTUtils.isIdentifier(node.init.callee)) {
  79. return;
  80. }
  81. const isComingFromValidRender = isReportableRender(node.init.callee);
  82. if (!isComingFromValidRender) {
  83. saveSafeDestructuredQueries(node);
  84. }
  85. const isWithinFunction = node.init.callee.name === 'within';
  86. const usesRenderOptions = isComingFromValidRender && usesContainerOrBaseElement(node.init);
  87. if (!isWithinFunction && !usesRenderOptions) {
  88. return;
  89. }
  90. if ((0, node_utils_1.isObjectPattern)(node.id)) {
  91. saveSafeDestructuredQueries(node);
  92. }
  93. else if (utils_1.ASTUtils.isIdentifier(node.id)) {
  94. withinDeclaredVariables.push(node.id.name);
  95. }
  96. },
  97. CallExpression(node) {
  98. const identifierNode = (0, node_utils_1.getDeepestIdentifierNode)(node);
  99. if (!identifierNode) {
  100. return;
  101. }
  102. if (helpers.isRenderUtil(identifierNode)) {
  103. detectRenderWrapper(identifierNode);
  104. }
  105. if (!helpers.isBuiltInQuery(identifierNode)) {
  106. return;
  107. }
  108. if (!(0, node_utils_1.isMemberExpression)(identifierNode.parent)) {
  109. const isSafeDestructuredQuery = safeDestructuredQueries.some((queryName) => queryName === identifierNode.name);
  110. if (isSafeDestructuredQuery) {
  111. return;
  112. }
  113. reportInvalidUsage(identifierNode);
  114. return;
  115. }
  116. const memberExpressionNode = identifierNode.parent;
  117. if ((0, node_utils_1.isCallExpression)(memberExpressionNode.object) &&
  118. utils_1.ASTUtils.isIdentifier(memberExpressionNode.object.callee) &&
  119. memberExpressionNode.object.callee.name !== 'within' &&
  120. isReportableRender(memberExpressionNode.object.callee) &&
  121. !usesContainerOrBaseElement(memberExpressionNode.object)) {
  122. reportInvalidUsage(identifierNode);
  123. return;
  124. }
  125. if (utils_1.ASTUtils.isIdentifier(memberExpressionNode.object) &&
  126. !isIdentifierAllowed(memberExpressionNode.object.name)) {
  127. reportInvalidUsage(identifierNode);
  128. }
  129. },
  130. };
  131. },
  132. });