From 936e871faa766b88d7a502e6a0ffdf64c464f586 Mon Sep 17 00:00:00 2001
From: Pascal Corpet
Date: Sun, 27 Mar 2022 21:11:33 +0200
Subject: [PATCH] [New] Introduce a plugin-wide setting for custom components.
For #174
---
README.md | 16 ++
__tests__/__util__/parserOptionsMapper.js | 2 +
.../__util__/ruleOptionsMapperFactory.js | 6 +-
__tests__/src/rules/accessible-emoji-test.js | 6 +
.../src/rules/anchor-has-content-test.js | 10 ++
__tests__/src/rules/anchor-is-valid-test.js | 16 +-
...aria-activedescendant-has-tabindex-test.js | 9 ++
__tests__/src/rules/aria-role-test.js | 20 +++
.../rules/aria-unsupported-elements-test.js | 6 +-
.../src/rules/autocomplete-valid-test.js | 11 ++
.../click-events-have-key-events-test.js | 2 +
.../control-has-associated-label-test.js | 2 +
.../src/rules/heading-has-content-test.js | 14 ++
__tests__/src/rules/html-has-lang-test.js | 2 +
__tests__/src/rules/iframe-has-title-test.js | 10 ++
__tests__/src/rules/img-redundant-alt-test.js | 11 ++
.../rules/interactive-supports-focus-test.js | 17 ++
.../label-has-associated-control-test.js | 19 +++
__tests__/src/rules/lang-test.js | 12 +-
__tests__/src/rules/media-has-caption-test.js | 72 +++++++++
__tests__/src/rules/no-autofocus-test.js | 17 ++
.../src/rules/no-distracting-elements-test.js | 9 ++
...ive-element-to-noninteractive-role-test.js | 14 ++
...oninteractive-element-interactions-test.js | 11 ++
...active-element-to-interactive-role-test.js | 14 ++
.../rules/no-noninteractive-tabindex-test.js | 12 ++
__tests__/src/rules/no-onchange-test.js | 12 ++
.../src/rules/no-redundant-roles-test.js | 10 ++
.../no-static-element-interactions-test.js | 12 ++
.../role-has-required-aria-props-test.js | 11 ++
__tests__/src/rules/scope-test.js | 11 ++
__tests__/src/util/getElementType-test.js | 84 ++++++++++
.../src/util/mayContainChildComponent-test.js | 23 +++
flow/eslint.js | 8 +
package.json | 1 +
src/rules/accessible-emoji.js | 54 ++++---
src/rules/anchor-has-content.js | 47 +++---
src/rules/anchor-is-valid.js | 150 +++++++++---------
.../aria-activedescendant-has-tabindex.js | 68 ++++----
src/rules/aria-role.js | 4 +-
src/rules/aria-unsupported-elements.js | 58 +++----
src/rules/autocomplete-valid.js | 72 +++++----
src/rules/click-events-have-key-events.js | 70 ++++----
src/rules/control-has-associated-label.js | 4 +-
src/rules/heading-has-content.js | 53 ++++---
src/rules/html-has-lang.js | 38 +++--
src/rules/iframe-has-title.js | 38 +++--
src/rules/img-redundant-alt.js | 68 ++++----
src/rules/interactive-supports-focus.js | 103 ++++++------
src/rules/label-has-associated-control.js | 5 +-
src/rules/label-has-for.js | 48 +++---
src/rules/lang.js | 68 ++++----
src/rules/media-has-caption.js | 80 +++++-----
src/rules/no-autofocus.js | 50 +++---
src/rules/no-distracting-elements.js | 35 ++--
...eractive-element-to-noninteractive-role.js | 3 +-
.../no-noninteractive-element-interactions.js | 3 +-
...interactive-element-to-interactive-role.js | 3 +-
src/rules/no-noninteractive-tabindex.js | 3 +-
src/rules/no-onchange.js | 44 ++---
src/rules/no-redundant-roles.js | 3 +-
src/rules/no-static-element-interactions.js | 3 +-
src/rules/role-has-required-aria-props.js | 93 +++++------
src/rules/scope.js | 56 ++++---
src/util/getElementType.js | 42 +++++
src/util/mayContainChildComponent.js | 5 +-
66 files changed, 1258 insertions(+), 625 deletions(-)
create mode 100644 __tests__/src/util/getElementType-test.js
create mode 100644 src/util/getElementType.js
diff --git a/README.md b/README.md
index c75e78dad..ac15c588b 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,22 @@ Add `plugin:jsx-a11y/recommended` or `plugin:jsx-a11y/strict` in `extends`:
}
```
+To enable your custom components to be checked as DOM elements, you can set global settings in your
+configuration file by mapping each DOM element type to your component names.
+
+```json
+{
+ "settins": {
+ "jsx-a11y": {
+ "components": {
+ "button": ["MyButton", "CustomButton", "RoundButton"],
+ "input": ["CityInput"]
+ }
+ }
+ }
+}
+```
+
## Supported Rules
diff --git a/__tests__/__util__/parserOptionsMapper.js b/__tests__/__util__/parserOptionsMapper.js
index c3d2f77bd..74db94a78 100644
--- a/__tests__/__util__/parserOptionsMapper.js
+++ b/__tests__/__util__/parserOptionsMapper.js
@@ -11,6 +11,7 @@ export default function parserOptionsMapper({
errors,
options = [],
parserOptions = {},
+ settings,
}) {
return {
code,
@@ -20,5 +21,6 @@ export default function parserOptionsMapper({
...defaultParserOptions,
...parserOptions,
},
+ settings,
};
}
diff --git a/__tests__/__util__/ruleOptionsMapperFactory.js b/__tests__/__util__/ruleOptionsMapperFactory.js
index 7175bf91b..1e1b628b0 100644
--- a/__tests__/__util__/ruleOptionsMapperFactory.js
+++ b/__tests__/__util__/ruleOptionsMapperFactory.js
@@ -6,7 +6,8 @@ type ESLintTestRunnerTestCase = {
code: string,
errors: ?Array<{ message: string, type: string }>,
options: ?Array,
- parserOptions: ?Array
+ parserOptions: ?Array,
+ settings?: {[string]: mixed},
};
type RuleOptionsMapperFactoryType = (
@@ -15,7 +16,7 @@ type RuleOptionsMapperFactoryType = (
export default function ruleOptionsMapperFactory(ruleOptions: Array = []): RuleOptionsMapperFactoryType {
// eslint-disable-next-line
- return ({ code, errors, options, parserOptions }: ESLintTestRunnerTestCase): ESLintTestRunnerTestCase => {
+ return ({ code, errors, options, parserOptions, settings }: ESLintTestRunnerTestCase): ESLintTestRunnerTestCase => {
return {
code,
errors,
@@ -25,6 +26,7 @@ export default function ruleOptionsMapperFactory(ruleOptions: Array = [])
...item,
}], [{}]),
parserOptions,
+ settings,
};
};
}
diff --git a/__tests__/src/rules/accessible-emoji-test.js b/__tests__/src/rules/accessible-emoji-test.js
index bcdb42964..891035a11 100644
--- a/__tests__/src/rules/accessible-emoji-test.js
+++ b/__tests__/src/rules/accessible-emoji-test.js
@@ -37,6 +37,11 @@ ruleTester.run('accessible-emoji', rule, {
{ code: '🐼' },
{ code: '🐼' },
{ code: '🐼
' },
+ { code: '🐼' },
+ {
+ code: '🐼',
+ settings: { 'jsx-a11y': { components: { input: ['CustomInput'] } } },
+ },
].map(parserOptionsMapper),
invalid: [
{ code: '🐼', errors: [expectedError] },
@@ -46,5 +51,6 @@ ruleTester.run('accessible-emoji', rule, {
{ code: '🐼', errors: [expectedError] },
{ code: '🐼', errors: [expectedError] },
{ code: '🐼', errors: [expectedError] },
+ { code: '🐼', errors: [expectedError] },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/anchor-has-content-test.js b/__tests__/src/rules/anchor-has-content-test.js
index 77559da31..b59d0d834 100644
--- a/__tests__/src/rules/anchor-has-content-test.js
+++ b/__tests__/src/rules/anchor-has-content-test.js
@@ -31,10 +31,20 @@ ruleTester.run('anchor-has-content', rule, {
{ code: '{foo.bar}' },
{ code: '' },
{ code: '' },
+ { code: '' },
+ {
+ code: 'foo',
+ settings: { 'jsx-a11y': { components: { a: ['Link'] } } },
+ },
].map(parserOptionsMapper),
invalid: [
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '{undefined}', errors: [expectedError] },
+ {
+ code: '',
+ errors: [expectedError],
+ settings: { 'jsx-a11y': { components: { a: ['Link'] } } },
+ },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/anchor-is-valid-test.js b/__tests__/src/rules/anchor-is-valid-test.js
index fd7f44940..b9a21f386 100644
--- a/__tests__/src/rules/anchor-is-valid-test.js
+++ b/__tests__/src/rules/anchor-is-valid-test.js
@@ -78,10 +78,18 @@ const componentsAndSpecialLinkAndNoHrefAspect = [{
aspects: ['noHref'],
}];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ a: ['Anchor', 'Link'],
+ },
+ },
+};
+
ruleTester.run('anchor-is-valid', rule, {
valid: [
// DEFAULT ELEMENT 'a' TESTS
- { code: ';' },
+ { code: '' },
{ code: '' },
{ code: '' },
{ code: '' },
@@ -119,6 +127,7 @@ ruleTester.run('anchor-is-valid', rule, {
{ code: '', options: components },
{ code: '', options: components },
{ code: '', options: components },
+ { code: '', settings: componentsSettings },
// CUSTOM PROP TESTS
{ code: '', options: specialLink },
@@ -332,6 +341,11 @@ ruleTester.run('anchor-is-valid', rule, {
errors: [preferButtonexpectedError],
options: components,
},
+ {
+ code: ' void 0} />',
+ errors: [preferButtonexpectedError],
+ settings: componentsSettings,
+ },
// CUSTOM PROP TESTS
// NO HREF
diff --git a/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js b/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js
index 917898e80..d16fe0329 100644
--- a/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js
+++ b/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js
@@ -36,6 +36,10 @@ ruleTester.run('aria-activedescendant-has-tabindex', rule, {
{
code: ';',
},
+ {
+ code: ';',
+ settings: { 'jsx-a11y': { components: { div: ['CustomComponent'] } } },
+ },
{
code: ';',
},
@@ -81,5 +85,10 @@ ruleTester.run('aria-activedescendant-has-tabindex', rule, {
code: ';',
errors: [expectedError],
},
+ {
+ code: ';',
+ errors: [expectedError],
+ settings: { 'jsx-a11y': { components: { div: ['CustomComponent'] } } },
+ },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/aria-role-test.js b/__tests__/src/rules/aria-role-test.js
index a9cdfeb51..09d0addd6 100644
--- a/__tests__/src/rules/aria-role-test.js
+++ b/__tests__/src/rules/aria-role-test.js
@@ -47,6 +47,14 @@ const ignoreNonDOMSchema = [{
ignoreNonDOM: true,
}];
+const customDivSettings = {
+ 'jsx-a11y': {
+ components: {
+ div: ['Div'],
+ },
+ },
+};
+
ruleTester.run('aria-role', rule, {
valid: [
// Variables should pass, as we are only testing literals.
@@ -66,6 +74,11 @@ ruleTester.run('aria-role', rule, {
{ code: '', options: ignoreNonDOMSchema },
{ code: '', options: ignoreNonDOMSchema },
{ code: '', options: ignoreNonDOMSchema },
+ {
+ code: '',
+ errors: [errorMessage],
+ settings: customDivSettings,
+ },
].concat(validTests).map(parserOptionsMapper),
invalid: [
@@ -82,5 +95,12 @@ ruleTester.run('aria-role', rule, {
{ code: '', errors: [errorMessage] },
{ code: '', errors: [errorMessage] },
{ code: '', errors: [errorMessage] },
+ { code: '', errors: [errorMessage], settings: customDivSettings },
+ {
+ code: '',
+ errors: [errorMessage],
+ options: ignoreNonDOMSchema,
+ settings: customDivSettings,
+ },
].concat(invalidTests).map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/aria-unsupported-elements-test.js b/__tests__/src/rules/aria-unsupported-elements-test.js
index d34b517eb..54118c3e7 100644
--- a/__tests__/src/rules/aria-unsupported-elements-test.js
+++ b/__tests__/src/rules/aria-unsupported-elements-test.js
@@ -54,7 +54,11 @@ const invalidRoleValidityTests = domElements
.map((reservedElem) => ({
code: `<${reservedElem} role {...props} />`,
errors: [errorMessage('role')],
- }));
+ })).concat({
+ code: '',
+ errors: [errorMessage('aria-hidden')],
+ settings: { 'jsx-a11y': { components: { meta: ['Meta'] } } },
+ });
const invalidAriaValidityTests = domElements
.filter((element) => Boolean(dom.get(element).reserved))
diff --git a/__tests__/src/rules/autocomplete-valid-test.js b/__tests__/src/rules/autocomplete-valid-test.js
index ef731b19c..0e3e3df13 100644
--- a/__tests__/src/rules/autocomplete-valid-test.js
+++ b/__tests__/src/rules/autocomplete-valid-test.js
@@ -28,6 +28,14 @@ const inappropriateAutocomplete = [{
type: 'JSXOpeningElement',
}];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ input: ['Input'],
+ },
+ },
+};
+
ruleTester.run('autocomplete-valid', rule, {
valid: [
// INAPPLICABLE
@@ -46,6 +54,8 @@ ruleTester.run('autocomplete-valid', rule, {
{ code: ';' },
{ code: ';' },
{ code: ';' },
+ { code: '', settings: componentsSettings },
+ { code: '' },
// PASSED "autocomplete-appropriate"
// see also: https://github.com/dequelabs/axe-core/issues/2912
@@ -61,5 +71,6 @@ ruleTester.run('autocomplete-valid', rule, {
{ code: ';', errors: invalidAutocomplete },
{ code: ';', errors: invalidAutocomplete },
{ code: ';', errors: invalidAutocomplete, options: [{ inputComponents: ['Bar'] }] },
+ { code: '', errors: invalidAutocomplete, settings: componentsSettings },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/click-events-have-key-events-test.js b/__tests__/src/rules/click-events-have-key-events-test.js
index 89965a57b..8f7197f27 100644
--- a/__tests__/src/rules/click-events-have-key-events-test.js
+++ b/__tests__/src/rules/click-events-have-key-events-test.js
@@ -51,6 +51,7 @@ ruleTester.run('click-events-have-key-events', rule, {
{ code: ' void 0} role="none" />;' },
{ code: '
' },
{ code: '
' },
+ { code: '
' },
].map(parserOptionsMapper),
invalid: [
{ code: '
void 0} />;', errors: [expectedError] },
@@ -70,5 +71,6 @@ ruleTester.run('click-events-have-key-events', rule, {
},
{ code: '
void 0} />', errors: [expectedError] },
{ code: ' void 0} />', errors: [expectedError] },
+ { code: '', errors: [expectedError], settings: { 'jsx-a11y': { components: { footer: ['Footer'] } } } },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/control-has-associated-label-test.js b/__tests__/src/rules/control-has-associated-label-test.js
index 3a285c74f..e3261c0b1 100644
--- a/__tests__/src/rules/control-has-associated-label-test.js
+++ b/__tests__/src/rules/control-has-associated-label-test.js
@@ -30,6 +30,7 @@ const alwaysValid = [
// Custom Control Components
{ code: 'Save', options: [{ depth: 3, controlComponents: ['CustomControl'] }] },
{ code: '', options: [{ depth: 3, controlComponents: ['CustomControl'], labelAttributes: ['label'] }] },
+ { code: 'Save', settings: { 'jsx-a11y': { components: { button: ['CustomControl'] } } } },
// Interactive Elements
{ code: '' },
{ code: '' },
@@ -255,6 +256,7 @@ const neverValid = [
{ code: '', errors: [expectedError] },
{ code: '', options: [{ depth: 3 }], errors: [expectedError] },
{ code: '', options: [{ depth: 3, controlComponents: ['CustomControl'] }], errors: [expectedError] },
+ { code: '', errors: [expectedError], settings: { 'jsx-a11y': { components: { button: ['CustomControl'] } } } },
{ code: '', errors: [expectedError] },
{ code: '
', errors: [expectedError] },
{ code: '
', errors: [expectedError] },
diff --git a/__tests__/src/rules/heading-has-content-test.js b/__tests__/src/rules/heading-has-content-test.js
index e52930452..e2d164115 100644
--- a/__tests__/src/rules/heading-has-content-test.js
+++ b/__tests__/src/rules/heading-has-content-test.js
@@ -26,6 +26,15 @@ const components = [{
components: ['Heading', 'Title'],
}];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ h2: ['Heading'],
+ h1: ['Title'],
+ },
+ },
+};
+
ruleTester.run('heading-has-content', rule, {
valid: [
// DEFAULT ELEMENT TESTS
@@ -51,6 +60,8 @@ ruleTester.run('heading-has-content', rule, {
{ code: '
', options: components },
{ code: '
', options: components },
{ code: '
' },
+ // CUSTOM ELEMENT TESTS FOR COMPONENTS SETTINGS
+ { code: '
Foo', settings: componentsSettings },
].map(parserOptionsMapper),
invalid: [
// DEFAULT ELEMENT TESTS
@@ -62,5 +73,8 @@ ruleTester.run('heading-has-content', rule, {
{ code: '
', errors: [expectedError], options: components },
{ code: '
', errors: [expectedError], options: components },
{ code: '
{undefined}', errors: [expectedError], options: components },
+
+ // CUSTOM ELEMENT TESTS FOR COMPONENTS SETTINGS
+ { code: '
', errors: [expectedError], settings: componentsSettings },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/html-has-lang-test.js b/__tests__/src/rules/html-has-lang-test.js
index 61348f676..6ea3fc341 100644
--- a/__tests__/src/rules/html-has-lang-test.js
+++ b/__tests__/src/rules/html-has-lang-test.js
@@ -30,10 +30,12 @@ ruleTester.run('html-has-lang', rule, {
{ code: '
' },
{ code: '
' },
{ code: '' },
+ { code: '', errors: [expectedError], settings: { 'jsx-a11y': { components: { html: ['HTMLTop'] } } } },
].map(parserOptionsMapper),
invalid: [
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
+ { code: '', errors: [expectedError], settings: { 'jsx-a11y': { components: { html: ['HTMLTop'] } } } },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/iframe-has-title-test.js b/__tests__/src/rules/iframe-has-title-test.js
index 195fdba44..e34e683c6 100644
--- a/__tests__/src/rules/iframe-has-title-test.js
+++ b/__tests__/src/rules/iframe-has-title-test.js
@@ -22,12 +22,21 @@ const expectedError = {
type: 'JSXOpeningElement',
};
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ iframe: ['FooComponent'],
+ },
+ },
+};
+
ruleTester.run('html-has-lang', rule, {
valid: [
{ code: ';' },
{ code: '' },
{ code: '' },
{ code: '' },
+ { code: '', settings: componentsSettings },
].map(parserOptionsMapper),
invalid: [
{ code: '', errors: [expectedError] },
@@ -40,5 +49,6 @@ ruleTester.run('html-has-lang', rule, {
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
+ { code: '', errors: [expectedError], settings: componentsSettings },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/img-redundant-alt-test.js b/__tests__/src/rules/img-redundant-alt-test.js
index e4ce2658f..d918a7cc6 100644
--- a/__tests__/src/rules/img-redundant-alt-test.js
+++ b/__tests__/src/rules/img-redundant-alt-test.js
@@ -22,6 +22,14 @@ const array = [{
words: ['Word1', 'Word2'],
}];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ img: ['Image'],
+ },
+ },
+};
+
const ruleTester = new RuleTester();
const expectedError = {
@@ -63,6 +71,8 @@ ruleTester.run('img-redundant-alt', rule, {
] : [],
{ code: ';' },
{ code: ';' },
+ { code: '' },
+ { code: '', settings: componentsSettings },
).map(parserOptionsMapper),
invalid: [
{ code: ';', errors: [expectedError] },
@@ -111,6 +121,7 @@ ruleTester.run('img-redundant-alt', rule, {
code: '',
errors: [expectedError],
},
+ { code: '', errors: [expectedError], settings: componentsSettings },
// TESTS FOR ARRAY OPTION TESTS
{ code: ';', options: array, errors: [expectedError] },
diff --git a/__tests__/src/rules/interactive-supports-focus-test.js b/__tests__/src/rules/interactive-supports-focus-test.js
index 4f97e786d..15a0d5236 100644
--- a/__tests__/src/rules/interactive-supports-focus-test.js
+++ b/__tests__/src/rules/interactive-supports-focus-test.js
@@ -38,6 +38,16 @@ const tabindexTemplate = template`<${0} role="${1}" ${2}={() => void 0} tabIndex
const tabbableTemplate = template`Elements with the '${0}' interactive role must be tabbable.`;
const focusableTemplate = template`Elements with the '${0}' interactive role must be focusable.`;
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ div: ['Div'],
+ },
+ },
+};
+
+const buttonError = { message: tabbableTemplate('button'), type };
+
const recommendedOptions = configs.recommended.rules[`jsx-a11y/${ruleName}`][1] || {};
const strictOptions = configs.strict.rules[`jsx-a11y/${ruleName}`][1] || {};
@@ -116,6 +126,11 @@ const alwaysValid = [
{ code: ' void 0} />' },
{ code: '
void 0} aria-hidden={false} />;' },
{ code: ' void 0} type="hidden" />;' },
+ { code: ' void 0} role="button" tabIndex="0" />', settings: componentsSettings },
+];
+
+const neverValid = [
+ { code: '
void 0} role="button" />', errors: [buttonError], settings: componentsSettings },
];
const interactiveRoles = [
@@ -209,6 +224,7 @@ ruleTester.run(`${ruleName}:recommended`, rule, {
.map(ruleOptionsMapperFactory(recommendedOptions))
.map(parserOptionsMapper),
invalid: [
+ ...neverValid,
...failReducer(recommendedRoles, triggeringHandlers, tabbableTemplate),
...failReducer(
interactiveRoles.filter((role) => !includes(recommendedRoles, role)),
@@ -237,6 +253,7 @@ ruleTester.run(`${ruleName}:strict`, rule, {
.map(ruleOptionsMapperFactory(strictOptions))
.map(parserOptionsMapper),
invalid: [
+ ...neverValid,
...failReducer(strictRoles, triggeringHandlers, tabbableTemplate),
...failReducer(
interactiveRoles.filter((role) => !includes(strictRoles, role)),
diff --git a/__tests__/src/rules/label-has-associated-control-test.js b/__tests__/src/rules/label-has-associated-control-test.js
index e80e21fcb..4284ed7be 100644
--- a/__tests__/src/rules/label-has-associated-control-test.js
+++ b/__tests__/src/rules/label-has-associated-control-test.js
@@ -25,6 +25,15 @@ const expectedError = {
type: 'JSXOpeningElement',
};
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ input: ['CustomInput'],
+ label: ['CustomLabel'],
+ },
+ },
+};
+
const htmlForValid = [
{ code: '
', options: [{ depth: 4 }] },
{ code: '
' },
@@ -33,6 +42,7 @@ const htmlForValid = [
// Custom label component.
{ code: '
', options: [{ labelComponents: ['CustomLabel'] }] },
{ code: '
', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
+ { code: '
', settings: componentsSettings },
// Custom label attributes.
{ code: '
', options: [{ labelAttributes: ['label'] }] },
// Glob support for controlComponents option.
@@ -57,6 +67,7 @@ const nestingValid = [
{ code: '
' },
// Custom controlComponents.
{ code: '
', options: [{ controlComponents: ['CustomInput'] }] },
+ { code: '
', settings: componentsSettings },
{ code: '
A label', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }] },
{ code: '
', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }] },
// Glob support for controlComponents option.
@@ -72,6 +83,8 @@ const bothValid = [
// Custom label component.
{ code: '
', options: [{ labelComponents: ['CustomLabel'] }] },
{ code: '
', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
+ { code: '
', settings: componentsSettings },
+ { code: '
', settings: componentsSettings },
// Custom label attributes.
{ code: '
', options: [{ labelAttributes: ['label'] }] },
{ code: '
' },
@@ -90,6 +103,7 @@ const htmlForInvalid = [
// Custom label component.
{ code: '
', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
{ code: '
', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
+ { code: '
', settings: componentsSettings, errors: [expectedError] },
// Custom label attributes.
{ code: '
', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
];
@@ -108,6 +122,8 @@ const nestingInvalid = [
{ code: '
', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
{ code: '
A label', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
{ code: '
', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedError] },
+ { code: '
', settings: componentsSettings, errors: [expectedError] },
+ { code: '
A label', settings: componentsSettings, errors: [expectedError] },
];
const neverValid = [
@@ -121,12 +137,15 @@ const neverValid = [
// Custom label component.
{ code: '
', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
{ code: '
', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
+ { code: '
', settings: componentsSettings, errors: [expectedError] },
// Custom label attributes.
{ code: '
', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
// Custom controlComponents.
{ code: '
', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
{ code: '
', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
{ code: '
', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedError] },
+ { code: '
', settings: componentsSettings, errors: [expectedError] },
+ { code: '
', settings: componentsSettings, errors: [expectedError] },
];
// htmlFor valid
ruleTester.run(ruleName, rule, {
diff --git a/__tests__/src/rules/lang-test.js b/__tests__/src/rules/lang-test.js
index 6fbde3996..8c8831265 100644
--- a/__tests__/src/rules/lang-test.js
+++ b/__tests__/src/rules/lang-test.js
@@ -22,6 +22,14 @@ const expectedError = {
type: 'JSXAttribute',
};
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ html: ['Foo'],
+ },
+ },
+};
+
ruleTester.run('lang', rule, {
valid: [
{ code: '
;' },
@@ -35,11 +43,13 @@ ruleTester.run('lang', rule, {
{ code: '
' },
{ code: '
' },
{ code: '' },
- { code: '
' },
+ { code: '
' },
+ { code: '
', settings: componentsSettings },
].map(parserOptionsMapper),
invalid: [
{ code: '
', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
+ { code: '', settings: componentsSettings, errors: [expectedError] },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/media-has-caption-test.js b/__tests__/src/rules/media-has-caption-test.js
index 0435f7803..1cd3b1aee 100644
--- a/__tests__/src/rules/media-has-caption-test.js
+++ b/__tests__/src/rules/media-has-caption-test.js
@@ -30,6 +30,16 @@ const customSchema = [
},
];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ audio: ['Audio'],
+ video: ['Video'],
+ track: ['Track'],
+ },
+ },
+};
+
ruleTester.run('media-has-caption', rule, {
valid: [
{ code: '
;' },
@@ -93,6 +103,46 @@ ruleTester.run('media-has-caption', rule, {
code: '',
options: customSchema,
},
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ },
].map(parserOptionsMapper),
invalid: [
{ code: '', errors: [expectedError] },
@@ -116,11 +166,23 @@ ruleTester.run('media-has-caption', rule, {
options: customSchema,
errors: [expectedError],
},
+ {
+ code: '',
+ settings: componentsSettings,
+ errors: [expectedError],
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ errors: [expectedError],
+ },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', options: customSchema, errors: [expectedError] },
{ code: '', options: customSchema, errors: [expectedError] },
+ { code: '', settings: componentsSettings, errors: [expectedError] },
+ { code: '', settings: componentsSettings, errors: [expectedError] },
{ code: '', options: customSchema, errors: [expectedError] },
{ code: '', options: customSchema, errors: [expectedError] },
{
@@ -133,5 +195,15 @@ ruleTester.run('media-has-caption', rule, {
options: customSchema,
errors: [expectedError],
},
+ {
+ code: '',
+ settings: componentsSettings,
+ errors: [expectedError],
+ },
+ {
+ code: '',
+ settings: componentsSettings,
+ errors: [expectedError],
+ },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/no-autofocus-test.js b/__tests__/src/rules/no-autofocus-test.js
index 636bb671e..f807094a4 100644
--- a/__tests__/src/rules/no-autofocus-test.js
+++ b/__tests__/src/rules/no-autofocus-test.js
@@ -28,6 +28,14 @@ const ignoreNonDOMSchema = [
},
];
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ button: ['Button'],
+ },
+ },
+};
+
ruleTester.run('no-autofocus', rule, {
valid: [
{ code: ';' },
@@ -36,6 +44,8 @@ ruleTester.run('no-autofocus', rule, {
{ code: '' },
{ code: '', options: ignoreNonDOMSchema },
{ code: '', options: ignoreNonDOMSchema },
+ { code: '', settings: componentsSettings },
+ { code: '', options: ignoreNonDOMSchema, settings: componentsSettings },
].map(parserOptionsMapper),
invalid: [
{ code: '', errors: [expectedError] },
@@ -46,5 +56,12 @@ ruleTester.run('no-autofocus', rule, {
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '', errors: [expectedError] },
+ { code: '', errors: [expectedError], settings: componentsSettings },
+ {
+ code: '',
+ errors: [expectedError],
+ options: ignoreNonDOMSchema,
+ settings: componentsSettings,
+ },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/no-distracting-elements-test.js b/__tests__/src/rules/no-distracting-elements-test.js
index 8cc484ebf..825d53f66 100644
--- a/__tests__/src/rules/no-distracting-elements-test.js
+++ b/__tests__/src/rules/no-distracting-elements-test.js
@@ -22,6 +22,14 @@ const expectedError = (element) => ({
type: 'JSXOpeningElement',
});
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ blink: ['Blink'],
+ },
+ },
+};
+
ruleTester.run('no-marquee', rule, {
valid: [
{ code: ';' },
@@ -37,5 +45,6 @@ ruleTester.run('no-marquee', rule, {
{ code: '', errors: [expectedError('blink')] },
{ code: '', errors: [expectedError('blink')] },
{ code: '', errors: [expectedError('blink')] },
+ { code: '', settings: componentsSettings, errors: [expectedError('blink')] },
].map(parserOptionsMapper),
});
diff --git a/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js b/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js
index e925476b7..8e599423a 100644
--- a/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js
+++ b/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js
@@ -28,6 +28,15 @@ const expectedError = {
const ruleName = 'jsx-a11y/no-interactive-element-to-noninteractive-role';
+const componentsSettings = {
+ 'jsx-a11y': {
+ components: {
+ a: ['Link'],
+ button: ['Button'],
+ },
+ },
+};
+
const alwaysValid = [
{ code: '' },
{ code: '' },
@@ -289,6 +298,9 @@ const alwaysValid = [
/* Namespaced roles are not checked */
{ code: '' },
{ code: '' },
+ { code: '' },
+ { code: '', settings: componentsSettings },
+ { code: '', settings: componentsSettings },
];
const neverValid = [
@@ -360,6 +372,8 @@ const neverValid = [
{ code: ';', errors: [expectedError] },
{ code: '', errors: [expectedError] },
{ code: '
;', errors: [expectedError] },
+ /* Custom elements */
+ { code: '', errors: [expectedError], settings: componentsSettings },
];
const recommendedOptions = (configs.recommended.rules[ruleName][1] || {});
diff --git a/__tests__/src/rules/no-noninteractive-element-interactions-test.js b/__tests__/src/rules/no-noninteractive-element-interactions-test.js
index dc949d22d..3d86376e3 100644
--- a/__tests__/src/rules/no-noninteractive-element-interactions-test.js
+++ b/__tests__/src/rules/no-noninteractive-element-interactions-test.js
@@ -31,6 +31,11 @@ const ruleName = 'no-noninteractive-element-interactions';
const alwaysValid = [
{ code: '' },
{ code: '' },
+ { code: ' void 0} />;' },
+ {
+ code: '