UNPKG

@masknet/eslint-plugin

Version:
93 lines 3.42 kB
import { createRule } from "../../rule.js"; const DEFAULT_ELEMENT_LIST = ['a', 'button', 'input', 'textarea']; const DEFAULT_ATTRIBUTE_LIST = ['editable', 'onInput', 'onChange', 'onClick', 'onKeyDown', 'onKeyUp', 'onSubmit']; const DEFAULT_IGNORE_ATTRIBUTE_LIST = ['disabled', 'readonly']; export default createRule({ name: 'jsx/prefer-test-id', meta: { type: 'suggestion', docs: { description: 'Enforces `data-test-id` attribute is present on interactive DOM elements to help with UI testing', }, schema: [ { type: 'object', properties: { 'id': { type: 'string' }, 'elements': { type: 'array', items: { type: 'string' }, uniqueItems: true }, 'attributes': { type: 'array', items: { type: 'string' }, uniqueItems: true }, 'ignore-attributes': { type: 'array', items: { type: 'string' }, uniqueItems: true }, }, additionalProperties: false, }, ], messages: { element: '<{{element}}> elements must have a `{{id}}` attribute', attribute: '<{{element}}> with an `{{name}}` handler must have a `{{id}}` attribute', }, }, defaultOptions: [ { 'id': 'data-test-id', 'elements': DEFAULT_ELEMENT_LIST, 'attributes': DEFAULT_ATTRIBUTE_LIST, 'ignore-attributes': DEFAULT_IGNORE_ATTRIBUTE_LIST, }, ], create(context, [options]) { const source = context.sourceCode; return { JSXOpeningElement(node) { const element = source.getText(node.name); const attributes = new Map(node.attributes .filter((node) => node.type === 'JSXAttribute') .map(({ name, value }) => [source.getText(name), value])); if (isValid(attributes.get(options.id))) return; if (shouldIgnore(attributes, options['ignore-attributes'])) return; if (options.elements.includes(element)) { context.report({ node, messageId: 'element', data: { element, id: options.id }, }); } else { const name = options.attributes.find((name) => attributes.has(name)); if (!name) return; context.report({ node, messageId: 'attribute', data: { element, name, id: options.id }, }); } }, }; }, }); function shouldIgnore(attributes, ignore) { return ignore.some((name) => { const node = attributes.get(name); if (node === null) return true; if (node?.type === 'JSXExpressionContainer') return false; return isValid(node); }); } function isValid(node) { if (!node) return false; switch (node.type) { case 'Literal': { return Boolean(node.value); } case 'JSXExpressionContainer': { return true; } } return false; } //# sourceMappingURL=prefer-test-id.js.map