@arcaauth/eslint-plugin-jsx-a11y 6.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +17 -0
- package/.eslintrc +44 -0
- package/CHANGELOG.md +774 -0
- package/LICENSE.md +8 -0
- package/README.md +423 -0
- package/__mocks__/IdentifierMock.js +15 -0
- package/__mocks__/JSXAttributeMock.js +39 -0
- package/__mocks__/JSXElementMock.js +37 -0
- package/__mocks__/JSXExpressionContainerMock.js +15 -0
- package/__mocks__/JSXSpreadAttributeMock.js +18 -0
- package/__mocks__/JSXTextMock.js +17 -0
- package/__mocks__/LiteralMock.js +17 -0
- package/__mocks__/genInteractives.js +218 -0
- package/__tests__/__util__/axeMapping.js +6 -0
- package/__tests__/__util__/helpers/getESLintCoreRule.js +9 -0
- package/__tests__/__util__/helpers/parsers.js +186 -0
- package/__tests__/__util__/parserOptionsMapper.js +53 -0
- package/__tests__/__util__/ruleOptionsMapperFactory.js +33 -0
- package/__tests__/index-test.js +40 -0
- package/__tests__/src/rules/accessible-emoji-test.js +66 -0
- package/__tests__/src/rules/alt-text-test.js +291 -0
- package/__tests__/src/rules/anchor-ambiguous-text-test.js +117 -0
- package/__tests__/src/rules/anchor-has-content-test.js +54 -0
- package/__tests__/src/rules/anchor-is-valid-test.js +532 -0
- package/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js +95 -0
- package/__tests__/src/rules/aria-props-test.js +69 -0
- package/__tests__/src/rules/aria-proptypes-test.js +311 -0
- package/__tests__/src/rules/aria-role-test.js +118 -0
- package/__tests__/src/rules/aria-unsupported-elements-test.js +75 -0
- package/__tests__/src/rules/autocomplete-valid-test.js +77 -0
- package/__tests__/src/rules/click-events-have-key-events-test.js +77 -0
- package/__tests__/src/rules/control-has-associated-label-test.js +327 -0
- package/__tests__/src/rules/heading-has-content-test.js +85 -0
- package/__tests__/src/rules/html-has-lang-test.js +42 -0
- package/__tests__/src/rules/iframe-has-title-test.js +55 -0
- package/__tests__/src/rules/img-redundant-alt-test.js +137 -0
- package/__tests__/src/rules/interactive-supports-focus-test.js +267 -0
- package/__tests__/src/rules/label-has-associated-control-test.js +243 -0
- package/__tests__/src/rules/label-has-for-test.js +235 -0
- package/__tests__/src/rules/lang-test.js +59 -0
- package/__tests__/src/rules/media-has-caption-test.js +220 -0
- package/__tests__/src/rules/mouse-events-have-key-events-test.js +154 -0
- package/__tests__/src/rules/no-access-key-test.js +48 -0
- package/__tests__/src/rules/no-aria-hidden-on-focusable-test.js +44 -0
- package/__tests__/src/rules/no-autofocus-test.js +68 -0
- package/__tests__/src/rules/no-distracting-elements-test.js +51 -0
- package/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js +405 -0
- package/__tests__/src/rules/no-noninteractive-element-interactions-test.js +502 -0
- package/__tests__/src/rules/no-noninteractive-element-to-interactive-role-test.js +500 -0
- package/__tests__/src/rules/no-noninteractive-tabindex-test.js +123 -0
- package/__tests__/src/rules/no-onchange-test.js +57 -0
- package/__tests__/src/rules/no-redundant-roles-test.js +98 -0
- package/__tests__/src/rules/no-static-element-interactions-test.js +501 -0
- package/__tests__/src/rules/prefer-tag-over-role-test.js +63 -0
- package/__tests__/src/rules/role-has-required-aria-props-test.js +134 -0
- package/__tests__/src/rules/role-supports-aria-props-test.js +570 -0
- package/__tests__/src/rules/scope-test.js +50 -0
- package/__tests__/src/rules/tabindex-no-positive-test.js +55 -0
- package/__tests__/src/util/attributesComparator-test.js +91 -0
- package/__tests__/src/util/getAccessibleChildText-test.js +174 -0
- package/__tests__/src/util/getComputedRole-test.js +71 -0
- package/__tests__/src/util/getElementType-test.js +154 -0
- package/__tests__/src/util/getExplicitRole-test.js +35 -0
- package/__tests__/src/util/getImplicitRole-test.js +25 -0
- package/__tests__/src/util/getSuggestion-test.js +33 -0
- package/__tests__/src/util/getTabIndex-test.js +85 -0
- package/__tests__/src/util/hasAccessibleChild-test.js +157 -0
- package/__tests__/src/util/implicitRoles/input-test.js +87 -0
- package/__tests__/src/util/implicitRoles/menu-test.js +20 -0
- package/__tests__/src/util/implicitRoles/menuitem-test.js +38 -0
- package/__tests__/src/util/isAbstractRole-test.js +51 -0
- package/__tests__/src/util/isContentEditable-test.js +52 -0
- package/__tests__/src/util/isDOMElement-test.js +30 -0
- package/__tests__/src/util/isDisabledElement-test.js +88 -0
- package/__tests__/src/util/isFocusable-test.js +111 -0
- package/__tests__/src/util/isInteractiveElement-test.js +104 -0
- package/__tests__/src/util/isInteractiveRole-test.js +59 -0
- package/__tests__/src/util/isNonInteractiveElement-test.js +97 -0
- package/__tests__/src/util/isNonInteractiveRole-test.js +59 -0
- package/__tests__/src/util/isNonLiteralProperty-test.js +52 -0
- package/__tests__/src/util/isSemanticRoleElement-test.js +72 -0
- package/__tests__/src/util/mayContainChildComponent-test.js +219 -0
- package/__tests__/src/util/mayHaveAccessibleLabel-test.js +256 -0
- package/__tests__/src/util/parserOptionsMapper-test.js +93 -0
- package/__tests__/src/util/schemas-test.js +35 -0
- package/docs/rules/accessible-emoji.md +30 -0
- package/docs/rules/alt-text.md +168 -0
- package/docs/rules/anchor-ambiguous-text.md +91 -0
- package/docs/rules/anchor-has-content.md +64 -0
- package/docs/rules/anchor-is-valid.md +270 -0
- package/docs/rules/aria-activedescendant-has-tabindex.md +52 -0
- package/docs/rules/aria-props.md +29 -0
- package/docs/rules/aria-proptypes.md +30 -0
- package/docs/rules/aria-role.md +51 -0
- package/docs/rules/aria-unsupported-elements.md +30 -0
- package/docs/rules/autocomplete-valid.md +49 -0
- package/docs/rules/click-events-have-key-events.md +28 -0
- package/docs/rules/control-has-associated-label.md +113 -0
- package/docs/rules/heading-has-content.md +67 -0
- package/docs/rules/html-has-lang.md +31 -0
- package/docs/rules/iframe-has-title.md +37 -0
- package/docs/rules/img-redundant-alt.md +48 -0
- package/docs/rules/interactive-supports-focus.md +156 -0
- package/docs/rules/label-has-associated-control.md +152 -0
- package/docs/rules/label-has-for.md +130 -0
- package/docs/rules/lang.md +31 -0
- package/docs/rules/media-has-caption.md +48 -0
- package/docs/rules/mouse-events-have-key-events.md +58 -0
- package/docs/rules/no-access-key.md +30 -0
- package/docs/rules/no-aria-hidden-on-focusable.md +37 -0
- package/docs/rules/no-autofocus.md +43 -0
- package/docs/rules/no-distracting-elements.md +41 -0
- package/docs/rules/no-interactive-element-to-noninteractive-role.md +73 -0
- package/docs/rules/no-noninteractive-element-interactions.md +145 -0
- package/docs/rules/no-noninteractive-element-to-interactive-role.md +76 -0
- package/docs/rules/no-noninteractive-tabindex.md +115 -0
- package/docs/rules/no-onchange.md +36 -0
- package/docs/rules/no-redundant-roles.md +46 -0
- package/docs/rules/no-static-element-interactions.md +114 -0
- package/docs/rules/prefer-tag-over-role.md +32 -0
- package/docs/rules/role-has-required-aria-props.md +31 -0
- package/docs/rules/role-supports-aria-props.md +39 -0
- package/docs/rules/scope.md +30 -0
- package/docs/rules/tabindex-no-positive.md +32 -0
- package/lib/configs/flat-config-base.js +11 -0
- package/lib/configs/legacy-config-base.js +9 -0
- package/lib/index.js +209 -0
- package/lib/rules/accessible-emoji.js +63 -0
- package/lib/rules/alt-text.js +218 -0
- package/lib/rules/anchor-ambiguous-text.js +64 -0
- package/lib/rules/anchor-has-content.js +60 -0
- package/lib/rules/anchor-is-valid.js +122 -0
- package/lib/rules/aria-activedescendant-has-tabindex.js +66 -0
- package/lib/rules/aria-props.js +59 -0
- package/lib/rules/aria-proptypes.js +114 -0
- package/lib/rules/aria-role.js +89 -0
- package/lib/rules/aria-unsupported-elements.js +64 -0
- package/lib/rules/autocomplete-valid.js +67 -0
- package/lib/rules/click-events-have-key-events.js +68 -0
- package/lib/rules/control-has-associated-label.js +103 -0
- package/lib/rules/heading-has-content.js +61 -0
- package/lib/rules/html-has-lang.js +50 -0
- package/lib/rules/iframe-has-title.js +50 -0
- package/lib/rules/img-redundant-alt.js +88 -0
- package/lib/rules/interactive-supports-focus.js +87 -0
- package/lib/rules/label-has-associated-control.js +127 -0
- package/lib/rules/label-has-for.js +150 -0
- package/lib/rules/lang.js +68 -0
- package/lib/rules/media-has-caption.js +96 -0
- package/lib/rules/mouse-events-have-key-events.js +94 -0
- package/lib/rules/no-access-key.js +43 -0
- package/lib/rules/no-aria-hidden-on-focusable.js +47 -0
- package/lib/rules/no-autofocus.js +62 -0
- package/lib/rules/no-distracting-elements.js +54 -0
- package/lib/rules/no-interactive-element-to-noninteractive-role.js +81 -0
- package/lib/rules/no-noninteractive-element-interactions.js +95 -0
- package/lib/rules/no-noninteractive-element-to-interactive-role.js +80 -0
- package/lib/rules/no-noninteractive-tabindex.js +109 -0
- package/lib/rules/no-onchange.js +52 -0
- package/lib/rules/no-redundant-roles.js +86 -0
- package/lib/rules/no-static-element-interactions.js +102 -0
- package/lib/rules/prefer-tag-over-role.js +75 -0
- package/lib/rules/role-has-required-aria-props.js +88 -0
- package/lib/rules/role-supports-aria-props.js +78 -0
- package/lib/rules/scope.js +58 -0
- package/lib/rules/tabindex-no-positive.js +53 -0
- package/lib/util/attributesComparator.js +34 -0
- package/lib/util/getAccessibleChildText.js +55 -0
- package/lib/util/getComputedRole.js +19 -0
- package/lib/util/getElementType.js +30 -0
- package/lib/util/getExplicitRole.js +27 -0
- package/lib/util/getImplicitRole.js +24 -0
- package/lib/util/getSuggestion.js +32 -0
- package/lib/util/getTabIndex.js +34 -0
- package/lib/util/hasAccessibleChild.js +30 -0
- package/lib/util/implicitRoles/a.js +17 -0
- package/lib/util/implicitRoles/area.js +17 -0
- package/lib/util/implicitRoles/article.js +13 -0
- package/lib/util/implicitRoles/aside.js +13 -0
- package/lib/util/implicitRoles/body.js +13 -0
- package/lib/util/implicitRoles/button.js +13 -0
- package/lib/util/implicitRoles/datalist.js +13 -0
- package/lib/util/implicitRoles/details.js +13 -0
- package/lib/util/implicitRoles/dialog.js +13 -0
- package/lib/util/implicitRoles/form.js +13 -0
- package/lib/util/implicitRoles/h1.js +13 -0
- package/lib/util/implicitRoles/h2.js +13 -0
- package/lib/util/implicitRoles/h3.js +13 -0
- package/lib/util/implicitRoles/h4.js +13 -0
- package/lib/util/implicitRoles/h5.js +13 -0
- package/lib/util/implicitRoles/h6.js +13 -0
- package/lib/util/implicitRoles/hr.js +13 -0
- package/lib/util/implicitRoles/img.js +31 -0
- package/lib/util/implicitRoles/index.js +82 -0
- package/lib/util/implicitRoles/input.js +38 -0
- package/lib/util/implicitRoles/li.js +13 -0
- package/lib/util/implicitRoles/link.js +17 -0
- package/lib/util/implicitRoles/menu.js +19 -0
- package/lib/util/implicitRoles/menuitem.js +28 -0
- package/lib/util/implicitRoles/meter.js +13 -0
- package/lib/util/implicitRoles/nav.js +13 -0
- package/lib/util/implicitRoles/ol.js +13 -0
- package/lib/util/implicitRoles/option.js +13 -0
- package/lib/util/implicitRoles/output.js +13 -0
- package/lib/util/implicitRoles/progress.js +13 -0
- package/lib/util/implicitRoles/section.js +13 -0
- package/lib/util/implicitRoles/select.js +13 -0
- package/lib/util/implicitRoles/tbody.js +13 -0
- package/lib/util/implicitRoles/textarea.js +13 -0
- package/lib/util/implicitRoles/tfoot.js +13 -0
- package/lib/util/implicitRoles/thead.js +13 -0
- package/lib/util/implicitRoles/ul.js +13 -0
- package/lib/util/isAbstractRole.js +23 -0
- package/lib/util/isContentEditable.js +13 -0
- package/lib/util/isDOMElement.js +15 -0
- package/lib/util/isDisabledElement.js +23 -0
- package/lib/util/isFocusable.js +23 -0
- package/lib/util/isHiddenFromScreenReader.js +26 -0
- package/lib/util/isInteractiveElement.js +116 -0
- package/lib/util/isInteractiveRole.js +54 -0
- package/lib/util/isNonInteractiveElement.js +131 -0
- package/lib/util/isNonInteractiveRole.js +55 -0
- package/lib/util/isNonLiteralProperty.js +29 -0
- package/lib/util/isPresentationRole.js +13 -0
- package/lib/util/isSemanticRoleElement.js +54 -0
- package/lib/util/mayContainChildComponent.js +50 -0
- package/lib/util/mayHaveAccessibleLabel.js +95 -0
- package/lib/util/schemas.js +52 -0
- package/package.json +120 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce all aria-* properties are valid.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { aria } from 'aria-query';
|
|
11
|
+
import { RuleTester } from 'eslint';
|
|
12
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
13
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
14
|
+
import rule from '../../../src/rules/aria-props';
|
|
15
|
+
import getSuggestion from '../../../src/util/getSuggestion';
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Tests
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const ruleTester = new RuleTester();
|
|
22
|
+
const ariaAttributes = aria.keys();
|
|
23
|
+
|
|
24
|
+
const errorMessage = (name) => {
|
|
25
|
+
const suggestions = getSuggestion(name, ariaAttributes);
|
|
26
|
+
const message = `${name}: This attribute is an invalid ARIA attribute.`;
|
|
27
|
+
|
|
28
|
+
if (suggestions.length > 0) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'JSXAttribute',
|
|
31
|
+
message: `${message} Did you mean to use ${suggestions}?`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
type: 'JSXAttribute',
|
|
37
|
+
message,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Create basic test cases using all valid role types.
|
|
42
|
+
const basicValidityTests = ariaAttributes.map((prop) => ({
|
|
43
|
+
code: `<div ${prop.toLowerCase()}="foobar" />`,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
ruleTester.run('aria-props', rule, {
|
|
47
|
+
valid: parsers.all([].concat(
|
|
48
|
+
// Variables should pass, as we are only testing literals.
|
|
49
|
+
{ code: '<div />' },
|
|
50
|
+
{ code: '<div></div>' },
|
|
51
|
+
{ code: '<div aria="wee"></div>' }, // Needs aria-*
|
|
52
|
+
{ code: '<div abcARIAdef="true"></div>' },
|
|
53
|
+
{ code: '<div fooaria-foobar="true"></div>' },
|
|
54
|
+
{ code: '<div fooaria-hidden="true"></div>' },
|
|
55
|
+
{ code: '<Bar baz />' },
|
|
56
|
+
{ code: '<input type="text" aria-errormessage="foobar" />' },
|
|
57
|
+
)).concat(basicValidityTests).map(parserOptionsMapper),
|
|
58
|
+
invalid: parsers.all([].concat(
|
|
59
|
+
{ code: '<div aria-="foobar" />', errors: [errorMessage('aria-')] },
|
|
60
|
+
{
|
|
61
|
+
code: '<div aria-labeledby="foobar" />',
|
|
62
|
+
errors: [errorMessage('aria-labeledby')],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
code: '<div aria-skldjfaria-klajsd="foobar" />',
|
|
66
|
+
errors: [errorMessage('aria-skldjfaria-klajsd')],
|
|
67
|
+
},
|
|
68
|
+
)).map(parserOptionsMapper),
|
|
69
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce ARIA state and property values are valid.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { aria } from 'aria-query';
|
|
11
|
+
import { RuleTester } from 'eslint';
|
|
12
|
+
import test from 'tape';
|
|
13
|
+
|
|
14
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
15
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
16
|
+
import rule from '../../../src/rules/aria-proptypes';
|
|
17
|
+
|
|
18
|
+
const { validityCheck } = rule;
|
|
19
|
+
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
21
|
+
// Tests
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const ruleTester = new RuleTester();
|
|
25
|
+
|
|
26
|
+
const errorMessage = (name) => {
|
|
27
|
+
const {
|
|
28
|
+
type,
|
|
29
|
+
values: permittedValues,
|
|
30
|
+
} = aria.get(name.toLowerCase());
|
|
31
|
+
|
|
32
|
+
switch (type) {
|
|
33
|
+
case 'tristate':
|
|
34
|
+
return { message: `The value for ${name} must be a boolean or the string "mixed".` };
|
|
35
|
+
case 'token':
|
|
36
|
+
return { message: `The value for ${name} must be a single token from the following: ${permittedValues}.` };
|
|
37
|
+
case 'tokenlist':
|
|
38
|
+
return {
|
|
39
|
+
message: `The value for ${name} must be a list of one or more \
|
|
40
|
+
tokens from the following: ${permittedValues}.`,
|
|
41
|
+
};
|
|
42
|
+
case 'idlist':
|
|
43
|
+
return { message: `The value for ${name} must be a list of strings that represent DOM element IDs (idlist)` };
|
|
44
|
+
case 'id':
|
|
45
|
+
return { message: `The value for ${name} must be a string that represents a DOM element ID` };
|
|
46
|
+
case 'boolean':
|
|
47
|
+
case 'string':
|
|
48
|
+
case 'integer':
|
|
49
|
+
case 'number':
|
|
50
|
+
default:
|
|
51
|
+
return { message: `The value for ${name} must be a ${type}.` };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
test('validityCheck', (t) => {
|
|
56
|
+
t.equal(
|
|
57
|
+
validityCheck(null, null),
|
|
58
|
+
false,
|
|
59
|
+
'is false for an unknown expected type',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
t.end();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
ruleTester.run('aria-proptypes', rule, {
|
|
66
|
+
valid: parsers.all([].concat(
|
|
67
|
+
// DON'T TEST INVALID ARIA-* PROPS
|
|
68
|
+
{ code: '<div aria-foo="true" />' },
|
|
69
|
+
{ code: '<div abcaria-foo="true" />' },
|
|
70
|
+
|
|
71
|
+
// BOOLEAN
|
|
72
|
+
{ code: '<div aria-hidden={true} />' },
|
|
73
|
+
{ code: '<div aria-hidden="true" />' },
|
|
74
|
+
{ code: '<div aria-hidden={"false"} />' },
|
|
75
|
+
{ code: '<div aria-hidden={!false} />' },
|
|
76
|
+
{ code: '<div aria-hidden />' },
|
|
77
|
+
{ code: '<div aria-hidden={false} />' },
|
|
78
|
+
{ code: '<div aria-hidden={!true} />' },
|
|
79
|
+
{ code: '<div aria-hidden={!"yes"} />' },
|
|
80
|
+
{ code: '<div aria-hidden={foo} />' },
|
|
81
|
+
{ code: '<div aria-hidden={foo.bar} />' },
|
|
82
|
+
{ code: '<div aria-hidden={null} />' },
|
|
83
|
+
{ code: '<div aria-hidden={undefined} />' },
|
|
84
|
+
{ code: '<div aria-hidden={<div />} />' },
|
|
85
|
+
|
|
86
|
+
// STRING
|
|
87
|
+
{ code: '<div aria-label="Close" />' },
|
|
88
|
+
{ code: '<div aria-label={`Close`} />' },
|
|
89
|
+
{ code: '<div aria-label={foo} />' },
|
|
90
|
+
{ code: '<div aria-label={foo.bar} />' },
|
|
91
|
+
{ code: '<div aria-label={null} />' },
|
|
92
|
+
{ code: '<div aria-label={undefined} />' },
|
|
93
|
+
{ code: '<input aria-invalid={error ? "true" : "false"} />' },
|
|
94
|
+
{ code: '<input aria-invalid={undefined ? "true" : "false"} />' },
|
|
95
|
+
|
|
96
|
+
// TRISTATE
|
|
97
|
+
{ code: '<div aria-checked={true} />' },
|
|
98
|
+
{ code: '<div aria-checked="true" />' },
|
|
99
|
+
{ code: '<div aria-checked={"false"} />' },
|
|
100
|
+
{ code: '<div aria-checked={!false} />' },
|
|
101
|
+
{ code: '<div aria-checked />' },
|
|
102
|
+
{ code: '<div aria-checked={false} />' },
|
|
103
|
+
{ code: '<div aria-checked={!true} />' },
|
|
104
|
+
{ code: '<div aria-checked={!"yes"} />' },
|
|
105
|
+
{ code: '<div aria-checked={foo} />' },
|
|
106
|
+
{ code: '<div aria-checked={foo.bar} />' },
|
|
107
|
+
{ code: '<div aria-checked="mixed" />' },
|
|
108
|
+
{ code: '<div aria-checked={`mixed`} />' },
|
|
109
|
+
{ code: '<div aria-checked={null} />' },
|
|
110
|
+
{ code: '<div aria-checked={undefined} />' },
|
|
111
|
+
|
|
112
|
+
// INTEGER
|
|
113
|
+
{ code: '<div aria-level={123} />' },
|
|
114
|
+
{ code: '<div aria-level={-123} />' },
|
|
115
|
+
{ code: '<div aria-level={+123} />' },
|
|
116
|
+
{ code: '<div aria-level={~123} />' },
|
|
117
|
+
{ code: '<div aria-level={"123"} />' },
|
|
118
|
+
{ code: '<div aria-level={`123`} />' },
|
|
119
|
+
{ code: '<div aria-level="123" />' },
|
|
120
|
+
{ code: '<div aria-level={foo} />' },
|
|
121
|
+
{ code: '<div aria-level={foo.bar} />' },
|
|
122
|
+
{ code: '<div aria-level={null} />' },
|
|
123
|
+
{ code: '<div aria-level={undefined} />' },
|
|
124
|
+
|
|
125
|
+
// NUMBER
|
|
126
|
+
{ code: '<div aria-valuemax={123} />' },
|
|
127
|
+
{ code: '<div aria-valuemax={-123} />' },
|
|
128
|
+
{ code: '<div aria-valuemax={+123} />' },
|
|
129
|
+
{ code: '<div aria-valuemax={~123} />' },
|
|
130
|
+
{ code: '<div aria-valuemax={"123"} />' },
|
|
131
|
+
{ code: '<div aria-valuemax={`123`} />' },
|
|
132
|
+
{ code: '<div aria-valuemax="123" />' },
|
|
133
|
+
{ code: '<div aria-valuemax={foo} />' },
|
|
134
|
+
{ code: '<div aria-valuemax={foo.bar} />' },
|
|
135
|
+
{ code: '<div aria-valuemax={null} />' },
|
|
136
|
+
{ code: '<div aria-valuemax={undefined} />' },
|
|
137
|
+
|
|
138
|
+
// TOKEN
|
|
139
|
+
{ code: '<div aria-sort="ascending" />' },
|
|
140
|
+
{ code: '<div aria-sort="ASCENDING" />' },
|
|
141
|
+
{ code: '<div aria-sort={"ascending"} />' },
|
|
142
|
+
{ code: '<div aria-sort={`ascending`} />' },
|
|
143
|
+
{ code: '<div aria-sort="descending" />' },
|
|
144
|
+
{ code: '<div aria-sort={"descending"} />' },
|
|
145
|
+
{ code: '<div aria-sort={`descending`} />' },
|
|
146
|
+
{ code: '<div aria-sort="none" />' },
|
|
147
|
+
{ code: '<div aria-sort={"none"} />' },
|
|
148
|
+
{ code: '<div aria-sort={`none`} />' },
|
|
149
|
+
{ code: '<div aria-sort="other" />' },
|
|
150
|
+
{ code: '<div aria-sort={"other"} />' },
|
|
151
|
+
{ code: '<div aria-sort={`other`} />' },
|
|
152
|
+
{ code: '<div aria-sort={foo} />' },
|
|
153
|
+
{ code: '<div aria-sort={foo.bar} />' },
|
|
154
|
+
{ code: '<div aria-invalid={true} />' },
|
|
155
|
+
{ code: '<div aria-invalid="true" />' },
|
|
156
|
+
{ code: '<div aria-invalid={false} />' },
|
|
157
|
+
{ code: '<div aria-invalid="false" />' },
|
|
158
|
+
{ code: '<div aria-invalid="grammar" />' },
|
|
159
|
+
{ code: '<div aria-invalid="spelling" />' },
|
|
160
|
+
{ code: '<div aria-invalid={null} />' },
|
|
161
|
+
{ code: '<div aria-invalid={undefined} />' },
|
|
162
|
+
|
|
163
|
+
// TOKENLIST
|
|
164
|
+
{ code: '<div aria-relevant="additions" />' },
|
|
165
|
+
{ code: '<div aria-relevant={"additions"} />' },
|
|
166
|
+
{ code: '<div aria-relevant={`additions`} />' },
|
|
167
|
+
{ code: '<div aria-relevant="additions removals" />' },
|
|
168
|
+
{ code: '<div aria-relevant="additions additions" />' },
|
|
169
|
+
{ code: '<div aria-relevant={"additions removals"} />' },
|
|
170
|
+
{ code: '<div aria-relevant={`additions removals`} />' },
|
|
171
|
+
{ code: '<div aria-relevant="additions removals text" />' },
|
|
172
|
+
{ code: '<div aria-relevant={"additions removals text"} />' },
|
|
173
|
+
{ code: '<div aria-relevant={`additions removals text`} />' },
|
|
174
|
+
{ code: '<div aria-relevant="additions removals text all" />' },
|
|
175
|
+
{ code: '<div aria-relevant={"additions removals text all"} />' },
|
|
176
|
+
{ code: '<div aria-relevant={`removals additions text all`} />' },
|
|
177
|
+
{ code: '<div aria-relevant={foo} />' },
|
|
178
|
+
{ code: '<div aria-relevant={foo.bar} />' },
|
|
179
|
+
{ code: '<div aria-relevant={null} />' },
|
|
180
|
+
{ code: '<div aria-relevant={undefined} />' },
|
|
181
|
+
|
|
182
|
+
// ID
|
|
183
|
+
{ code: '<div aria-activedescendant="ascending" />' },
|
|
184
|
+
{ code: '<div aria-activedescendant="ASCENDING" />' },
|
|
185
|
+
{ code: '<div aria-activedescendant={"ascending"} />' },
|
|
186
|
+
{ code: '<div aria-activedescendant={`ascending`} />' },
|
|
187
|
+
{ code: '<div aria-activedescendant="descending" />' },
|
|
188
|
+
{ code: '<div aria-activedescendant={"descending"} />' },
|
|
189
|
+
{ code: '<div aria-activedescendant={`descending`} />' },
|
|
190
|
+
{ code: '<div aria-activedescendant="none" />' },
|
|
191
|
+
{ code: '<div aria-activedescendant={"none"} />' },
|
|
192
|
+
{ code: '<div aria-activedescendant={`none`} />' },
|
|
193
|
+
{ code: '<div aria-activedescendant="other" />' },
|
|
194
|
+
{ code: '<div aria-activedescendant={"other"} />' },
|
|
195
|
+
{ code: '<div aria-activedescendant={`other`} />' },
|
|
196
|
+
{ code: '<div aria-activedescendant={foo} />' },
|
|
197
|
+
{ code: '<div aria-activedescendant={foo.bar} />' },
|
|
198
|
+
{ code: '<div aria-activedescendant={null} />' },
|
|
199
|
+
{ code: '<div aria-activedescendant={undefined} />' },
|
|
200
|
+
|
|
201
|
+
// IDLIST
|
|
202
|
+
{ code: '<div aria-labelledby="additions" />' },
|
|
203
|
+
{ code: '<div aria-labelledby={"additions"} />' },
|
|
204
|
+
{ code: '<div aria-labelledby={`additions`} />' },
|
|
205
|
+
{ code: '<div aria-labelledby="additions removals" />' },
|
|
206
|
+
{ code: '<div aria-labelledby="additions additions" />' },
|
|
207
|
+
{ code: '<div aria-labelledby={"additions removals"} />' },
|
|
208
|
+
{ code: '<div aria-labelledby={`additions removals`} />' },
|
|
209
|
+
{ code: '<div aria-labelledby="additions removals text" />' },
|
|
210
|
+
{ code: '<div aria-labelledby={"additions removals text"} />' },
|
|
211
|
+
{ code: '<div aria-labelledby={`additions removals text`} />' },
|
|
212
|
+
{ code: '<div aria-labelledby="additions removals text all" />' },
|
|
213
|
+
{ code: '<div aria-labelledby={"additions removals text all"} />' },
|
|
214
|
+
{ code: '<div aria-labelledby={`removals additions text all`} />' },
|
|
215
|
+
{ code: '<div aria-labelledby={foo} />' },
|
|
216
|
+
{ code: '<div aria-labelledby={foo.bar} />' },
|
|
217
|
+
{ code: '<div aria-labelledby={null} />' },
|
|
218
|
+
{ code: '<div aria-labelledby={undefined} />' },
|
|
219
|
+
)).map(parserOptionsMapper),
|
|
220
|
+
invalid: parsers.all([].concat(
|
|
221
|
+
// BOOLEAN
|
|
222
|
+
{ code: '<div aria-hidden="yes" />', errors: [errorMessage('aria-hidden')] },
|
|
223
|
+
{ code: '<div aria-hidden="no" />', errors: [errorMessage('aria-hidden')] },
|
|
224
|
+
{ code: '<div aria-hidden={1234} />', errors: [errorMessage('aria-hidden')] },
|
|
225
|
+
{
|
|
226
|
+
code: '<div aria-hidden={`${abc}`} />',
|
|
227
|
+
errors: [errorMessage('aria-hidden')],
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// STRING
|
|
231
|
+
{ code: '<div aria-label />', errors: [errorMessage('aria-label')] },
|
|
232
|
+
{ code: '<div aria-label={true} />', errors: [errorMessage('aria-label')] },
|
|
233
|
+
{ code: '<div aria-label={false} />', errors: [errorMessage('aria-label')] },
|
|
234
|
+
{ code: '<div aria-label={1234} />', errors: [errorMessage('aria-label')] },
|
|
235
|
+
{ code: '<div aria-label={!true} />', errors: [errorMessage('aria-label')] },
|
|
236
|
+
|
|
237
|
+
// TRISTATE
|
|
238
|
+
{ code: '<div aria-checked="yes" />', errors: [errorMessage('aria-checked')] },
|
|
239
|
+
{ code: '<div aria-checked="no" />', errors: [errorMessage('aria-checked')] },
|
|
240
|
+
{ code: '<div aria-checked={1234} />', errors: [errorMessage('aria-checked')] },
|
|
241
|
+
{
|
|
242
|
+
code: '<div aria-checked={`${abc}`} />',
|
|
243
|
+
errors: [errorMessage('aria-checked')],
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// INTEGER
|
|
247
|
+
{ code: '<div aria-level="yes" />', errors: [errorMessage('aria-level')] },
|
|
248
|
+
{ code: '<div aria-level="no" />', errors: [errorMessage('aria-level')] },
|
|
249
|
+
{ code: '<div aria-level={`abc`} />', errors: [errorMessage('aria-level')] },
|
|
250
|
+
{ code: '<div aria-level={true} />', errors: [errorMessage('aria-level')] },
|
|
251
|
+
{ code: '<div aria-level />', errors: [errorMessage('aria-level')] },
|
|
252
|
+
{ code: '<div aria-level={"false"} />', errors: [errorMessage('aria-level')] },
|
|
253
|
+
{ code: '<div aria-level={!"false"} />', errors: [errorMessage('aria-level')] },
|
|
254
|
+
|
|
255
|
+
// NUMBER
|
|
256
|
+
{ code: '<div aria-valuemax="yes" />', errors: [errorMessage('aria-valuemax')] },
|
|
257
|
+
{ code: '<div aria-valuemax="no" />', errors: [errorMessage('aria-valuemax')] },
|
|
258
|
+
{
|
|
259
|
+
code: '<div aria-valuemax={`abc`} />',
|
|
260
|
+
errors: [errorMessage('aria-valuemax')],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
code: '<div aria-valuemax={true} />',
|
|
264
|
+
errors: [errorMessage('aria-valuemax')],
|
|
265
|
+
},
|
|
266
|
+
{ code: '<div aria-valuemax />', errors: [errorMessage('aria-valuemax')] },
|
|
267
|
+
{
|
|
268
|
+
code: '<div aria-valuemax={"false"} />',
|
|
269
|
+
errors: [errorMessage('aria-valuemax')],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
code: '<div aria-valuemax={!"false"} />',
|
|
273
|
+
errors: [errorMessage('aria-valuemax')],
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// TOKEN
|
|
277
|
+
{ code: '<div aria-sort="" />', errors: [errorMessage('aria-sort')] },
|
|
278
|
+
{ code: '<div aria-sort="descnding" />', errors: [errorMessage('aria-sort')] },
|
|
279
|
+
{ code: '<div aria-sort />', errors: [errorMessage('aria-sort')] },
|
|
280
|
+
{ code: '<div aria-sort={true} />', errors: [errorMessage('aria-sort')] },
|
|
281
|
+
{ code: '<div aria-sort={"false"} />', errors: [errorMessage('aria-sort')] },
|
|
282
|
+
{
|
|
283
|
+
code: '<div aria-sort="ascending descending" />',
|
|
284
|
+
errors: [errorMessage('aria-sort')],
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// TOKENLIST
|
|
288
|
+
{ code: '<div aria-relevant="" />', errors: [errorMessage('aria-relevant')] },
|
|
289
|
+
{
|
|
290
|
+
code: '<div aria-relevant="foobar" />',
|
|
291
|
+
errors: [errorMessage('aria-relevant')],
|
|
292
|
+
},
|
|
293
|
+
{ code: '<div aria-relevant />', errors: [errorMessage('aria-relevant')] },
|
|
294
|
+
{
|
|
295
|
+
code: '<div aria-relevant={true} />',
|
|
296
|
+
errors: [errorMessage('aria-relevant')],
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
code: '<div aria-relevant={"false"} />',
|
|
300
|
+
errors: [errorMessage('aria-relevant')],
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
code: '<div aria-relevant="additions removalss" />',
|
|
304
|
+
errors: [errorMessage('aria-relevant')],
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
code: '<div aria-relevant="additions removalss " />',
|
|
308
|
+
errors: [errorMessage('aria-relevant')],
|
|
309
|
+
},
|
|
310
|
+
)).map(parserOptionsMapper),
|
|
311
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce aria role attribute is valid.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { roles } from 'aria-query';
|
|
11
|
+
import { RuleTester } from 'eslint';
|
|
12
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
13
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
14
|
+
import rule from '../../../src/rules/aria-role';
|
|
15
|
+
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
// Tests
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const ruleTester = new RuleTester();
|
|
21
|
+
|
|
22
|
+
const errorMessage = {
|
|
23
|
+
message: 'Elements with ARIA roles must use a valid, non-abstract ARIA role.',
|
|
24
|
+
type: 'JSXAttribute',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const roleKeys = roles.keys();
|
|
28
|
+
|
|
29
|
+
const validRoles = roleKeys.filter((role) => roles.get(role).abstract === false);
|
|
30
|
+
const invalidRoles = roleKeys.filter((role) => roles.get(role).abstract === true);
|
|
31
|
+
|
|
32
|
+
const createTests = (roleNames) => roleNames.map((role) => ({
|
|
33
|
+
code: `<div role="${role.toLowerCase()}" />`,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const validTests = createTests(validRoles);
|
|
37
|
+
const invalidTests = createTests(invalidRoles).map((test) => {
|
|
38
|
+
const invalidTest = { ...test };
|
|
39
|
+
invalidTest.errors = [errorMessage];
|
|
40
|
+
return invalidTest;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const allowedInvalidRoles = [{
|
|
44
|
+
allowedInvalidRoles: ['invalid-role', 'other-invalid-role'],
|
|
45
|
+
}];
|
|
46
|
+
|
|
47
|
+
const ignoreNonDOMSchema = [{
|
|
48
|
+
ignoreNonDOM: true,
|
|
49
|
+
}];
|
|
50
|
+
|
|
51
|
+
const customDivSettings = {
|
|
52
|
+
'jsx-a11y': {
|
|
53
|
+
polymorphicPropName: 'asChild',
|
|
54
|
+
components: {
|
|
55
|
+
Div: 'div',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
ruleTester.run('aria-role', rule, {
|
|
61
|
+
valid: parsers.all([].concat(
|
|
62
|
+
// Variables should pass, as we are only testing literals.
|
|
63
|
+
{ code: '<div />' },
|
|
64
|
+
{ code: '<div></div>' },
|
|
65
|
+
{ code: '<div role={role} />' },
|
|
66
|
+
{ code: '<div role={role || "button"} />' },
|
|
67
|
+
{ code: '<div role={role || "foobar"} />' },
|
|
68
|
+
{ code: '<div role="tabpanel row" />' },
|
|
69
|
+
{ code: '<div role="switch" />' },
|
|
70
|
+
{ code: '<div role="doc-abstract" />' },
|
|
71
|
+
{ code: '<div role="doc-appendix doc-bibliography" />' },
|
|
72
|
+
{ code: '<Bar baz />' },
|
|
73
|
+
{ code: '<img role="invalid-role" />', options: allowedInvalidRoles },
|
|
74
|
+
{ code: '<img role="invalid-role tabpanel" />', options: allowedInvalidRoles },
|
|
75
|
+
{ code: '<img role="invalid-role other-invalid-role" />', options: allowedInvalidRoles },
|
|
76
|
+
{ code: '<Foo role="bar" />', options: ignoreNonDOMSchema },
|
|
77
|
+
{ code: '<fakeDOM role="bar" />', options: ignoreNonDOMSchema },
|
|
78
|
+
{ code: '<img role="presentation" />', options: ignoreNonDOMSchema },
|
|
79
|
+
{
|
|
80
|
+
code: '<Div role="button" />',
|
|
81
|
+
settings: customDivSettings,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: '<Box asChild="div" role="button" />',
|
|
85
|
+
settings: customDivSettings,
|
|
86
|
+
},
|
|
87
|
+
{ code: '<svg role="graphics-document document" />' },
|
|
88
|
+
{ code: '<svg role="img" />' },
|
|
89
|
+
)).concat(validTests).map(parserOptionsMapper),
|
|
90
|
+
|
|
91
|
+
invalid: parsers.all([].concat(
|
|
92
|
+
{ code: '<div role="foobar" />', errors: [errorMessage] },
|
|
93
|
+
{ code: '<div role="datepicker"></div>', errors: [errorMessage] },
|
|
94
|
+
{ code: '<div role="range"></div>', errors: [errorMessage] },
|
|
95
|
+
{ code: '<div role="Button"></div>', errors: [errorMessage] },
|
|
96
|
+
{ code: '<div role=""></div>', errors: [errorMessage] },
|
|
97
|
+
{ code: '<div role="tabpanel row foobar"></div>', errors: [errorMessage] },
|
|
98
|
+
{ code: '<div role="tabpanel row range"></div>', errors: [errorMessage] },
|
|
99
|
+
{ code: '<div role="doc-endnotes range"></div>', errors: [errorMessage] },
|
|
100
|
+
{ code: '<div role />', errors: [errorMessage] },
|
|
101
|
+
{ code: '<div role="unknown-invalid-role" />', errors: [errorMessage], options: allowedInvalidRoles },
|
|
102
|
+
{ code: '<div role={null}></div>', errors: [errorMessage] },
|
|
103
|
+
{ code: '<Foo role="datepicker" />', errors: [errorMessage] },
|
|
104
|
+
{ code: '<Foo role="Button" />', errors: [errorMessage] },
|
|
105
|
+
{ code: '<Div role="Button" />', errors: [errorMessage], settings: customDivSettings },
|
|
106
|
+
{
|
|
107
|
+
code: '<Div role="Button" />',
|
|
108
|
+
errors: [errorMessage],
|
|
109
|
+
options: ignoreNonDOMSchema,
|
|
110
|
+
settings: customDivSettings,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
code: '<Box asChild="div" role="Button" />',
|
|
114
|
+
settings: customDivSettings,
|
|
115
|
+
errors: [errorMessage],
|
|
116
|
+
},
|
|
117
|
+
)).concat(invalidTests).map(parserOptionsMapper),
|
|
118
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that elements that do not support ARIA roles,
|
|
3
|
+
* states and properties do not have those attributes.
|
|
4
|
+
* @author Ethan Cohen
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// -----------------------------------------------------------------------------
|
|
8
|
+
// Requirements
|
|
9
|
+
// -----------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import { dom } from 'aria-query';
|
|
12
|
+
import { RuleTester } from 'eslint';
|
|
13
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
14
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
15
|
+
import rule from '../../../src/rules/aria-unsupported-elements';
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Tests
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const ruleTester = new RuleTester();
|
|
22
|
+
|
|
23
|
+
const errorMessage = (invalidProp) => ({
|
|
24
|
+
message: `This element does not support ARIA roles, states and properties. \
|
|
25
|
+
Try removing the prop '${invalidProp}'.`,
|
|
26
|
+
type: 'JSXOpeningElement',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const domElements = dom.keys();
|
|
30
|
+
// Generate valid test cases
|
|
31
|
+
const roleValidityTests = domElements.map((element) => {
|
|
32
|
+
const isReserved = dom.get(element).reserved || false;
|
|
33
|
+
const role = isReserved ? '' : 'role';
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
code: `<${element} ${role} />`,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const ariaValidityTests = domElements.map((element) => {
|
|
41
|
+
const isReserved = dom.get(element).reserved || false;
|
|
42
|
+
const aria = isReserved ? '' : 'aria-hidden';
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
code: `<${element} ${aria} />`,
|
|
46
|
+
};
|
|
47
|
+
}).concat({
|
|
48
|
+
code: '<fake aria-hidden />',
|
|
49
|
+
errors: [errorMessage('aria-hidden')],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Generate invalid test cases.
|
|
53
|
+
const invalidRoleValidityTests = domElements
|
|
54
|
+
.filter((element) => dom.get(element).reserved)
|
|
55
|
+
.map((reservedElem) => ({
|
|
56
|
+
code: `<${reservedElem} role {...props} />`,
|
|
57
|
+
errors: [errorMessage('role')],
|
|
58
|
+
})).concat({
|
|
59
|
+
code: '<Meta aria-hidden />',
|
|
60
|
+
errors: [errorMessage('aria-hidden')],
|
|
61
|
+
settings: { 'jsx-a11y': { components: { Meta: 'meta' } } },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const invalidAriaValidityTests = domElements
|
|
65
|
+
.filter((element) => dom.get(element).reserved)
|
|
66
|
+
.map((reservedElem) => ({
|
|
67
|
+
code: `<${reservedElem} aria-hidden aria-role="none" {...props} />`,
|
|
68
|
+
errors: [errorMessage('aria-hidden')],
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
ruleTester.run('aria-unsupported-elements', rule, {
|
|
72
|
+
valid: parsers.all([].concat(roleValidityTests, ariaValidityTests)).map(parserOptionsMapper),
|
|
73
|
+
invalid: parsers.all([].concat(invalidRoleValidityTests, invalidAriaValidityTests))
|
|
74
|
+
.map(parserOptionsMapper),
|
|
75
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ensure autocomplete attribute is correct.
|
|
3
|
+
* @author Wilco Fiers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
12
|
+
import { axeFailMessage } from '../../__util__/axeMapping';
|
|
13
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
14
|
+
import rule from '../../../src/rules/autocomplete-valid';
|
|
15
|
+
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
// Tests
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const ruleTester = new RuleTester();
|
|
21
|
+
|
|
22
|
+
const invalidAutocomplete = [{
|
|
23
|
+
message: axeFailMessage('autocomplete-valid'),
|
|
24
|
+
type: 'JSXOpeningElement',
|
|
25
|
+
}];
|
|
26
|
+
|
|
27
|
+
const inappropriateAutocomplete = [{
|
|
28
|
+
message: axeFailMessage('autocomplete-appropriate'),
|
|
29
|
+
type: 'JSXOpeningElement',
|
|
30
|
+
}];
|
|
31
|
+
|
|
32
|
+
const componentsSettings = {
|
|
33
|
+
'jsx-a11y': {
|
|
34
|
+
components: {
|
|
35
|
+
Input: 'input',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
ruleTester.run('autocomplete-valid', rule, {
|
|
41
|
+
valid: parsers.all([].concat(
|
|
42
|
+
// INAPPLICABLE
|
|
43
|
+
{ code: '<input type="text" />;' },
|
|
44
|
+
// // PASSED AUTOCOMPLETE
|
|
45
|
+
{ code: '<input type="text" autocomplete="name" />;' },
|
|
46
|
+
{ code: '<input type="text" autocomplete="" />;' },
|
|
47
|
+
{ code: '<input type="text" autocomplete="off" />;' },
|
|
48
|
+
{ code: '<input type="text" autocomplete="on" />;' },
|
|
49
|
+
{ code: '<input type="text" autocomplete="billing family-name" />;' },
|
|
50
|
+
{ code: '<input type="text" autocomplete="section-blue shipping street-address" />;' },
|
|
51
|
+
{ code: '<input type="text" autocomplete="section-somewhere shipping work email" />;' },
|
|
52
|
+
{ code: '<input type="text" autocomplete />;' },
|
|
53
|
+
{ code: '<input type="text" autocomplete={autocompl} />;' },
|
|
54
|
+
{ code: '<input type="text" autocomplete={autocompl || "name"} />;' },
|
|
55
|
+
{ code: '<input type="text" autocomplete={autocompl || "foo"} />;' },
|
|
56
|
+
{ code: '<Foo autocomplete="bar"></Foo>;' },
|
|
57
|
+
{ code: '<input type={isEmail ? "email" : "text"} autocomplete="none" />;' },
|
|
58
|
+
{ code: '<Input type="text" autocomplete="name" />', settings: componentsSettings },
|
|
59
|
+
{ code: '<Input type="text" autocomplete="baz" />' },
|
|
60
|
+
|
|
61
|
+
// PASSED "autocomplete-appropriate"
|
|
62
|
+
// see also: https://github.com/dequelabs/axe-core/issues/2912
|
|
63
|
+
{ code: '<input type="date" autocomplete="email" />;', errors: inappropriateAutocomplete },
|
|
64
|
+
{ code: '<input type="number" autocomplete="url" />;', errors: inappropriateAutocomplete },
|
|
65
|
+
{ code: '<input type="month" autocomplete="tel" />;', errors: inappropriateAutocomplete },
|
|
66
|
+
{ code: '<Foo type="month" autocomplete="tel"></Foo>;', errors: inappropriateAutocomplete, options: [{ inputComponents: ['Foo'] }] },
|
|
67
|
+
)).map(parserOptionsMapper),
|
|
68
|
+
invalid: parsers.all([].concat(
|
|
69
|
+
// FAILED "autocomplete-valid"
|
|
70
|
+
{ code: '<input type="text" autocomplete="foo" />;', errors: invalidAutocomplete },
|
|
71
|
+
{ code: '<input type="text" autocomplete="name invalid" />;', errors: invalidAutocomplete },
|
|
72
|
+
{ code: '<input type="text" autocomplete="invalid name" />;', errors: invalidAutocomplete },
|
|
73
|
+
{ code: '<input type="text" autocomplete="home url" />;', errors: invalidAutocomplete },
|
|
74
|
+
{ code: '<Bar autocomplete="baz"></Bar>;', errors: invalidAutocomplete, options: [{ inputComponents: ['Bar'] }] },
|
|
75
|
+
{ code: '<Input type="text" autocomplete="baz" />', errors: invalidAutocomplete, settings: componentsSettings },
|
|
76
|
+
)).map(parserOptionsMapper),
|
|
77
|
+
});
|