@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,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce a clickable non-interactive element has at least 1 keyboard event listener.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
12
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
13
|
+
import rule from '../../../src/rules/click-events-have-key-events';
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Tests
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const ruleTester = new RuleTester();
|
|
20
|
+
|
|
21
|
+
const errorMessage = 'Visible, non-interactive elements with click handlers must have at least one keyboard listener.';
|
|
22
|
+
|
|
23
|
+
const expectedError = {
|
|
24
|
+
message: errorMessage,
|
|
25
|
+
type: 'JSXOpeningElement',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
ruleTester.run('click-events-have-key-events', rule, {
|
|
29
|
+
valid: parsers.all([].concat(
|
|
30
|
+
{ code: '<div onClick={() => void 0} onKeyDown={foo}/>;' },
|
|
31
|
+
{ code: '<div onClick={() => void 0} onKeyUp={foo} />;' },
|
|
32
|
+
{ code: '<div onClick={() => void 0} onKeyPress={foo}/>;' },
|
|
33
|
+
{ code: '<div onClick={() => void 0} onKeyDown={foo} onKeyUp={bar} />;' },
|
|
34
|
+
{ code: '<div onClick={() => void 0} onKeyDown={foo} {...props} />;' },
|
|
35
|
+
{ code: '<div className="foo" />;' },
|
|
36
|
+
{ code: '<div onClick={() => void 0} aria-hidden />;' },
|
|
37
|
+
{ code: '<div onClick={() => void 0} aria-hidden={true} />;' },
|
|
38
|
+
{ code: '<div onClick={() => void 0} aria-hidden={false} onKeyDown={foo} />;' },
|
|
39
|
+
{
|
|
40
|
+
code: '<div onClick={() => void 0} onKeyDown={foo} aria-hidden={undefined} />;',
|
|
41
|
+
},
|
|
42
|
+
{ code: '<input type="text" onClick={() => void 0} />' },
|
|
43
|
+
{ code: '<input onClick={() => void 0} />' },
|
|
44
|
+
{ code: '<button onClick={() => void 0} className="foo" />' },
|
|
45
|
+
{ code: '<option onClick={() => void 0} className="foo" />' },
|
|
46
|
+
{ code: '<select onClick={() => void 0} className="foo" />' },
|
|
47
|
+
{ code: '<textarea onClick={() => void 0} className="foo" />' },
|
|
48
|
+
{ code: '<a onClick={() => void 0} href="http://x.y.z" />' },
|
|
49
|
+
{ code: '<a onClick={() => void 0} href="http://x.y.z" tabIndex="0" />' },
|
|
50
|
+
{ code: '<input onClick={() => void 0} type="hidden" />;' },
|
|
51
|
+
{ code: '<div onClick={() => void 0} role="presentation" />;' },
|
|
52
|
+
{ code: '<div onClick={() => void 0} role="none" />;' },
|
|
53
|
+
{ code: '<TestComponent onClick={doFoo} />' },
|
|
54
|
+
{ code: '<Button onClick={doFoo} />' },
|
|
55
|
+
{ code: '<Footer onClick={doFoo} />' },
|
|
56
|
+
)).map(parserOptionsMapper),
|
|
57
|
+
invalid: parsers.all([].concat(
|
|
58
|
+
{ code: '<div onClick={() => void 0} />;', errors: [expectedError] },
|
|
59
|
+
{
|
|
60
|
+
code: '<div onClick={() => void 0} role={undefined} />;',
|
|
61
|
+
errors: [expectedError],
|
|
62
|
+
},
|
|
63
|
+
{ code: '<div onClick={() => void 0} {...props} />;', errors: [expectedError] },
|
|
64
|
+
{ code: '<section onClick={() => void 0} />;', errors: [expectedError] },
|
|
65
|
+
{ code: '<main onClick={() => void 0} />;', errors: [expectedError] },
|
|
66
|
+
{ code: '<article onClick={() => void 0} />;', errors: [expectedError] },
|
|
67
|
+
{ code: '<header onClick={() => void 0} />;', errors: [expectedError] },
|
|
68
|
+
{ code: '<footer onClick={() => void 0} />;', errors: [expectedError] },
|
|
69
|
+
{
|
|
70
|
+
code: '<div onClick={() => void 0} aria-hidden={false} />;',
|
|
71
|
+
errors: [expectedError],
|
|
72
|
+
},
|
|
73
|
+
{ code: '<a onClick={() => void 0} />', errors: [expectedError] },
|
|
74
|
+
{ code: '<a tabIndex="0" onClick={() => void 0} />', errors: [expectedError] },
|
|
75
|
+
{ code: '<Footer onClick={doFoo} />', errors: [expectedError], settings: { 'jsx-a11y': { components: { Footer: 'footer' } } } },
|
|
76
|
+
)).map(parserOptionsMapper),
|
|
77
|
+
});
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Control elements must be associated with a text label
|
|
3
|
+
* @author jessebeach
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import { configs } from '../../../src/index';
|
|
12
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
13
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
14
|
+
import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
|
|
15
|
+
import rule from '../../../src/rules/control-has-associated-label';
|
|
16
|
+
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Tests
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const ruleTester = new RuleTester();
|
|
22
|
+
|
|
23
|
+
const ruleName = 'jsx-a11y/control-has-associated-label';
|
|
24
|
+
|
|
25
|
+
const expectedError = {
|
|
26
|
+
message: 'A control must be associated with a text label.',
|
|
27
|
+
type: 'JSXOpeningElement',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const alwaysValid = [
|
|
31
|
+
// Custom Control Components
|
|
32
|
+
{ code: '<CustomControl><span><span>Save</span></span></CustomControl>', options: [{ depth: 3, controlComponents: ['CustomControl'] }] },
|
|
33
|
+
{ code: '<CustomControl><span><span label="Save"></span></span></CustomControl>', options: [{ depth: 3, controlComponents: ['CustomControl'], labelAttributes: ['label'] }] },
|
|
34
|
+
{ code: '<CustomControl>Save</CustomControl>', settings: { 'jsx-a11y': { components: { CustomControl: 'button' } } } },
|
|
35
|
+
// Interactive Elements
|
|
36
|
+
{ code: '<button>Save</button>' },
|
|
37
|
+
{ code: '<button><span>Save</span></button>' },
|
|
38
|
+
{ code: '<button><span><span>Save</span></span></button>', options: [{ depth: 3 }] },
|
|
39
|
+
{ code: '<button><span><span><span><span><span><span><span><span>Save</span></span></span></span></span></span></span></span></button>', options: [{ depth: 9 }] },
|
|
40
|
+
{ code: '<button><img alt="Save" /></button>' },
|
|
41
|
+
{ code: '<button aria-label="Save" />' },
|
|
42
|
+
{ code: '<button><span aria-label="Save" /></button>' },
|
|
43
|
+
{ code: '<button aria-labelledby="js_1" />' },
|
|
44
|
+
{ code: '<button><span aria-labelledby="js_1" /></button>' },
|
|
45
|
+
{ code: '<button>{sureWhyNot}</button>' },
|
|
46
|
+
{ code: '<button><span><span label="Save"></span></span></button>', options: [{ depth: 3, labelAttributes: ['label'] }] },
|
|
47
|
+
{ code: '<a href="#">Save</a>' },
|
|
48
|
+
{ code: '<area href="#">Save</area>' },
|
|
49
|
+
{ code: '<link>Save</link>' },
|
|
50
|
+
{ code: '<menuitem>Save</menuitem>' },
|
|
51
|
+
{ code: '<option>Save</option>' },
|
|
52
|
+
{ code: '<th>Save</th>' },
|
|
53
|
+
// Interactive Roles
|
|
54
|
+
{ code: '<div role="button">Save</div>' },
|
|
55
|
+
{ code: '<div role="checkbox">Save</div>' },
|
|
56
|
+
{ code: '<div role="columnheader">Save</div>' },
|
|
57
|
+
{ code: '<div role="combobox">Save</div>' },
|
|
58
|
+
{ code: '<div role="gridcell">Save</div>' },
|
|
59
|
+
{ code: '<div role="link">Save</div>' },
|
|
60
|
+
{ code: '<div role="menuitem">Save</div>' },
|
|
61
|
+
{ code: '<div role="menuitemcheckbox">Save</div>' },
|
|
62
|
+
{ code: '<div role="menuitemradio">Save</div>' },
|
|
63
|
+
{ code: '<div role="option">Save</div>' },
|
|
64
|
+
{ code: '<div role="progressbar">Save</div>' },
|
|
65
|
+
{ code: '<div role="radio">Save</div>' },
|
|
66
|
+
{ code: '<div role="rowheader">Save</div>' },
|
|
67
|
+
{ code: '<div role="searchbox">Save</div>' },
|
|
68
|
+
{ code: '<div role="slider">Save</div>' },
|
|
69
|
+
{ code: '<div role="spinbutton">Save</div>' },
|
|
70
|
+
{ code: '<div role="switch">Save</div>' },
|
|
71
|
+
{ code: '<div role="tab">Save</div>' },
|
|
72
|
+
{ code: '<div role="textbox">Save</div>' },
|
|
73
|
+
{ code: '<div role="treeitem">Save</div>' },
|
|
74
|
+
{ code: '<div role="button" aria-label="Save" />' },
|
|
75
|
+
{ code: '<div role="checkbox" aria-label="Save" />' },
|
|
76
|
+
{ code: '<div role="columnheader" aria-label="Save" />' },
|
|
77
|
+
{ code: '<div role="combobox" aria-label="Save" />' },
|
|
78
|
+
{ code: '<div role="gridcell" aria-label="Save" />' },
|
|
79
|
+
{ code: '<div role="link" aria-label="Save" />' },
|
|
80
|
+
{ code: '<div role="menuitem" aria-label="Save" />' },
|
|
81
|
+
{ code: '<div role="menuitemcheckbox" aria-label="Save" />' },
|
|
82
|
+
{ code: '<div role="menuitemradio" aria-label="Save" />' },
|
|
83
|
+
{ code: '<div role="option" aria-label="Save" />' },
|
|
84
|
+
{ code: '<div role="progressbar" aria-label="Save" />' },
|
|
85
|
+
{ code: '<div role="radio" aria-label="Save" />' },
|
|
86
|
+
{ code: '<div role="rowheader" aria-label="Save" />' },
|
|
87
|
+
{ code: '<div role="searchbox" aria-label="Save" />' },
|
|
88
|
+
{ code: '<div role="slider" aria-label="Save" />' },
|
|
89
|
+
{ code: '<div role="spinbutton" aria-label="Save" />' },
|
|
90
|
+
{ code: '<div role="switch" aria-label="Save" />' },
|
|
91
|
+
{ code: '<div role="tab" aria-label="Save" />' },
|
|
92
|
+
{ code: '<div role="textbox" aria-label="Save" />' },
|
|
93
|
+
{ code: '<div role="treeitem" aria-label="Save" />' },
|
|
94
|
+
{ code: '<div role="button" aria-labelledby="js_1" />' },
|
|
95
|
+
{ code: '<div role="checkbox" aria-labelledby="js_1" />' },
|
|
96
|
+
{ code: '<div role="columnheader" aria-labelledby="js_1" />' },
|
|
97
|
+
{ code: '<div role="combobox" aria-labelledby="js_1" />' },
|
|
98
|
+
{ code: '<div role="gridcell" aria-labelledby="Save" />' },
|
|
99
|
+
{ code: '<div role="link" aria-labelledby="js_1" />' },
|
|
100
|
+
{ code: '<div role="menuitem" aria-labelledby="js_1" />' },
|
|
101
|
+
{ code: '<div role="menuitemcheckbox" aria-labelledby="js_1" />' },
|
|
102
|
+
{ code: '<div role="menuitemradio" aria-labelledby="js_1" />' },
|
|
103
|
+
{ code: '<div role="option" aria-labelledby="js_1" />' },
|
|
104
|
+
{ code: '<div role="progressbar" aria-labelledby="js_1" />' },
|
|
105
|
+
{ code: '<div role="radio" aria-labelledby="js_1" />' },
|
|
106
|
+
{ code: '<div role="rowheader" aria-labelledby="js_1" />' },
|
|
107
|
+
{ code: '<div role="searchbox" aria-labelledby="js_1" />' },
|
|
108
|
+
{ code: '<div role="slider" aria-labelledby="js_1" />' },
|
|
109
|
+
{ code: '<div role="spinbutton" aria-labelledby="js_1" />' },
|
|
110
|
+
{ code: '<div role="switch" aria-labelledby="js_1" />' },
|
|
111
|
+
{ code: '<div role="tab" aria-labelledby="js_1" />' },
|
|
112
|
+
{ code: '<div role="textbox" aria-labelledby="js_1" />' },
|
|
113
|
+
{ code: '<div role="treeitem" aria-labelledby="js_1" />' },
|
|
114
|
+
// Non-interactive Elements
|
|
115
|
+
{ code: '<abbr />' },
|
|
116
|
+
{ code: '<article />' },
|
|
117
|
+
{ code: '<blockquote />' },
|
|
118
|
+
{ code: '<br />' },
|
|
119
|
+
{ code: '<caption />' },
|
|
120
|
+
{ code: '<dd />' },
|
|
121
|
+
{ code: '<details />' },
|
|
122
|
+
{ code: '<dfn />' },
|
|
123
|
+
{ code: '<dialog />' },
|
|
124
|
+
{ code: '<dir />' },
|
|
125
|
+
{ code: '<dl />' },
|
|
126
|
+
{ code: '<dt />' },
|
|
127
|
+
{ code: '<fieldset />' },
|
|
128
|
+
{ code: '<figcaption />' },
|
|
129
|
+
{ code: '<figure />' },
|
|
130
|
+
{ code: '<footer />' },
|
|
131
|
+
{ code: '<form />' },
|
|
132
|
+
{ code: '<frame />' },
|
|
133
|
+
{ code: '<h1 />' },
|
|
134
|
+
{ code: '<h2 />' },
|
|
135
|
+
{ code: '<h3 />' },
|
|
136
|
+
{ code: '<h4 />' },
|
|
137
|
+
{ code: '<h5 />' },
|
|
138
|
+
{ code: '<h6 />' },
|
|
139
|
+
{ code: '<hr />' },
|
|
140
|
+
{ code: '<iframe />' },
|
|
141
|
+
{ code: '<img />' },
|
|
142
|
+
{ code: '<label />' },
|
|
143
|
+
{ code: '<legend />' },
|
|
144
|
+
{ code: '<li />' },
|
|
145
|
+
{ code: '<link />' },
|
|
146
|
+
{ code: '<main />' },
|
|
147
|
+
{ code: '<mark />' },
|
|
148
|
+
{ code: '<marquee />' },
|
|
149
|
+
{ code: '<menu />' },
|
|
150
|
+
{ code: '<meter />' },
|
|
151
|
+
{ code: '<nav />' },
|
|
152
|
+
{ code: '<ol />' },
|
|
153
|
+
{ code: '<p />' },
|
|
154
|
+
{ code: '<pre />' },
|
|
155
|
+
{ code: '<progress />' },
|
|
156
|
+
{ code: '<ruby />' },
|
|
157
|
+
{ code: '<section />' },
|
|
158
|
+
{ code: '<table />' },
|
|
159
|
+
{ code: '<tbody />' },
|
|
160
|
+
{ code: '<tfoot />' },
|
|
161
|
+
{ code: '<thead />' },
|
|
162
|
+
{ code: '<time />' },
|
|
163
|
+
{ code: '<ul />' },
|
|
164
|
+
// Non-interactive Roles
|
|
165
|
+
{ code: '<div role="alert" />' },
|
|
166
|
+
{ code: '<div role="alertdialog" />' },
|
|
167
|
+
{ code: '<div role="application" />' },
|
|
168
|
+
{ code: '<div role="article" />' },
|
|
169
|
+
{ code: '<div role="banner" />' },
|
|
170
|
+
{ code: '<div role="cell" />' },
|
|
171
|
+
{ code: '<div role="complementary" />' },
|
|
172
|
+
{ code: '<div role="contentinfo" />' },
|
|
173
|
+
{ code: '<div role="definition" />' },
|
|
174
|
+
{ code: '<div role="dialog" />' },
|
|
175
|
+
{ code: '<div role="directory" />' },
|
|
176
|
+
{ code: '<div role="document" />' },
|
|
177
|
+
{ code: '<div role="feed" />' },
|
|
178
|
+
{ code: '<div role="figure" />' },
|
|
179
|
+
{ code: '<div role="form" />' },
|
|
180
|
+
{ code: '<div role="group" />' },
|
|
181
|
+
{ code: '<div role="heading" />' },
|
|
182
|
+
{ code: '<div role="img" />' },
|
|
183
|
+
{ code: '<div role="list" />' },
|
|
184
|
+
{ code: '<div role="listitem" />' },
|
|
185
|
+
{ code: '<div role="log" />' },
|
|
186
|
+
{ code: '<div role="main" />' },
|
|
187
|
+
{ code: '<div role="marquee" />' },
|
|
188
|
+
{ code: '<div role="math" />' },
|
|
189
|
+
{ code: '<div role="navigation" />' },
|
|
190
|
+
{ code: '<div role="none" />' },
|
|
191
|
+
{ code: '<div role="note" />' },
|
|
192
|
+
{ code: '<div role="presentation" />' },
|
|
193
|
+
{ code: '<div role="region" />' },
|
|
194
|
+
{ code: '<div role="rowgroup" />' },
|
|
195
|
+
{ code: '<div role="search" />' },
|
|
196
|
+
{ code: '<div role="separator" />' },
|
|
197
|
+
{ code: '<div role="status" />' },
|
|
198
|
+
{ code: '<div role="table" />' },
|
|
199
|
+
{ code: '<div role="tabpanel" />' },
|
|
200
|
+
{ code: '<div role="term" />' },
|
|
201
|
+
{ code: '<div role="timer" />' },
|
|
202
|
+
{ code: '<div role="tooltip" />' },
|
|
203
|
+
// Via config
|
|
204
|
+
// Inputs. Ignore them because they might get a label from a wrapping label element.
|
|
205
|
+
{ code: '<input />' },
|
|
206
|
+
{ code: '<input type="button" />' },
|
|
207
|
+
{ code: '<input type="checkbox" />' },
|
|
208
|
+
{ code: '<input type="color" />' },
|
|
209
|
+
{ code: '<input type="date" />' },
|
|
210
|
+
{ code: '<input type="datetime" />' },
|
|
211
|
+
{ code: '<input type="email" />' },
|
|
212
|
+
{ code: '<input type="file" />' },
|
|
213
|
+
{ code: '<input type="hidden" />' },
|
|
214
|
+
{ code: '<input type="hidden" name="bot-field"/>' },
|
|
215
|
+
{ code: '<input type="hidden" name="form-name" value="Contact Form"/>' },
|
|
216
|
+
{ code: '<input type="image" />' },
|
|
217
|
+
{ code: '<input type="month" />' },
|
|
218
|
+
{ code: '<input type="number" />' },
|
|
219
|
+
{ code: '<input type="password" />' },
|
|
220
|
+
{ code: '<input type="radio" />' },
|
|
221
|
+
{ code: '<input type="range" />' },
|
|
222
|
+
{ code: '<input type="reset" />' },
|
|
223
|
+
{ code: '<input type="search" />' },
|
|
224
|
+
{ code: '<input type="submit" />' },
|
|
225
|
+
{ code: '<input type="tel" />' },
|
|
226
|
+
{ code: '<input type="text" />' },
|
|
227
|
+
{ code: '<label>Foo <input type="text" /></label>' },
|
|
228
|
+
{ code: '<input name={field.name} id="foo" type="text" value={field.value} disabled={isDisabled} onChange={changeText(field.onChange, field.name)} onBlur={field.onBlur} />' },
|
|
229
|
+
{ code: '<input type="time" />' },
|
|
230
|
+
{ code: '<input type="url" />' },
|
|
231
|
+
{ code: '<input type="week" />' },
|
|
232
|
+
// Marginal interactive elements. It is difficult to insist that these
|
|
233
|
+
// elements contain a text label.
|
|
234
|
+
{ code: '<audio />' },
|
|
235
|
+
{ code: '<canvas />' },
|
|
236
|
+
{ code: '<embed />' },
|
|
237
|
+
{ code: '<textarea />' },
|
|
238
|
+
{ code: '<tr />' },
|
|
239
|
+
{ code: '<video />' },
|
|
240
|
+
// Interactive roles to ignore
|
|
241
|
+
{ code: '<div role="grid" />' },
|
|
242
|
+
{ code: '<div role="listbox" />' },
|
|
243
|
+
{ code: '<div role="menu" />' },
|
|
244
|
+
{ code: '<div role="menubar" />' },
|
|
245
|
+
{ code: '<div role="radiogroup" />' },
|
|
246
|
+
{ code: '<div role="row" />' },
|
|
247
|
+
{ code: '<div role="tablist" />' },
|
|
248
|
+
{ code: '<div role="toolbar" />' },
|
|
249
|
+
{ code: '<div role="tree" />' },
|
|
250
|
+
{ code: '<div role="treegrid" />' },
|
|
251
|
+
];
|
|
252
|
+
const neverValid = [
|
|
253
|
+
{ code: '<button />', errors: [expectedError] },
|
|
254
|
+
{ code: '<button><span /></button>', errors: [expectedError] },
|
|
255
|
+
{ code: '<button><img /></button>', errors: [expectedError] },
|
|
256
|
+
{ code: '<button><span title="This is not a real label" /></button>', errors: [expectedError] },
|
|
257
|
+
{ code: '<button><span><span><span>Save</span></span></span></button>', options: [{ depth: 3 }], errors: [expectedError] },
|
|
258
|
+
{ code: '<CustomControl><span><span></span></span></CustomControl>', options: [{ depth: 3, controlComponents: ['CustomControl'] }], errors: [expectedError] },
|
|
259
|
+
{ code: '<CustomControl></CustomControl>', errors: [expectedError], settings: { 'jsx-a11y': { components: { CustomControl: 'button' } } } },
|
|
260
|
+
{ code: '<a href="#" />', errors: [expectedError] },
|
|
261
|
+
{ code: '<area href="#" />', errors: [expectedError] },
|
|
262
|
+
{ code: '<menuitem />', errors: [expectedError] },
|
|
263
|
+
{ code: '<option />', errors: [expectedError] },
|
|
264
|
+
{ code: '<th />', errors: [expectedError] },
|
|
265
|
+
{ code: '<td />', errors: [expectedError] },
|
|
266
|
+
// Interactive Roles
|
|
267
|
+
{ code: '<div role="button" />', errors: [expectedError] },
|
|
268
|
+
{ code: '<div role="checkbox" />', errors: [expectedError] },
|
|
269
|
+
{ code: '<div role="columnheader" />', errors: [expectedError] },
|
|
270
|
+
{ code: '<div role="combobox" />', errors: [expectedError] },
|
|
271
|
+
{ code: '<div role="link" />', errors: [expectedError] },
|
|
272
|
+
{ code: '<div role="gridcell" />', errors: [expectedError] },
|
|
273
|
+
{ code: '<div role="menuitem" />', errors: [expectedError] },
|
|
274
|
+
{ code: '<div role="menuitemcheckbox" />', errors: [expectedError] },
|
|
275
|
+
{ code: '<div role="menuitemradio" />', errors: [expectedError] },
|
|
276
|
+
{ code: '<div role="option" />', errors: [expectedError] },
|
|
277
|
+
{ code: '<div role="progressbar" />', errors: [expectedError] },
|
|
278
|
+
{ code: '<div role="radio" />', errors: [expectedError] },
|
|
279
|
+
{ code: '<div role="rowheader" />', errors: [expectedError] },
|
|
280
|
+
{ code: '<div role="scrollbar" />', errors: [expectedError] },
|
|
281
|
+
{ code: '<div role="searchbox" />', errors: [expectedError] },
|
|
282
|
+
{ code: '<div role="slider" />', errors: [expectedError] },
|
|
283
|
+
{ code: '<div role="spinbutton" />', errors: [expectedError] },
|
|
284
|
+
{ code: '<div role="switch" />', errors: [expectedError] },
|
|
285
|
+
{ code: '<div role="tab" />', errors: [expectedError] },
|
|
286
|
+
{ code: '<div role="textbox" />', errors: [expectedError] },
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const recommendedOptions = (configs.recommended.rules[ruleName][1] || {});
|
|
290
|
+
ruleTester.run(`${ruleName}:recommended`, rule, {
|
|
291
|
+
valid: parsers.all([].concat(
|
|
292
|
+
...alwaysValid,
|
|
293
|
+
))
|
|
294
|
+
.map(ruleOptionsMapperFactory(recommendedOptions))
|
|
295
|
+
.map(parserOptionsMapper),
|
|
296
|
+
invalid: parsers.all([].concat(
|
|
297
|
+
...neverValid,
|
|
298
|
+
))
|
|
299
|
+
.map(ruleOptionsMapperFactory(recommendedOptions))
|
|
300
|
+
.map(parserOptionsMapper),
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const strictOptions = (configs.strict.rules[ruleName][1] || {});
|
|
304
|
+
ruleTester.run(`${ruleName}:strict`, rule, {
|
|
305
|
+
valid: parsers.all([].concat(
|
|
306
|
+
...alwaysValid,
|
|
307
|
+
))
|
|
308
|
+
.map(ruleOptionsMapperFactory(strictOptions))
|
|
309
|
+
.map(parserOptionsMapper),
|
|
310
|
+
invalid: parsers.all([].concat(
|
|
311
|
+
...neverValid,
|
|
312
|
+
))
|
|
313
|
+
.map(ruleOptionsMapperFactory(strictOptions))
|
|
314
|
+
.map(parserOptionsMapper),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
ruleTester.run(`${ruleName}:no-config`, rule, {
|
|
318
|
+
valid: parsers.all([].concat(
|
|
319
|
+
{ code: '<input type="hidden" />' },
|
|
320
|
+
{ code: '<input type="text" aria-hidden="true" />' },
|
|
321
|
+
))
|
|
322
|
+
.map(parserOptionsMapper),
|
|
323
|
+
invalid: parsers.all([].concat(
|
|
324
|
+
{ code: '<input type="text" />', errors: [expectedError] },
|
|
325
|
+
))
|
|
326
|
+
.map(parserOptionsMapper),
|
|
327
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce heading (h1, h2, etc) elements contain accessible content.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
12
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
13
|
+
import rule from '../../../src/rules/heading-has-content';
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Tests
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const ruleTester = new RuleTester();
|
|
20
|
+
|
|
21
|
+
const expectedError = {
|
|
22
|
+
message: 'Headings must have content and the content must be accessible by a screen reader.',
|
|
23
|
+
type: 'JSXOpeningElement',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const components = [{
|
|
27
|
+
components: ['Heading', 'Title'],
|
|
28
|
+
}];
|
|
29
|
+
|
|
30
|
+
const componentsSettings = {
|
|
31
|
+
'jsx-a11y': {
|
|
32
|
+
components: {
|
|
33
|
+
CustomInput: 'input',
|
|
34
|
+
Title: 'h1',
|
|
35
|
+
Heading: 'h2',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
ruleTester.run('heading-has-content', rule, {
|
|
41
|
+
valid: parsers.all([].concat(
|
|
42
|
+
// DEFAULT ELEMENT TESTS
|
|
43
|
+
{ code: '<div />;' },
|
|
44
|
+
{ code: '<h1>Foo</h1>' },
|
|
45
|
+
{ code: '<h2>Foo</h2>' },
|
|
46
|
+
{ code: '<h3>Foo</h3>' },
|
|
47
|
+
{ code: '<h4>Foo</h4>' },
|
|
48
|
+
{ code: '<h5>Foo</h5>' },
|
|
49
|
+
{ code: '<h6>Foo</h6>' },
|
|
50
|
+
{ code: '<h6>123</h6>' },
|
|
51
|
+
{ code: '<h1><Bar /></h1>' },
|
|
52
|
+
{ code: '<h1>{foo}</h1>' },
|
|
53
|
+
{ code: '<h1>{foo.bar}</h1>' },
|
|
54
|
+
{ code: '<h1 dangerouslySetInnerHTML={{ __html: "foo" }} />' },
|
|
55
|
+
{ code: '<h1 children={children} />' },
|
|
56
|
+
// CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION
|
|
57
|
+
{ code: '<Heading>Foo</Heading>', options: components },
|
|
58
|
+
{ code: '<Title>Foo</Title>', options: components },
|
|
59
|
+
{ code: '<Heading><Bar /></Heading>', options: components },
|
|
60
|
+
{ code: '<Heading>{foo}</Heading>', options: components },
|
|
61
|
+
{ code: '<Heading>{foo.bar}</Heading>', options: components },
|
|
62
|
+
{ code: '<Heading dangerouslySetInnerHTML={{ __html: "foo" }} />', options: components },
|
|
63
|
+
{ code: '<Heading children={children} />', options: components },
|
|
64
|
+
{ code: '<h1 aria-hidden />' },
|
|
65
|
+
// CUSTOM ELEMENT TESTS FOR COMPONENTS SETTINGS
|
|
66
|
+
{ code: '<Heading>Foo</Heading>', settings: componentsSettings },
|
|
67
|
+
{ code: '<h1><CustomInput type="hidden" /></h1>' },
|
|
68
|
+
)).map(parserOptionsMapper),
|
|
69
|
+
invalid: parsers.all([].concat(
|
|
70
|
+
// DEFAULT ELEMENT TESTS
|
|
71
|
+
{ code: '<h1 />', errors: [expectedError] },
|
|
72
|
+
{ code: '<h1><Bar aria-hidden /></h1>', errors: [expectedError] },
|
|
73
|
+
{ code: '<h1>{undefined}</h1>', errors: [expectedError] },
|
|
74
|
+
{ code: '<h1><input type="hidden" /></h1>', errors: [expectedError] },
|
|
75
|
+
|
|
76
|
+
// CUSTOM ELEMENT TESTS FOR COMPONENTS OPTION
|
|
77
|
+
{ code: '<Heading />', errors: [expectedError], options: components },
|
|
78
|
+
{ code: '<Heading><Bar aria-hidden /></Heading>', errors: [expectedError], options: components },
|
|
79
|
+
{ code: '<Heading>{undefined}</Heading>', errors: [expectedError], options: components },
|
|
80
|
+
|
|
81
|
+
// CUSTOM ELEMENT TESTS FOR COMPONENTS SETTINGS
|
|
82
|
+
{ code: '<Heading />', errors: [expectedError], settings: componentsSettings },
|
|
83
|
+
{ code: '<h1><CustomInput type="hidden" /></h1>', errors: [expectedError], settings: componentsSettings },
|
|
84
|
+
)).map(parserOptionsMapper),
|
|
85
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce html element has lang prop.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
12
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
13
|
+
import rule from '../../../src/rules/html-has-lang';
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Tests
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const ruleTester = new RuleTester();
|
|
20
|
+
|
|
21
|
+
const expectedError = {
|
|
22
|
+
message: '<html> elements must have the lang prop.',
|
|
23
|
+
type: 'JSXOpeningElement',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
ruleTester.run('html-has-lang', rule, {
|
|
27
|
+
valid: parsers.all([].concat(
|
|
28
|
+
{ code: '<div />;' },
|
|
29
|
+
{ code: '<html lang="en" />' },
|
|
30
|
+
{ code: '<html lang="en-US" />' },
|
|
31
|
+
{ code: '<html lang={foo} />' },
|
|
32
|
+
{ code: '<html lang />' },
|
|
33
|
+
{ code: '<HTML />' },
|
|
34
|
+
{ code: '<HTMLTop lang="en" />', errors: [expectedError], settings: { 'jsx-a11y': { components: { HTMLTop: 'html' } } } },
|
|
35
|
+
)).map(parserOptionsMapper),
|
|
36
|
+
invalid: parsers.all([].concat(
|
|
37
|
+
{ code: '<html />', errors: [expectedError] },
|
|
38
|
+
{ code: '<html {...props} />', errors: [expectedError] },
|
|
39
|
+
{ code: '<html lang={undefined} />', errors: [expectedError] },
|
|
40
|
+
{ code: '<HTMLTop />', errors: [expectedError], settings: { 'jsx-a11y': { components: { HTMLTop: 'html' } } } },
|
|
41
|
+
)).map(parserOptionsMapper),
|
|
42
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce iframe elements have a title attribute.
|
|
3
|
+
* @author Ethan Cohen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
// Requirements
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import { RuleTester } from 'eslint';
|
|
11
|
+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
|
|
12
|
+
import parsers from '../../__util__/helpers/parsers';
|
|
13
|
+
import rule from '../../../src/rules/iframe-has-title';
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Tests
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const ruleTester = new RuleTester();
|
|
20
|
+
|
|
21
|
+
const expectedError = {
|
|
22
|
+
message: '<iframe> elements must have a unique title property.',
|
|
23
|
+
type: 'JSXOpeningElement',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const componentsSettings = {
|
|
27
|
+
'jsx-a11y': {
|
|
28
|
+
components: {
|
|
29
|
+
FooComponent: 'iframe',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
ruleTester.run('html-has-lang', rule, {
|
|
35
|
+
valid: parsers.all([].concat(
|
|
36
|
+
{ code: '<div />;' },
|
|
37
|
+
{ code: '<iframe title="Unique title" />' },
|
|
38
|
+
{ code: '<iframe title={foo} />' },
|
|
39
|
+
{ code: '<FooComponent />' },
|
|
40
|
+
{ code: '<FooComponent title="Unique title" />', settings: componentsSettings },
|
|
41
|
+
)).map(parserOptionsMapper),
|
|
42
|
+
invalid: parsers.all([].concat(
|
|
43
|
+
{ code: '<iframe />', errors: [expectedError] },
|
|
44
|
+
{ code: '<iframe {...props} />', errors: [expectedError] },
|
|
45
|
+
{ code: '<iframe title={undefined} />', errors: [expectedError] },
|
|
46
|
+
{ code: '<iframe title="" />', errors: [expectedError] },
|
|
47
|
+
{ code: '<iframe title={false} />', errors: [expectedError] },
|
|
48
|
+
{ code: '<iframe title={true} />', errors: [expectedError] },
|
|
49
|
+
{ code: "<iframe title={''} />", errors: [expectedError] },
|
|
50
|
+
{ code: '<iframe title={``} />', errors: [expectedError] },
|
|
51
|
+
{ code: '<iframe title={""} />', errors: [expectedError] },
|
|
52
|
+
{ code: '<iframe title={42} />', errors: [expectedError] },
|
|
53
|
+
{ code: '<FooComponent />', errors: [expectedError], settings: componentsSettings },
|
|
54
|
+
)).map(parserOptionsMapper),
|
|
55
|
+
});
|