babel-plugin-react-data-testid-generator
Version:
Enhanced babel plugin for automatic React data-testid generation with unique IDs and class component support
143 lines (142 loc) • 5.69 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const t = __importStar(require("@babel/types"));
const DEFAULT_DATA_TESTID = "data-testid";
const componentCounters = new Map();
function createDataAttribute(name, attributeName) {
return t.jsxAttribute(t.jsxIdentifier(attributeName), t.stringLiteral(name));
}
function hasDataAttribute(node, attributeName) {
return node.attributes.some((attribute) => t.isJSXAttribute(attribute) &&
t.isJSXIdentifier(attribute.name, { name: attributeName }));
}
function getElementName(node) {
if (t.isJSXIdentifier(node.name)) {
return node.name.name;
}
else if (t.isJSXMemberExpression(node.name)) {
return t.isJSXIdentifier(node.name.property) ? node.name.property.name : "";
}
return "";
}
function getUniqueElementId(componentName, elementName) {
if (!componentCounters.has(componentName)) {
componentCounters.set(componentName, new Map());
}
const elementCounters = componentCounters.get(componentName);
const currentCount = elementCounters.get(elementName) || 0;
const nextCount = currentCount + 1;
elementCounters.set(elementName, nextCount);
if (nextCount === 1) {
return elementName;
}
else {
return `${elementName}${nextCount}`;
}
}
function addDataTestIds(jsxElement, componentName, attributes) {
const openingElement = jsxElement.get("openingElement");
const elementName = getElementName(openingElement.node);
if (!elementName)
return;
const uniqueElementId = getUniqueElementId(componentName, elementName);
const testId = `${componentName}.${uniqueElementId}`;
for (const attribute of attributes) {
if (!hasDataAttribute(openingElement.node, attribute)) {
const dataAttribute = createDataAttribute(testId, attribute);
openingElement.node.attributes.unshift(dataAttribute);
}
}
}
function getComponentName(path) {
const node = path.node;
if (t.isFunctionDeclaration(node) && node.id && t.isIdentifier(node.id)) {
return node.id.name;
}
if (path.isArrowFunctionExpression() || path.isFunctionExpression()) {
const parent = path.parentPath;
if (parent &&
parent.isVariableDeclarator() &&
t.isIdentifier(parent.node.id)) {
return parent.node.id.name;
}
}
return null;
}
function processJSXElements(path, componentName, attributes) {
componentCounters.set(componentName, new Map());
path.traverse({
JSXElement(jsxPath) {
addDataTestIds(jsxPath, componentName, attributes);
},
});
}
function default_1() {
return {
name: "babel-plugin-react-data-testid-generator",
visitor: {
FunctionDeclaration(path, state) {
const componentName = getComponentName(path);
if (!componentName)
return;
const attributes = state.opts?.attributes ?? [DEFAULT_DATA_TESTID];
processJSXElements(path, componentName, attributes);
},
FunctionExpression(path, state) {
const componentName = getComponentName(path);
if (!componentName)
return;
const attributes = state.opts?.attributes ?? [DEFAULT_DATA_TESTID];
processJSXElements(path, componentName, attributes);
},
ArrowFunctionExpression(path, state) {
const componentName = getComponentName(path);
if (!componentName)
return;
const attributes = state.opts?.attributes ?? [DEFAULT_DATA_TESTID];
processJSXElements(path, componentName, attributes);
},
ClassMethod(path, state) {
const node = path.node;
if (t.isIdentifier(node.key) &&
node.key.name === "render" &&
path.parentPath &&
path.parentPath.isClassBody()) {
let className = "Component";
const classPath = path.parentPath.parentPath;
if (classPath &&
classPath.isClassDeclaration() &&
classPath.node.id) {
className = classPath.node.id.name;
}
const attributes = state.opts?.attributes ?? [DEFAULT_DATA_TESTID];
processJSXElements(path, className, attributes);
}
},
},
};
}
exports.default = default_1;