@yasanchezz/eslint-plugin-vue-data-testid
Version:
An `eslint` plugin for checking accessibility rules from within `.vue` files. Add `data-testid` to use better behavior testing
75 lines (74 loc) • 3.67 kB
JavaScript
import { getAttributeNames, getAttributeValue, calculateAttributeNames } from "../utils/index.js";
import * as path from 'node:path';
export default function createRule({ attributeName = 'data-testid', buildDataTestid, ignoreNode = () => false, }) {
return {
meta: {
docs: { description: 'should add data-testid attribute' },
messages: { add: 'add data-testid="{{attribute}}".' },
type: 'suggestion',
fixable: 'code',
hasSuggestions: true,
schema: [], // no options
},
create(context) {
const filename = path.basename(context.filename);
const extname = path.extname(context.filename);
if (extname !== '.vue') {
return {};
}
return context.sourceCode.parserServices.defineTemplateBodyVisitor({
VElement(node) {
const isRoot = node.parent.parent?.type === 'VDocumentFragment';
const classNames = Array.from(getAttributeValue(node, 'class'))
.flatMap(className => className.split(' '));
const option = {
nodeName: node.rawName,
filepath: context.filename,
filename,
isRoot,
classNames,
};
const userAttributeName = typeof attributeName === 'string'
? attributeName
: attributeName(option);
const searchedAttributeNames = calculateAttributeNames(userAttributeName);
const attributeNames = getAttributeNames(node);
const hasDataTestIdAttribute = attributeNames.intersection(searchedAttributeNames).size > 0;
const [startRange] = node.startTag.range;
const nameLength = node.rawName.length;
if (hasDataTestIdAttribute) {
return {};
}
const classNameOptions = classNames.map(className => ({
...option,
className,
}));
const options = classNameOptions.length ? classNameOptions : [option];
const filteredOptions = options.filter(option => !ignoreNode(option));
const dataTestids = filteredOptions
.map(buildDataTestid)
.filter(dataTestid => dataTestid != null)
.filter(dataTestid => dataTestid.trim());
const firstDataTestid = dataTestids.at(0);
if (!firstDataTestid) {
return {};
}
function fix(dataTestid, fixer) {
const message = ` ${userAttributeName}="${dataTestid}"`;
return fixer.insertTextAfterRange([startRange, startRange + nameLength + 1], message);
}
context.report({
node: node,
message: `should add ${userAttributeName} for further behavior testing`,
fix: fix.bind(undefined, firstDataTestid),
suggest: dataTestids.map(dataTestid => ({
data: { attribute: dataTestid },
messageId: 'add',
fix: fix.bind(undefined, dataTestid)
}))
});
}
});
}
};
}