Skip to content

Commit 3d8fa26

Browse files
authored
Merge branch 'master' into label-has-associated-control
2 parents 8f1a374 + 20e894a commit 3d8fa26

26 files changed

+259
-164
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
language: node_js
22
node_js:
3+
- "10"
4+
- "9"
35
- "8"
46
- "7"
57
- "6"
@@ -27,5 +29,6 @@ matrix:
2729
- node_js: "node"
2830
env: LINT=true TEST=false
2931
allow_failures:
32+
- node_js: "9"
3033
- node_js: "7"
3134
- node_js: "5"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ You can also see a text-based version of the AX Tree in Chrome in the stable rel
291291
### Pulling it all together
292292
A browser constructs an AX Tree as a subset of the DOM. ARIA heavily informs the properties of this AX Tree. This AX Tree is exposed to the system level Accessibility API which mediates assistive technology agents.
293293

294-
We model ARIA in the [aria-query](https://github.com/a11yance/aria-query) project. We model AXObjects (that comprise the AX Tree) in the [axobject-query](https://github.com/A11yance/axobject-query) project. The goal of the WAI-ARIA specification is to be a complete complete declarative interface to the AXObject model. The [in-draft 1.2 version](https://github.com/w3c/aria/issues?q=is%3Aissue+is%3Aopen+label%3A%22ARIA+1.2%22) is moving towards this goal. But until then, we must consider the semantics constructs affored by ARIA as well as those afforded by the AXObject model (AXAPI) in order to determine how HTML can be used to express user interface affordances to assistive technology users.
294+
We model ARIA in the [aria-query](https://github.com/a11yance/aria-query) project. We model AXObjects (that comprise the AX Tree) in the [axobject-query](https://github.com/A11yance/axobject-query) project. The goal of the WAI-ARIA specification is to be a complete declarative interface to the AXObject model. The [in-draft 1.2 version](https://github.com/w3c/aria/issues?q=is%3Aissue+is%3Aopen+label%3A%22ARIA+1.2%22) is moving towards this goal. But until then, we must consider the semantics constructs affored by ARIA as well as those afforded by the AXObject model (AXAPI) in order to determine how HTML can be used to express user interface affordances to assistive technology users.
295295

296296
## License
297297

__tests__/src/rules/label-has-for-test.js

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,22 @@ import rule from '../../../src/rules/label-has-for';
1919

2020
const ruleTester = new RuleTester();
2121

22-
const expectedError = {
23-
message: 'Form label must have associated control',
22+
const expectedNestingError = {
23+
message: 'Form label must have the following type of associated control: nesting',
2424
type: 'JSXOpeningElement',
2525
};
2626

27+
const expectedSomeError = {
28+
message: 'Form label must have ANY of the following types of associated control: nesting, id',
29+
type: 'JSXOpeningElement',
30+
};
31+
32+
const expectedEveryError = {
33+
message: 'Form label must have ALL of the following types of associated control: nesting, id',
34+
type: 'JSXOpeningElement',
35+
};
36+
37+
2738
const array = [{
2839
components: ['Label', 'Descriptor'],
2940
}];
@@ -81,78 +92,88 @@ ruleTester.run('label-has-for', rule, {
8192
].map(parserOptionsMapper),
8293
invalid: [
8394
// DEFAULT ELEMENT 'label' TESTS
84-
{ code: '<label id="foo" />', errors: [expectedError] },
85-
{ code: '<label htmlFor={undefined} />', errors: [expectedError] },
86-
{ code: '<label htmlFor={`${undefined}`} />', errors: [expectedError] },
87-
{ code: '<label>First Name</label>', errors: [expectedError] },
88-
{ code: '<label {...props}>Foo</label>', errors: [expectedError] },
89-
{ code: '<label><input /></label>', errors: [expectedError] },
90-
{ code: '<label>{children}</label>', errors: [expectedError] },
91-
{ code: '<label htmlFor="foo" />', errors: [expectedError] },
92-
{ code: '<label htmlFor={"foo"} />', errors: [expectedError] },
93-
{ code: '<label htmlFor={foo} />', errors: [expectedError] },
94-
{ code: '<label htmlFor={`${id}`} />', errors: [expectedError] },
95-
{ code: '<label htmlFor="foo">Test!</label>', errors: [expectedError] },
95+
{ code: '<label id="foo" />', errors: [expectedEveryError] },
96+
{ code: '<label htmlFor={undefined} />', errors: [expectedEveryError] },
97+
{ code: '<label htmlFor={`${undefined}`} />', errors: [expectedEveryError] },
98+
{ code: '<label>First Name</label>', errors: [expectedEveryError] },
99+
{ code: '<label {...props}>Foo</label>', errors: [expectedEveryError] },
100+
{ code: '<label><input /></label>', errors: [expectedEveryError] },
101+
{ code: '<label>{children}</label>', errors: [expectedEveryError] },
102+
{ code: '<label htmlFor="foo" />', errors: [expectedEveryError] },
103+
{ code: '<label htmlFor={"foo"} />', errors: [expectedEveryError] },
104+
{ code: '<label htmlFor={foo} />', errors: [expectedEveryError] },
105+
{ code: '<label htmlFor={`${id}`} />', errors: [expectedEveryError] },
106+
{ code: '<label htmlFor="foo">Test!</label>', errors: [expectedEveryError] },
96107
//
97108
// // CUSTOM ELEMENT ARRAY OPTION TESTS
98-
{ code: '<Label></Label>', errors: [expectedError], options: array },
99-
{ code: '<Label htmlFor="foo" />', errors: [expectedError], options: array },
100-
{ code: '<Label htmlFor={"foo"} />', errors: [expectedError], options: array },
101-
{ code: '<Label htmlFor={foo} />', errors: [expectedError], options: array },
102-
{ code: '<Label htmlFor={`${id}`} />', errors: [expectedError], options: array },
103-
{ code: '<Label htmlFor="foo">Test!</Label>', errors: [expectedError], options: array },
104-
{ code: '<Descriptor htmlFor="foo" />', errors: [expectedError], options: array },
105-
{ code: '<Descriptor htmlFor={"foo"} />', errors: [expectedError], options: array },
106-
{ code: '<Descriptor htmlFor={foo} />', errors: [expectedError], options: array },
107-
{ code: '<Descriptor htmlFor={`${id}`} />', errors: [expectedError], options: array },
109+
{ code: '<Label></Label>', errors: [expectedEveryError], options: array },
110+
{ code: '<Label htmlFor="foo" />', errors: [expectedEveryError], options: array },
111+
{ code: '<Label htmlFor={"foo"} />', errors: [expectedEveryError], options: array },
112+
{ code: '<Label htmlFor={foo} />', errors: [expectedEveryError], options: array },
113+
{ code: '<Label htmlFor={`${id}`} />', errors: [expectedEveryError], options: array },
114+
{ code: '<Label htmlFor="foo">Test!</Label>', errors: [expectedEveryError], options: array },
115+
{ code: '<Descriptor htmlFor="foo" />', errors: [expectedEveryError], options: array },
116+
{ code: '<Descriptor htmlFor={"foo"} />', errors: [expectedEveryError], options: array },
117+
{ code: '<Descriptor htmlFor={foo} />', errors: [expectedEveryError], options: array },
118+
{ code: '<Descriptor htmlFor={`${id}`} />', errors: [expectedEveryError], options: array },
108119
{
109120
code: '<Descriptor htmlFor="foo">Test!</Descriptor>',
110-
errors: [expectedError],
121+
errors: [expectedEveryError],
111122
options: array,
112123
},
113-
{ code: '<Label id="foo" />', errors: [expectedError], options: array },
124+
{ code: '<Label id="foo" />', errors: [expectedEveryError], options: array },
114125
{
115126
code: '<Label htmlFor={undefined} />',
116-
errors: [expectedError],
127+
errors: [expectedEveryError],
117128
options: array,
118129
},
119130
{
120131
code: '<Label htmlFor={`${undefined}`} />',
121-
errors: [expectedError],
132+
errors: [expectedEveryError],
122133
options: array,
123134
},
124-
{ code: '<Label>First Name</Label>', errors: [expectedError], options: array },
135+
{ code: '<Label>First Name</Label>', errors: [expectedEveryError], options: array },
125136
{
126137
code: '<Label {...props}>Foo</Label>',
127-
errors: [expectedError],
138+
errors: [expectedEveryError],
128139
options: array,
129140
},
130-
{ code: '<Descriptor id="foo" />', errors: [expectedError], options: array },
141+
{ code: '<Descriptor id="foo" />', errors: [expectedEveryError], options: array },
131142
{
132143
code: '<Descriptor htmlFor={undefined} />',
133-
errors: [expectedError],
144+
errors: [expectedEveryError],
134145
options: array,
135146
},
136147
{
137148
code: '<Descriptor htmlFor={`${undefined}`} />',
138-
errors: [expectedError],
149+
errors: [expectedEveryError],
139150
options: array,
140151
},
141152
{
142153
code: '<Descriptor>First Name</Descriptor>',
143-
errors: [expectedError],
154+
errors: [expectedEveryError],
144155
options: array,
145156
},
146157
{
147158
code: '<Descriptor {...props}>Foo</Descriptor>',
148-
errors: [expectedError],
159+
errors: [expectedEveryError],
149160
options: array,
150161
},
151-
{ code: '<label>{children}</label>', errors: [expectedError], options: array },
152-
{ code: '<label htmlFor="foo" />', errors: [expectedError], options: optionsRequiredNesting },
153-
{ code: '<label>First Name</label>', errors: [expectedError], options: optionsRequiredNesting },
154-
{ code: '<label>First Name</label>', errors: [expectedError], options: optionsRequiredSome },
155-
{ code: '<label>{children}</label>', errors: [expectedError], options: optionsRequiredSome },
156-
{ code: '<label>{children}</label>', errors: [expectedError], options: optionsRequiredNesting },
162+
{ code: '<label>{children}</label>', errors: [expectedEveryError], options: array },
163+
{ code: '<label htmlFor="foo" />', errors: [expectedNestingError], options: optionsRequiredNesting },
164+
{ code: '<label>First Name</label>', errors: [expectedNestingError], options: optionsRequiredNesting },
165+
{ code: '<label>First Name</label>', errors: [expectedSomeError], options: optionsRequiredSome },
166+
{ code: '<label>{children}</label>', errors: [expectedSomeError], options: optionsRequiredSome },
167+
{ code: '<label>{children}</label>', errors: [expectedNestingError], options: optionsRequiredNesting },
168+
{
169+
code: '<form><input type="text" id="howmuch" value="1" /><label htmlFor="howmuch">How much ?</label></form>',
170+
errors: [expectedEveryError],
171+
options: optionsRequiredEvery,
172+
},
173+
{
174+
code: '<form><input type="text" id="howmuch" value="1" /><label htmlFor="howmuch">How much ?<span /></label></form>',
175+
errors: [expectedEveryError],
176+
options: optionsRequiredEvery,
177+
},
157178
].map(parserOptionsMapper),
158179
});

__tests__/src/rules/media-has-caption-test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ ruleTester.run('media-has-caption', rule, {
4646
code: '<video><track kind="Captions" /><track kind="subtitles" /></video>',
4747
},
4848
{
49-
code: '<audio mute={true}></audio>',
49+
code: '<audio muted={true}></audio>',
5050
},
5151
{
52-
code: '<video mute={true}></video>',
52+
code: '<video muted={true}></video>',
5353
},
5454
{
55-
code: '<video mute></video>',
55+
code: '<video muted></video>',
5656
},
5757
{
5858
code: '<Audio><track kind="captions" /></Audio>',
@@ -79,19 +79,19 @@ ruleTester.run('media-has-caption', rule, {
7979
options: customSchema,
8080
},
8181
{
82-
code: '<Video mute></Video>',
82+
code: '<Video muted></Video>',
8383
options: customSchema,
8484
},
8585
{
86-
code: '<Video mute={true}></Video>',
86+
code: '<Video muted={true}></Video>',
8787
options: customSchema,
8888
},
8989
{
90-
code: '<Audio mute></Audio>',
90+
code: '<Audio muted></Audio>',
9191
options: customSchema,
9292
},
9393
{
94-
code: '<Audio mute={true}></Audio>',
94+
code: '<Audio muted={true}></Audio>',
9595
options: customSchema,
9696
},
9797
].map(parserOptionsMapper),
@@ -108,12 +108,12 @@ ruleTester.run('media-has-caption', rule, {
108108
errors: [expectedError],
109109
},
110110
{
111-
code: '<Audio mute={false}></Audio>',
111+
code: '<Audio muted={false}></Audio>',
112112
options: customSchema,
113113
errors: [expectedError],
114114
},
115115
{
116-
code: '<Video mute={false}></Video>',
116+
code: '<Video muted={false}></Video>',
117117
options: customSchema,
118118
errors: [expectedError],
119119
},

__tests__/src/rules/no-redundant-roles-test.js

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import { RuleTester } from 'eslint';
1313
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
1414
import rule from '../../../src/rules/no-redundant-roles';
15+
import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
1516

1617
// -----------------------------------------------------------------------------
1718
// Tests
@@ -24,16 +25,41 @@ const expectedError = (element, implicitRole) => ({
2425
type: 'JSXOpeningElement',
2526
});
2627

27-
ruleTester.run('no-redundant-roles', rule, {
28+
const ruleName = 'jsx-a11y/no-redundant-roles';
29+
30+
const alwaysValid = [
31+
{ code: '<div />;' },
32+
{ code: '<button role="main" />' },
33+
{ code: '<MyComponent role="button" />' },
34+
{ code: '<button role={`${foo}button`} />' },
35+
];
36+
37+
const neverValid = [
38+
{ code: '<button role="button" />', errors: [expectedError('button', 'button')] },
39+
{ code: '<body role="DOCUMENT" />', errors: [expectedError('body', 'document')] },
40+
{ code: '<button role={`${undefined}button`} />', errors: [expectedError('button', 'button')] },
41+
];
42+
43+
ruleTester.run(`${ruleName}:recommended`, rule, {
2844
valid: [
29-
{ code: '<div />;' },
30-
{ code: '<button role="main" />' },
31-
{ code: '<MyComponent role="button" />' },
32-
{ code: '<button role={`${foo}button`} />' },
33-
].map(parserOptionsMapper),
45+
...alwaysValid,
46+
{ code: '<nav role="navigation" />' },
47+
]
48+
.map(parserOptionsMapper),
49+
invalid: neverValid
50+
.map(parserOptionsMapper),
51+
});
52+
53+
const noNavExceptionsOptions = { nav: [] };
54+
55+
ruleTester.run(`${ruleName}:recommended`, rule, {
56+
valid: alwaysValid
57+
.map(ruleOptionsMapperFactory(noNavExceptionsOptions))
58+
.map(parserOptionsMapper),
3459
invalid: [
35-
{ code: '<button role="button" />', errors: [expectedError('button', 'button')] },
36-
{ code: '<body role="DOCUMENT" />', errors: [expectedError('body', 'document')] },
37-
{ code: '<button role={`${undefined}button`} />', errors: [expectedError('button', 'button')] },
38-
].map(parserOptionsMapper),
60+
...neverValid,
61+
{ code: '<nav role="navigation" />', errors: [expectedError('nav', 'navigation')] },
62+
]
63+
.map(ruleOptionsMapperFactory(noNavExceptionsOptions))
64+
.map(parserOptionsMapper),
3965
});

__tests__/src/util/hasAccessibleChild-test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ describe('hasAccessibleChild', () => {
3636
expect(hasAccessibleChild(element)).toBe(true);
3737
});
3838

39+
it('Returns true for JSXText Element', () => {
40+
const child = {
41+
type: 'JSXText',
42+
value: 'foo',
43+
};
44+
const element = JSXElementMock('div', [], [child]);
45+
expect(hasAccessibleChild(element)).toBe(true);
46+
});
47+
3948
it('Returns false for hidden child JSXElement', () => {
4049
const ariaHiddenAttr = JSXAttributeMock('aria-hidden', true);
4150
const child = JSXElementMock('div', [ariaHiddenAttr]);

docs/rules/accessible-emoji.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Emojis have become a common way of communicating content to the end user. To a person using a screenreader, however, he/she may not be aware that this content is there at all. By wrapping the emoji in a `<span>`, giving it the `role="img"`, and providing a useful description in `aria-label`, the screenreader will treat the emoji as an image in the accessibility tree with an accessible name for the end user.
44

55
#### Resources
6-
1. [Lèonie Watson](http://tink.uk/accessible-emoji/)
6+
1. [Léonie Watson](http://tink.uk/accessible-emoji/)
77

88
## Rule details
99

docs/rules/alt-text.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ An `<img>` must have the `alt` prop set with meaningful text or as an empty stri
1414

1515
For images that are being used as icons for a button or control, the `alt` prop should be set to an empty string (`alt=""`).
1616

17-
```js
17+
```jsx
1818
<button>
1919
<img src="icon.png" alt="" />
2020
Save
@@ -38,7 +38,7 @@ This rule takes one optional object argument of type object:
3838
```json
3939
{
4040
"rules": {
41-
"jsx-a11y/img-has-alt": [ 2, {
41+
"jsx-a11y/alt-text": [ 2, {
4242
"elements": [ "img", "object", "area", "input[type=\"image\"]" ],
4343
"img": ["Image"],
4444
"object": ["Object"],

docs/rules/aria-role.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# aria-role
22

3-
Elements with ARIA roles must use a valid, non-abstract ARIA role. A reference to role defintions can be found at [WAI-ARIA](https://www.w3.org/TR/wai-aria/roles#role_definitions) site.
3+
Elements with ARIA roles must use a valid, non-abstract ARIA role. A reference to role defintions can be found at [WAI-ARIA](https://www.w3.org/TR/wai-aria/#role_definitions) site.
44

55
[AX_ARIA_01](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_01)
66
[DPUB-ARIA roles](https://www.w3.org/TR/dpub-aria-1.0/)

docs/rules/media-has-caption.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Providing captions for media is essential for deaf users to follow along. Captions should be a transcription or translation of the dialogue, sound effects, relevant musical cues, and other relevant audio information. Not only is this important for accessibility, but can also be useful for all users in the case that the media is unavailable (similar to `alt` text on an image when an image is unable to load).
44

5-
The captions should contain all important and relevant information to understand the corresponding media. This may mean that the captions are not a 1:1 mapping of the dialogue in the media content. However, captions are *not* necessary for video components with the mute attribute.
5+
The captions should contain all important and relevant information to understand the corresponding media. This may mean that the captions are not a 1:1 mapping of the dialogue in the media content. However, captions are *not* necessary for video components with the `muted` attribute.
66

77
### References
88

@@ -31,7 +31,7 @@ For the `audio`, `video`, and `track` options, these strings determine which JSX
3131
```jsx
3232
<audio><track kind="captions" {...props} /></audio>
3333
<video><track kind="captions" {...props} /></video>
34-
<video mute {...props} ></video>
34+
<video muted {...props} ></video>
3535
```
3636

3737
### Fail

0 commit comments

Comments
 (0)