eslint-plugin-testing-library
Version:
ESLint plugin to follow best practices and anticipate common mistakes when writing tests with Testing Library
1,112 lines (1,096 loc) • 161 kB
JavaScript
import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils";
import { DefinitionType, ScopeType } from "@typescript-eslint/scope-manager";
import { isAwaitExpression, isIdentifier } from "@typescript-eslint/utils/ast-utils";
//#region src/configs/angular.ts
var angular_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: "userEvent" }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-events": ["error", { eventModules: ["fire-event"] }],
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "angular"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error"
} };
//#endregion
//#region src/configs/dom.ts
var dom_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: "userEvent" }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-events": ["error", { eventModules: ["fire-event"] }],
"testing-library/no-await-sync-queries": "error",
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error"
} };
//#endregion
//#region src/configs/marko.ts
var marko_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: ["fireEvent", "userEvent"] }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "marko"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-unnecessary-act": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error"
} };
//#endregion
//#region src/configs/react.ts
var react_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: "userEvent" }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-events": ["error", { eventModules: ["fire-event"] }],
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "react"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-manual-cleanup": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-unnecessary-act": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error"
} };
//#endregion
//#region src/configs/svelte.ts
var svelte_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: ["fireEvent", "userEvent"] }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "svelte"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-manual-cleanup": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error"
} };
//#endregion
//#region src/configs/vue.ts
var vue_default = { rules: {
"testing-library/await-async-events": ["error", { eventModule: ["fireEvent", "userEvent"] }],
"testing-library/await-async-queries": "error",
"testing-library/await-async-utils": "error",
"testing-library/no-await-sync-queries": "error",
"testing-library/no-container": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": ["error", "vue"],
"testing-library/no-global-regexp-flag-in-query": "error",
"testing-library/no-manual-cleanup": "error",
"testing-library/no-node-access": "error",
"testing-library/no-promise-in-fire-event": "error",
"testing-library/no-render-in-lifecycle": "error",
"testing-library/no-wait-for-multiple-assertions": "error",
"testing-library/no-wait-for-side-effects": "error",
"testing-library/no-wait-for-snapshot": "error",
"testing-library/prefer-find-by": "error",
"testing-library/prefer-presence-queries": "error",
"testing-library/prefer-query-by-disappearance": "error",
"testing-library/prefer-screen-queries": "error",
"testing-library/render-result-naming-convention": "error"
} };
//#endregion
//#region src/configs/index.ts
const baseConfigs = {
dom: dom_default,
angular: angular_default,
react: react_default,
vue: vue_default,
svelte: svelte_default,
marko: marko_default
};
//#endregion
//#region src/utils/compat.ts
/* istanbul ignore next */
const getFilename = (context) => {
return context.filename ?? context.getFilename();
};
/* istanbul ignore next */
const getSourceCode = (context) => {
return context.sourceCode ?? context.getSourceCode();
};
/* istanbul ignore next */
const getScope = (context, node) => {
return getSourceCode(context).getScope?.(node) ?? context.getScope();
};
/* istanbul ignore next */
const getDeclaredVariables = (context, node) => {
return getSourceCode(context).getDeclaredVariables?.(node) ?? context.getDeclaredVariables(node);
};
//#endregion
//#region src/node-utils/is-node-of-type.ts
const isArrayExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.ArrayExpression);
const isArrowFunctionExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.ArrowFunctionExpression);
const isBlockStatement = ASTUtils.isNodeOfType(AST_NODE_TYPES.BlockStatement);
const isCallExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.CallExpression);
const isExpressionStatement = ASTUtils.isNodeOfType(AST_NODE_TYPES.ExpressionStatement);
const isVariableDeclaration = ASTUtils.isNodeOfType(AST_NODE_TYPES.VariableDeclaration);
const isAssignmentExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.AssignmentExpression);
const isChainExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.ChainExpression);
const isSequenceExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.SequenceExpression);
const isImportDeclaration = ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportDeclaration);
const isImportDefaultSpecifier = ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportDefaultSpecifier);
const isTSImportEqualsDeclaration = ASTUtils.isNodeOfType(AST_NODE_TYPES.TSImportEqualsDeclaration);
const isImportExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportExpression);
const isImportNamespaceSpecifier = ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportNamespaceSpecifier);
const isImportSpecifier = ASTUtils.isNodeOfType(AST_NODE_TYPES.ImportSpecifier);
const isJSXAttribute = ASTUtils.isNodeOfType(AST_NODE_TYPES.JSXAttribute);
const isLiteral = ASTUtils.isNodeOfType(AST_NODE_TYPES.Literal);
const isTemplateLiteral = ASTUtils.isNodeOfType(AST_NODE_TYPES.TemplateLiteral);
const isMemberExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.MemberExpression);
const isNewExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.NewExpression);
const isObjectExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.ObjectExpression);
const isObjectPattern = ASTUtils.isNodeOfType(AST_NODE_TYPES.ObjectPattern);
const isProperty = ASTUtils.isNodeOfType(AST_NODE_TYPES.Property);
const isReturnStatement = ASTUtils.isNodeOfType(AST_NODE_TYPES.ReturnStatement);
const isFunctionExpression = ASTUtils.isNodeOfType(AST_NODE_TYPES.FunctionExpression);
const isFunctionDeclaration = ASTUtils.isNodeOfType(AST_NODE_TYPES.FunctionDeclaration);
//#endregion
//#region src/node-utils/index.ts
const ValidLeftHandSideExpressions = [
AST_NODE_TYPES.CallExpression,
AST_NODE_TYPES.ClassExpression,
AST_NODE_TYPES.ClassDeclaration,
AST_NODE_TYPES.FunctionExpression,
AST_NODE_TYPES.Literal,
AST_NODE_TYPES.TemplateLiteral,
AST_NODE_TYPES.MemberExpression,
AST_NODE_TYPES.ArrayExpression,
AST_NODE_TYPES.ArrayPattern,
AST_NODE_TYPES.ClassExpression,
AST_NODE_TYPES.FunctionExpression,
AST_NODE_TYPES.Identifier,
AST_NODE_TYPES.JSXElement,
AST_NODE_TYPES.JSXFragment,
AST_NODE_TYPES.JSXOpeningElement,
AST_NODE_TYPES.MetaProperty,
AST_NODE_TYPES.ObjectExpression,
AST_NODE_TYPES.ObjectPattern,
AST_NODE_TYPES.Super,
AST_NODE_TYPES.ThisExpression,
AST_NODE_TYPES.TSNullKeyword,
AST_NODE_TYPES.TaggedTemplateExpression,
AST_NODE_TYPES.TSNonNullExpression,
AST_NODE_TYPES.TSAsExpression,
AST_NODE_TYPES.ArrowFunctionExpression
];
/**
* Finds the closest CallExpression node for a given node.
* @param node
* @param shouldRestrictInnerScope - If true, CallExpression must belong to innermost scope of given node
*/
function findClosestCallExpressionNode(node, shouldRestrictInnerScope = false) {
if (isCallExpression(node)) return node;
if (!node?.parent) return null;
if (shouldRestrictInnerScope && !ValidLeftHandSideExpressions.includes(node.parent.type)) return null;
return findClosestCallExpressionNode(node.parent, shouldRestrictInnerScope);
}
function findClosestVariableDeclaratorNode(node) {
if (!node) return null;
if (ASTUtils.isVariableDeclarator(node)) return node;
return findClosestVariableDeclaratorNode(node.parent);
}
function findClosestFunctionExpressionNode(node) {
if (!node) return null;
if (isArrowFunctionExpression(node) || isFunctionExpression(node) || isFunctionDeclaration(node)) return node;
return findClosestFunctionExpressionNode(node.parent);
}
/**
* TODO: remove this one in favor of {@link findClosestCallExpressionNode}
*/
function findClosestCallNode(node, name$1) {
if (!node.parent) return null;
if (isCallExpression(node) && ASTUtils.isIdentifier(node.callee) && node.callee.name === name$1) return node;
else return findClosestCallNode(node.parent, name$1);
}
function hasThenProperty(node) {
return isMemberExpression(node) && ASTUtils.isIdentifier(node.property) && node.property.name === "then";
}
function hasPromiseHandlerProperty(node) {
return isMemberExpression(node) && ASTUtils.isIdentifier(node.property) && [
"then",
"catch",
"finally"
].includes(node.property.name);
}
function hasChainedPromiseHandler(node) {
const parent = node.parent;
if (isCallExpression(parent) && parent.parent) return hasPromiseHandlerProperty(parent.parent);
return !!parent && hasPromiseHandlerProperty(parent);
}
function isPromiseIdentifier(node) {
return ASTUtils.isIdentifier(node) && node.name === "Promise";
}
function isPromiseAll(node) {
return isMemberExpression(node.callee) && isPromiseIdentifier(node.callee.object) && ASTUtils.isIdentifier(node.callee.property) && node.callee.property.name === "all";
}
function isPromiseAllSettled(node) {
return isMemberExpression(node.callee) && isPromiseIdentifier(node.callee.object) && ASTUtils.isIdentifier(node.callee.property) && node.callee.property.name === "allSettled";
}
/**
* Determines whether a given node belongs to handled `Promise.all` or `Promise.allSettled`
* array expression.
*/
function isPromisesArrayResolved(node) {
const closestCallExpression = findClosestCallExpressionNode(node, true);
if (!closestCallExpression) return false;
return !!closestCallExpression.parent && isArrayExpression(closestCallExpression.parent) && isCallExpression(closestCallExpression.parent.parent) && (isPromiseAll(closestCallExpression.parent.parent) || isPromiseAllSettled(closestCallExpression.parent.parent));
}
/**
* Determines whether an Identifier related to a promise is considered as handled.
*
* It will be considered as handled if:
* - it belongs to the `await` expression
* - it belongs to the `Promise.all` method
* - it belongs to the `Promise.allSettled` method
* - it's chained with the `then`, `catch`, `finally` method
* - it's returned from a function
* - has `resolves` or `rejects` jest methods
* - has `toResolve` or `toReject` jest-extended matchers
* - has a jasmine async matcher
*/
function isPromiseHandled(nodeIdentifier) {
const closestCallExpressionNode = findClosestCallExpressionNode(nodeIdentifier, true);
return [nodeIdentifier, closestCallExpressionNode == null ? null : getRootExpression(closestCallExpressionNode)].filter((node) => node != null).some((node) => {
if (!node.parent) return false;
if (ASTUtils.isAwaitExpression(node.parent)) return true;
if (isArrowFunctionExpression(node.parent) || isReturnStatement(node.parent)) return true;
if (hasClosestExpectHandlesPromise(node.parent)) return true;
if (hasChainedPromiseHandler(node)) return true;
if (isPromisesArrayResolved(node)) return true;
});
}
/**
* For an expression in a parent that evaluates to the expression or another child returns the parent node recursively.
*/
function getRootExpression(expression) {
const { parent } = expression;
if (parent == null) return expression;
switch (parent.type) {
case AST_NODE_TYPES.ConditionalExpression: return getRootExpression(parent);
case AST_NODE_TYPES.LogicalExpression: {
let rootExpression;
switch (parent.operator) {
case "??":
case "||":
rootExpression = getRootExpression(parent);
break;
case "&&":
rootExpression = parent.right === expression ? getRootExpression(parent) : expression;
break;
}
return rootExpression ?? expression;
}
case AST_NODE_TYPES.SequenceExpression: return parent.expressions[parent.expressions.length - 1] === expression ? getRootExpression(parent) : expression;
case AST_NODE_TYPES.ChainExpression: return getRootExpression(parent);
default: return expression;
}
}
function getVariableReferences(context, node) {
if (ASTUtils.isVariableDeclarator(node)) return getDeclaredVariables(context, node)[0]?.references?.slice(1) ?? [];
return [];
}
function getInnermostFunctionScope(context, asyncQueryNode) {
const innermostScope = ASTUtils.getInnermostScope(getScope(context, asyncQueryNode), asyncQueryNode);
if (innermostScope.type === ScopeType.function && ASTUtils.isFunction(innermostScope.block)) return innermostScope;
return null;
}
function getFunctionReturnStatementNode(functionNode) {
if (isBlockStatement(functionNode.body)) {
const returnStatementNode = functionNode.body.body.find((statement) => isReturnStatement(statement));
if (!returnStatementNode) return null;
return returnStatementNode.argument;
} else if (functionNode.expression) return functionNode.body;
return null;
}
/**
* Gets the property identifier node of a given property node.
*
* Not to be confused with {@link getDeepestIdentifierNode}
*
* An example:
* Having `const a = rtl.within('foo').getByRole('button')`:
* if we call `getPropertyIdentifierNode` with `rtl` property node,
* it will return `rtl` identifier node
*/
function getPropertyIdentifierNode(node) {
if (ASTUtils.isIdentifier(node)) return node;
if (isMemberExpression(node)) return getPropertyIdentifierNode(node.object);
if (isCallExpression(node)) return getPropertyIdentifierNode(node.callee);
if (isExpressionStatement(node) || isChainExpression(node)) return getPropertyIdentifierNode(node.expression);
if (ASTUtils.isAwaitExpression(node)) return getPropertyIdentifierNode(node.argument);
return null;
}
/**
* Gets the deepest identifier node in the expression from a given node.
*
* Opposite of {@link getReferenceNode}
*
* An example:
* Having `const a = rtl.within('foo').getByRole('button')`:
* if we call `getDeepestIdentifierNode` with `rtl` node,
* it will return `getByRole` identifier
*/
function getDeepestIdentifierNode(node) {
if (ASTUtils.isIdentifier(node)) return node;
if (isChainExpression(node)) return getDeepestIdentifierNode(node.expression);
if (isMemberExpression(node) && ASTUtils.isIdentifier(node.property)) return node.property;
if (isCallExpression(node)) return getDeepestIdentifierNode(node.callee);
if (ASTUtils.isAwaitExpression(node)) return getDeepestIdentifierNode(node.argument);
return null;
}
/**
* Gets the farthest node in the expression from a given node.
*
* Opposite of {@link getDeepestIdentifierNode}
* An example:
* Having `const a = rtl.within('foo').getByRole('button')`:
* if we call `getReferenceNode` with `getByRole` identifier,
* it will return `rtl` node
*/
function getReferenceNode(node) {
if (node.parent && (isMemberExpression(node.parent) || isCallExpression(node.parent))) return getReferenceNode(node.parent);
return node;
}
function getFunctionName(node) {
return ASTUtils.getFunctionNameWithKind(node).match(/('\w+')/g)?.[0].replace(/'/g, "") ?? "";
}
function getImportModuleName(node) {
if (isImportDeclaration(node) && typeof node.source.value === "string") return node.source.value;
if (isCallExpression(node) && isLiteral(node.arguments[0]) && typeof node.arguments[0].value === "string") return node.arguments[0].value;
}
/**
* Extracts matcher info from MemberExpression node representing an assert.
*/
function getAssertNodeInfo(node) {
const emptyInfo = {
matcher: null,
isNegated: false
};
if (!isCallExpression(node.object) || !ASTUtils.isIdentifier(node.object.callee)) return emptyInfo;
if (node.object.callee.name !== "expect") return emptyInfo;
let matcher = ASTUtils.getPropertyName(node);
const isNegated = matcher === "not";
if (isNegated) matcher = node.parent && isMemberExpression(node.parent) ? ASTUtils.getPropertyName(node.parent) : null;
if (!matcher) return emptyInfo;
return {
matcher,
isNegated
};
}
const matcherNamesHandlePromise = [
"resolves",
"rejects",
"toResolve",
"toReject",
"toBeRejected",
"toBeRejectedWith",
"toBeRejectedWithError",
"toBePending",
"toBeResolved",
"toBeResolvedTo"
];
/**
* Determines whether a node belongs to an async assertion that is fulfilled by:
* - `resolves` or `rejects` properties
* - `toResolve` or `toReject` jest-extended matchers
* - jasmine async matchers
*/
function hasClosestExpectHandlesPromise(node) {
if (isCallExpression(node) && ASTUtils.isIdentifier(node.callee) && node.parent && isMemberExpression(node.parent) && ["expect", "expectAsync"].includes(node.callee.name)) {
const expectMatcher = node.parent.property;
return ASTUtils.isIdentifier(expectMatcher) && matcherNamesHandlePromise.includes(expectMatcher.name);
}
if (!node.parent) return false;
return hasClosestExpectHandlesPromise(node.parent);
}
/**
* Gets the Function node which returns the given Identifier.
*/
function getInnermostReturningFunction(context, node) {
const functionScope = getInnermostFunctionScope(context, node);
if (!functionScope) return;
const returnStatementNode = getFunctionReturnStatementNode(functionScope.block);
if (!returnStatementNode) return;
if (getDeepestIdentifierNode(returnStatementNode)?.name !== node.name) return;
return functionScope.block;
}
function hasImportMatch(importNode, identifierName) {
if (ASTUtils.isIdentifier(importNode)) return importNode.name === identifierName;
return importNode.local.name === identifierName;
}
function getCallExpressionFromNode(node) {
if (isCallExpression(node)) return node;
if (isChainExpression(node)) return getCallExpressionFromNode(node.expression);
if (ASTUtils.isAwaitExpression(node)) return getCallExpressionFromNode(node.argument);
if (isAssignmentExpression(node)) return getCallExpressionFromNode(node.right);
return null;
}
function getStatementCallExpression(statement) {
if (isExpressionStatement(statement)) return getCallExpressionFromNode(statement.expression);
if (isReturnStatement(statement)) return getCallExpressionFromNode(statement.argument);
if (isVariableDeclaration(statement)) for (const declaration of statement.declarations) return getCallExpressionFromNode(declaration.init);
return null;
}
/**
* Determines whether a given function node is considered as empty function or not.
*
* A function is considered empty if its body is empty.
*
* Note that comments don't affect the check.
*
* If node given is not a function, `false` will be returned.
*/
function isEmptyFunction(node) {
if (ASTUtils.isFunction(node) && isBlockStatement(node.body)) return node.body.body.length === 0;
return false;
}
/**
* Finds the import specifier matching a given name for a given import module node.
*/
function findImportSpecifier(specifierName, node) {
if (isImportDeclaration(node)) {
const namedExport = node.specifiers.find((n) => {
return isImportSpecifier(n) && ASTUtils.isIdentifier(n.imported) && [n.imported.name, n.local.name].includes(specifierName);
});
if (namedExport) return namedExport;
return node.specifiers.find((n) => isImportNamespaceSpecifier(n));
} else {
if (!ASTUtils.isVariableDeclarator(node.parent)) return;
const requireNode = node.parent;
if (ASTUtils.isIdentifier(requireNode.id)) return requireNode.id;
if (!isObjectPattern(requireNode.id)) return;
const property = requireNode.id.properties.find((n) => isProperty(n) && ASTUtils.isIdentifier(n.key) && n.key.name === specifierName);
if (!property) return;
return property.key;
}
}
//#endregion
//#region src/utils/is-testing-library-module.ts
const isOfficialTestingLibraryModule = (importSourceName) => [
...OLD_LIBRARY_MODULES,
...LIBRARY_MODULES,
USER_EVENT_MODULE
].includes(importSourceName);
const isCustomTestingLibraryModule = (importSourceName, customModuleSetting) => typeof customModuleSetting === "string" && importSourceName.endsWith(customModuleSetting);
const isTestingLibraryModule = (importSourceName, customModuleSetting) => isOfficialTestingLibraryModule(importSourceName) || isCustomTestingLibraryModule(importSourceName, customModuleSetting);
//#endregion
//#region src/node-utils/accessors.ts
/**
* Checks if the given `node` is a `StringLiteral`.
*
* If a `value` is provided & the `node` is a `StringLiteral`,
* the `value` will be compared to that of the `StringLiteral`.
*/
const isStringLiteral = (node, value) => isLiteral(node) && typeof node.value === "string" && (value === void 0 || node.value === value);
/**
* Checks if the given `node` is a `TemplateLiteral`.
*
* Complex `TemplateLiteral`s are not considered specific, and so will return `false`.
*
* If a `value` is provided & the `node` is a `TemplateLiteral`,
* the `value` will be compared to that of the `TemplateLiteral`.
*/
const isSimpleTemplateLiteral = (node, value) => isTemplateLiteral(node) && node.quasis.length === 1 && (value === void 0 || node.quasis[0]?.value.raw === value);
/**
* Checks if the given `node` is a {@link StringNode}.
*/
const isStringNode = (node, specifics) => isStringLiteral(node, specifics) || isSimpleTemplateLiteral(node, specifics);
/**
* Gets the value of the given `StringNode`.
*
* If the `node` is a `TemplateLiteral`, the `raw` value is used;
* otherwise, `value` is returned instead.
*/
const getStringValue = (node) => isSimpleTemplateLiteral(node) ? node.quasis[0].value.raw : node.value;
/**
* Checks if the given `node` is an `Identifier`.
*
* If a `name` is provided, & the `node` is an `Identifier`,
* the `name` will be compared to that of the `identifier`.
*/
const isIdentifier$1 = (node, name$1) => ASTUtils.isIdentifier(node) && (name$1 === void 0 || node.name === name$1);
/**
* Checks if the given `node` is a "supported accessor".
*
* This means that it's a node can be used to access properties,
* and who's "value" can be statically determined.
*
* `MemberExpression` nodes most commonly contain accessors,
* but it's possible for other nodes to contain them.
*
* If a `value` is provided & the `node` is an `AccessorNode`,
* the `value` will be compared to that of the `AccessorNode`.
*
* Note that `value` here refers to the normalised value.
* The property that holds the value is not always called `name`.
*/
const isSupportedAccessor = (node, value) => isIdentifier$1(node, value) || isStringNode(node, value);
/**
* Gets the value of the given `AccessorNode`,
* account for the different node types.
*/
const getAccessorValue = (accessor) => accessor.type === AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor);
//#endregion
//#region src/utils/resolve-to-testing-library-fn.ts
const describeImportDefAsImport = (def) => {
if (isTSImportEqualsDeclaration(def.parent)) return null;
if (isImportDefaultSpecifier(def.node)) return {
source: def.parent.source.value,
imported: null,
local: def.node.local.name
};
if (!isImportSpecifier(def.node)) return null;
if (def.parent.importKind === "type") return null;
return {
source: def.parent.source.value,
imported: "name" in def.node.imported ? def.node.imported.name : def.node.imported.value,
local: def.node.local.name
};
};
const describeVariableDefAsImport = (def) => {
if (!def.node.init) return null;
const sourceNode = isCallExpression(def.node.init) && isIdentifier$1(def.node.init.callee, "require") ? def.node.init.arguments[0] : ASTUtils.isAwaitExpression(def.node.init) && isImportExpression(def.node.init.argument) ? def.node.init.argument.source : null;
if (!sourceNode || !isStringNode(sourceNode)) return null;
if (!isProperty(def.name.parent)) return null;
if (!isSupportedAccessor(def.name.parent.key)) return null;
return {
source: getStringValue(sourceNode),
imported: getAccessorValue(def.name.parent.key),
local: def.name.name
};
};
const describePossibleImportDef = (def) => {
if (def.type === DefinitionType.Variable) return describeVariableDefAsImport(def);
if (def.type === DefinitionType.ImportBinding) return describeImportDefAsImport(def);
return null;
};
const resolveScope = (scope, identifier) => {
let currentScope = scope;
while (currentScope !== null) {
const ref = currentScope.set.get(identifier);
if (ref && ref.defs.length > 0) {
const def = ref.defs[ref.defs.length - 1];
const importDetails = def ? describePossibleImportDef(def) : null;
if (importDetails?.local === identifier) return importDetails;
return "local";
}
currentScope = currentScope.upper;
}
return null;
};
const joinChains = (a, b) => a && b ? [...a, ...b] : null;
const getNodeChain = (node) => {
if (isSupportedAccessor(node)) return [node];
switch (node.type) {
case AST_NODE_TYPES.MemberExpression: return joinChains(getNodeChain(node.object), getNodeChain(node.property));
case AST_NODE_TYPES.CallExpression: return getNodeChain(node.callee);
}
return null;
};
const resolveToTestingLibraryFn = (node, context) => {
const chain = getNodeChain(node);
if (!chain?.length) return null;
const identifier = chain[0];
if (!identifier) return null;
const maybeImport = resolveScope(context.sourceCode.getScope(identifier), getAccessorValue(identifier));
if (maybeImport === "local" || maybeImport === null) return null;
const customModuleSetting = context.settings["testing-library/utils-module"];
if (isTestingLibraryModule(maybeImport.source, customModuleSetting)) return {
original: maybeImport.imported,
local: maybeImport.local
};
return null;
};
//#endregion
//#region src/utils/index.ts
const combineQueries = (variants, methods) => {
const combinedQueries = [];
variants.forEach((variant) => {
const variantPrefix = variant.replace("By", "");
methods.forEach((method) => {
combinedQueries.push(`${variantPrefix}${method}`);
});
});
return combinedQueries;
};
const getDocsUrl = (ruleName) => `https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/${ruleName}.md`;
const LIBRARY_MODULES = [
"@testing-library/dom",
"@testing-library/angular",
"@testing-library/react",
"@testing-library/preact",
"@testing-library/vue",
"@testing-library/svelte",
"@marko/testing-library"
];
const USER_EVENT_MODULE = "@testing-library/user-event";
const OLD_LIBRARY_MODULES = [
"dom-testing-library",
"vue-testing-library",
"react-testing-library"
];
const SYNC_QUERIES_VARIANTS = [
"getBy",
"getAllBy",
"queryBy",
"queryAllBy"
];
const ASYNC_QUERIES_VARIANTS = ["findBy", "findAllBy"];
const ALL_QUERIES_VARIANTS = [...SYNC_QUERIES_VARIANTS, ...ASYNC_QUERIES_VARIANTS];
const ALL_QUERIES_METHODS = [
"ByLabelText",
"ByPlaceholderText",
"ByText",
"ByAltText",
"ByTitle",
"ByDisplayValue",
"ByRole",
"ByTestId"
];
const SYNC_QUERIES_COMBINATIONS = combineQueries(SYNC_QUERIES_VARIANTS, ALL_QUERIES_METHODS);
const ASYNC_QUERIES_COMBINATIONS = combineQueries(ASYNC_QUERIES_VARIANTS, ALL_QUERIES_METHODS);
const ALL_QUERIES_COMBINATIONS = [...SYNC_QUERIES_COMBINATIONS, ...ASYNC_QUERIES_COMBINATIONS];
const ASYNC_UTILS = ["waitFor", "waitForElementToBeRemoved"];
const DEBUG_UTILS = [
"debug",
"logTestingPlaygroundURL",
"prettyDOM",
"logRoles",
"logDOM",
"prettyFormat"
];
const EVENTS_SIMULATORS = ["fireEvent", "userEvent"];
const TESTING_FRAMEWORK_SETUP_HOOKS = ["beforeEach", "beforeAll"];
const PROPERTIES_RETURNING_NODES = [
"activeElement",
"children",
"childElementCount",
"firstChild",
"firstElementChild",
"fullscreenElement",
"lastChild",
"lastElementChild",
"nextElementSibling",
"nextSibling",
"parentElement",
"parentNode",
"pointerLockElement",
"previousElementSibling",
"previousSibling",
"rootNode",
"scripts"
];
const METHODS_RETURNING_NODES = [
"closest",
"getElementById",
"getElementsByClassName",
"getElementsByName",
"getElementsByTagName",
"getElementsByTagNameNS",
"querySelector",
"querySelectorAll"
];
const EVENT_HANDLER_METHODS = [
"click",
"select",
"submit"
];
const ALL_RETURNING_NODES = [...PROPERTIES_RETURNING_NODES, ...METHODS_RETURNING_NODES];
const PRESENCE_MATCHERS = [
"toBeOnTheScreen",
"toBeInTheDocument",
"toBeTruthy",
"toBeDefined"
];
const ABSENCE_MATCHERS = ["toBeNull", "toBeFalsy"];
//#endregion
//#region src/create-testing-library-rule/detect-testing-library-utils.ts
const SETTING_OPTION_OFF = "off";
const REACT_DOM_TEST_UTILS_PACKAGE = "react-dom/test-utils";
const FIRE_EVENT_NAME$1 = "fireEvent";
const CREATE_EVENT_NAME = "createEvent";
const USER_EVENT_NAME$2 = "userEvent";
const RENDER_NAME = "render";
/**
* Enhances a given rule `create` with helpers to detect Testing Library utils.
*/
function detectTestingLibraryUtils(ruleCreate, { skipRuleReportingCheck = false } = {}) {
return (context, optionsWithDefault) => {
const importedTestingLibraryNodes = [];
let importedCustomModuleNode = null;
let importedUserEventLibraryNode = null;
let importedReactDomTestUtilsNode = null;
const customModuleSetting = context.settings["testing-library/utils-module"];
const customRendersSetting = context.settings["testing-library/custom-renders"];
const customQueriesSetting = context.settings["testing-library/custom-queries"];
/**
* Small method to extract common checks to determine whether a node is
* related to Testing Library or not.
*
* To determine whether a node is a valid Testing Library util, there are
* two conditions to match:
* - it's named in a particular way (decided by given callback)
* - it's imported from valid Testing Library module (depends on aggressive
* reporting)
*/
function isPotentialTestingLibraryFunction(node, isPotentialFunctionCallback) {
if (!node) return false;
const referenceNodeIdentifier = getPropertyIdentifierNode(getReferenceNode(node));
if (!referenceNodeIdentifier) return false;
const importedUtilSpecifier = getTestingLibraryImportedUtilSpecifier(referenceNodeIdentifier);
const originalNodeName = isImportSpecifier(importedUtilSpecifier) && ASTUtils.isIdentifier(importedUtilSpecifier.imported) && importedUtilSpecifier.local.name !== importedUtilSpecifier.imported.name ? importedUtilSpecifier.imported.name : void 0;
if (!isPotentialFunctionCallback(node.name, originalNodeName)) return false;
if (isAggressiveModuleReportingEnabled()) return true;
return isNodeComingFromTestingLibrary(referenceNodeIdentifier);
}
/**
* Determines whether aggressive module reporting is enabled or not.
*
* This aggressive reporting mechanism is considered as enabled when custom
* module is not set, so we need to assume everything matching Testing
* Library utils is related to Testing Library no matter from where module
* they are coming from. Otherwise, this aggressive reporting mechanism is
* opted-out in favour to report only those utils coming from Testing
* Library package or custom module set up on settings.
*/
const isAggressiveModuleReportingEnabled = () => !customModuleSetting;
/**
* Determines whether aggressive render reporting is enabled or not.
*
* This aggressive reporting mechanism is considered as enabled when custom
* renders are not set, so we need to assume every method containing
* "render" is a valid Testing Library `render`. Otherwise, this aggressive
* reporting mechanism is opted-out in favour to report only `render` or
* names set up on custom renders setting.
*/
const isAggressiveRenderReportingEnabled = () => {
const isSwitchedOff = customRendersSetting === SETTING_OPTION_OFF;
const hasCustomOptions = Array.isArray(customRendersSetting) && customRendersSetting.length > 0;
return !isSwitchedOff && !hasCustomOptions;
};
/**
* Determines whether Aggressive Reporting for queries is enabled or not.
*
* This Aggressive Reporting mechanism is considered as enabled when custom-queries setting is not set,
* so the plugin needs to report both built-in and custom queries.
* Otherwise, this Aggressive Reporting mechanism is opted-out in favour of reporting only built-in queries + those
* indicated in custom-queries setting.
*/
const isAggressiveQueryReportingEnabled = () => {
const isSwitchedOff = customQueriesSetting === SETTING_OPTION_OFF;
const hasCustomOptions = Array.isArray(customQueriesSetting) && customQueriesSetting.length > 0;
return !isSwitchedOff && !hasCustomOptions;
};
const getCustomModule = () => {
if (!isAggressiveModuleReportingEnabled() && customModuleSetting !== SETTING_OPTION_OFF) return customModuleSetting;
};
const getCustomRenders = () => {
if (!isAggressiveRenderReportingEnabled() && customRendersSetting !== SETTING_OPTION_OFF) return customRendersSetting;
return [];
};
const getCustomQueries = () => {
if (!isAggressiveQueryReportingEnabled() && customQueriesSetting !== SETTING_OPTION_OFF) return customQueriesSetting;
return [];
};
const getTestingLibraryImportNode = () => {
return importedTestingLibraryNodes[0] ?? null;
};
const getAllTestingLibraryImportNodes = () => {
return importedTestingLibraryNodes;
};
const getCustomModuleImportNode = () => {
return importedCustomModuleNode;
};
const getTestingLibraryImportName = () => {
return getImportModuleName(importedTestingLibraryNodes[0]);
};
const getCustomModuleImportName = () => {
return getImportModuleName(importedCustomModuleNode);
};
/**
* Determines whether Testing Library utils are imported or not for
* current file being analyzed.
*
* By default, it is ALWAYS considered as imported. This is what we call
* "aggressive reporting" so we don't miss TL utils reexported from
* custom modules.
*
* However, there is a setting to customize the module where TL utils can
* be imported from: "testing-library/utils-module". If this setting is enabled,
* then this method will return `true` ONLY IF a testing-library package
* or custom module are imported.
*/
const isTestingLibraryImported = (isStrict = false) => {
const isSomeModuleImported = importedTestingLibraryNodes.length !== 0 || !!importedCustomModuleNode;
return !isStrict && isAggressiveModuleReportingEnabled() || isSomeModuleImported;
};
/**
* Determines whether a given node is a reportable query,
* either a built-in or a custom one.
*
* Depending on Aggressive Query Reporting setting, custom queries will be
* reportable or not.
*/
const isQuery = (node) => {
if (!/^(get|query|find)(All)?By.+$/.test(node.name)) return false;
if (isAggressiveQueryReportingEnabled()) return true;
const customQueries = getCustomQueries();
const isBuiltInQuery$1 = ALL_QUERIES_COMBINATIONS.includes(node.name);
const isReportableCustomQuery = customQueries.some((pattern) => new RegExp(pattern).test(node.name));
return isBuiltInQuery$1 || isReportableCustomQuery;
};
/**
* Determines whether a given node is `get*` query variant or not.
*/
const isGetQueryVariant = (node) => {
return isQuery(node) && node.name.startsWith("get");
};
/**
* Determines whether a given node is `query*` query variant or not.
*/
const isQueryQueryVariant = (node) => {
return isQuery(node) && node.name.startsWith("query");
};
/**
* Determines whether a given node is `find*` query variant or not.
*/
const isFindQueryVariant = (node) => {
return isQuery(node) && node.name.startsWith("find");
};
/**
* Determines whether a given node is sync query or not.
*/
const isSyncQuery = (node) => {
return isGetQueryVariant(node) || isQueryQueryVariant(node);
};
/**
* Determines whether a given node is async query or not.
*/
const isAsyncQuery = (node) => {
return isFindQueryVariant(node);
};
const isCustomQuery = (node) => {
return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name);
};
const isBuiltInQuery = (node) => {
return isQuery(node) && ALL_QUERIES_COMBINATIONS.includes(node.name);
};
/**
* Determines whether a given node is a valid async util or not.
*
* A node will be interpreted as a valid async util based on two conditions:
* the name matches with some Testing Library async util, and the node is
* coming from Testing Library module.
*
* The latter depends on Aggressive module reporting:
* if enabled, then it doesn't matter from where the given node was imported
* from as it will be considered part of Testing Library.
* Otherwise, it means `custom-module` has been set up, so only those nodes
* coming from Testing Library will be considered as valid.
*/
const isAsyncUtil = (node, validNames = ASYNC_UTILS) => {
return isPotentialTestingLibraryFunction(node, (identifierNodeName, originalNodeName) => {
return validNames.includes(identifierNodeName) || !!originalNodeName && validNames.includes(originalNodeName);
});
};
/**
* Determines whether a given node is fireEvent util itself or not.
*
* Not to be confused with {@link isFireEventMethod}
*/
const isFireEventUtil = (node) => {
return isPotentialTestingLibraryFunction(node, (identifierNodeName, originalNodeName) => {
return [identifierNodeName, originalNodeName].includes("fireEvent");
});
};
/**
* Determines whether a given node is userEvent util itself or not.
*
* Not to be confused with {@link isUserEventMethod}
*/
const isUserEventUtil = (node) => {
const userEvent = findImportedUserEventSpecifier();
let userEventName;
if (userEvent) userEventName = userEvent.name;
else if (isAggressiveModuleReportingEnabled()) userEventName = USER_EVENT_NAME$2;
if (!userEventName) return false;
return node.name === userEventName;
};
/**
* Determines whether a given node is fireEvent method or not
*/
const isFireEventMethod = (node) => {
const fireEventUtil = findImportedTestingLibraryUtilSpecifier(FIRE_EVENT_NAME$1);
let fireEventUtilName;
if (fireEventUtil) fireEventUtilName = ASTUtils.isIdentifier(fireEventUtil) ? fireEventUtil.name : fireEventUtil.local.name;
else if (isAggressiveModuleReportingEnabled()) fireEventUtilName = FIRE_EVENT_NAME$1;
if (!fireEventUtilName) return false;
const parentMemberExpression = node.parent && isMemberExpression(node.parent) ? node.parent : void 0;
const parentCallExpression = node.parent && isCallExpression(node.parent) ? node.parent : void 0;
if (!parentMemberExpression && !parentCallExpression) return false;
if (parentCallExpression) return [fireEventUtilName, FIRE_EVENT_NAME$1].includes(node.name);
const definedParentMemberExpression = parentMemberExpression;
const regularCall = ASTUtils.isIdentifier(definedParentMemberExpression.object) && isCallExpression(definedParentMemberExpression.parent) && definedParentMemberExpression.object.name === fireEventUtilName && node.name !== FIRE_EVENT_NAME$1 && node.name !== fireEventUtilName;
const wildcardCall = isMemberExpression(definedParentMemberExpression.object) && ASTUtils.isIdentifier(definedParentMemberExpression.object.object) && definedParentMemberExpression.object.object.name === fireEventUtilName && ASTUtils.isIdentifier(definedParentMemberExpression.object.property) && definedParentMemberExpression.object.property.name === FIRE_EVENT_NAME$1 && node.name !== FIRE_EVENT_NAME$1 && node.name !== fireEventUtilName;
const wildcardCallWithCallExpression = ASTUtils.isIdentifier(definedParentMemberExpression.object) && definedParentMemberExpression.object.name === fireEventUtilName && ASTUtils.isIdentifier(definedParentMemberExpression.property) && definedParentMemberExpression.property.name === FIRE_EVENT_NAME$1 && !isMemberExpression(definedParentMemberExpression.parent) && node.name === FIRE_EVENT_NAME$1 && node.name !== fireEventUtilName;
return regularCall || wildcardCall || wildcardCallWithCallExpression;
};
const isUserEventMethod = (node, userEventSetupVars) => {
const userEvent = findImportedUserEventSpecifier();
let userEventName;
if (userEvent) userEventName = userEvent.name;
else if (isAggressiveModuleReportingEnabled()) userEventName = USER_EVENT_NAME$2;
const parentMemberExpression = node.parent && isMemberExpression(node.parent) ? node.parent : void 0;
if (!parentMemberExpression) return false;
if (userEventName && [userEventName, USER_EVENT_NAME$2].includes(node.name) || ASTUtils.isIdentifier(parentMemberExpression.object) && parentMemberExpression.object.name === node.name) return false;
if (userEventName && ASTUtils.isIdentifier(parentMemberExpression.object) && parentMemberExpression.object.name === userEventName) return true;
if (userEventSetupVars && ASTUtils.isIdentifier(parentMemberExpression.object) && userEventSetupVars.has(parentMemberExpression.object.name)) return true;
return false;
};
/**
* Determines whether a given node is a valid render util or not.
*
* A node will be interpreted as a valid render based on two conditions:
* the name matches with a valid "render" option, and the node is coming
* from Testing Library module. This depends on:
*
* - Aggressive render reporting: if enabled, then every node name
* containing "render" will be assumed as Testing Library render util.
* Otherwise, it means `custom-modules` has been set up, so only those nodes
* named as "render" or some of the `custom-modules` options will be
* considered as Testing Library render util.
* - Aggressive module reporting: if enabled, then it doesn't matter from
* where the given node was imported from as it will be considered part of
* Testing Library. Otherwise, it means `custom-module` has been set up, so
* only those nodes coming from Testing Library will be considered as valid.
*/
const isRenderUtil = (node) => isPotentialTestingLibraryFunction(node, (identifierNodeName, originalNodeName) => {
if (isAggressiveRenderReportingEnabled()) return identifierNodeName.toLowerCase().includes(RENDER_NAME);
return [RENDER_NAME, ...getCustomRenders()].some((validRenderName) => validRenderName === identifierNodeName || Boolean(originalNodeName) && validRenderName === originalNodeName);
});
const isCreateEventUtil = (node) => {
const isCreateEventCallback = (identifierNodeName, originalNodeName) => [identifierNodeName, originalNodeName].includes(CREATE_EVENT_NAME);
if (isCallExpression(node) && isMemberExpression(node.callee) && ASTUtils.isIdentifier(node.callee.object)) return isPotentialTestingLibraryFunction(node.callee.object, isCreateEventCallback);
if (isCallExpression(node) && isMemberExpression(node.callee) && isMemberExpression(node.callee.object) && ASTUtils.isIdentifier(node.callee.object.property)) return isPotentialTestingLibraryFunction(node.callee.object.property, isCreateEventCallback);
return isPotentialTestingLibraryFunction(getDeepestIdentifierNode(node), isCreateEventCallback);
};
const isRenderVariableDeclarator = (node) => {
if (!node.init) return false;
const initIdentifierNode = getDeepestIdentifierNode(node.init);
if (!initIdentifierNode) return false;
return isRenderUtil(initIdentifierNode);
};
const isDebugUtil = (identifierNode, validNames = DEBUG_UTILS) => {
return !(isMemberExpression(identifierNode.parent) && ASTUtils.isIdentifier(identifierNode.parent.object) && identifierNode.parent.object.name === "console") && isPotentialTestingLibraryFunction(identifierNode, (identifierNodeName, originalNodeName) => {
return validNames.includes(identifierNodeName) || !!originalNodeName && validNames.includes(originalNodeName);
});
};
/**
* Determines whether a given node is some reportable `act` util.
*
* An `act` is reportable if some of these conditions is met:
* - it's related to Testing Library module (this depends on Aggressive Reporting)
* - it's related to React DOM Test Utils
*/
const isActUtil = (node) => {
const isTestingLibraryAct = isPotentialTestingLibraryFunction(node, (identifierNodeName, originalNodeName) => {
return [identifierNodeName, originalNodeName].filter(Boolean).includes("act");
});
const isReactDomTestUtilsAct = (() => {
if (!importedReactDomTestUtilsNode) return false;
const referenceNodeIdentifier = getPropertyIdentifierNode(getReferenceNode(node));
if (!referenceNodeIdentifier) return false;
const importedUtilSpecifier = findImportSpecifier(node.name, importedReactDomTestUtilsNode);
if (!importedUtilSpecifier) return false;
const importDeclaration = (() => {
if (isImportDeclaration(importedUtilSpecifier.parent)) return importedUtilSpecifier.parent;
const variableDeclarator = findClosestVariableDeclaratorNode(importedUtilSpecifier);
if (isCallExpression(variableDeclarator?.init)) return variableDeclarator?.init;
})();
if (!importDeclaration) return false;
const importDeclarationName = getImportModuleName(importDeclaration);
if (!importDeclarationName) return false;
if (importDeclarationName !== REACT_DOM_TEST_UTILS_PACKAGE) return false;
return hasImportMatch(importedUtilSpecifier, referenceNodeIdentifier.name);
})();
return isTestingLibraryAct || isReactDomTestUtilsAct;
};
const isTestingLibraryUtil = (node) => {
return isAsyncUtil(node) || isQuery(node) || isRenderUtil(node) || isFireEventMethod(node) || isUserEventMethod(node) || isActUtil(node) || isCreateEventUtil(node);
};
/**
* Determines whether a given MemberExpression node is a presence assert
*
* Presence asserts could have shape of:
* - expect(element).toBeInTheDocument()
* - expect(element).not.toBeNull()
*/
const isPresenceAssert = (node) => {
const { matcher, isNegated } = getAssertNodeInfo(node);
if (!matcher) return false;
return isNegated ? ABSENCE_MATCHERS.some((absenceMather) => absenceMather === matcher) : PRESENCE_MATCHERS.some((presenceMather) => presenceMather === matcher);
};
/**
* Determines whether a given MemberExpression node is an absence assert
*
* Absence asserts could have shape of:
* - expect(element).toBeNull()
* - expect(element).not.toBeInTheDocument()
*/
const isAbsenceAssert = (node) => {
const { matcher, isNegated } = getAssertNodeInfo(node);
if (!matcher) return false;
return isNegated ? PRESENCE_MATCHERS.some((presenceMather) => pre