Skip to content

Commit

Permalink
[New] anchor-ambiguous-text, getAccessibleChildText: Implements c…
Browse files Browse the repository at this point in the history
…heck for `alt` tags on `<img />` elements
  • Loading branch information
mattxwang authored and ljharb committed Jul 27, 2022
1 parent c9687cc commit bb84abc
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 1 deletion.
13 changes: 13 additions & 0 deletions __tests__/src/rules/anchor-ambiguous-text-test.js
Expand Up @@ -39,6 +39,7 @@ ruleTester.run('anchor-ambiguous-text', rule, {
{ code: '<a>${here}</a>;' },
{ code: '<a aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</a>;' },
{ code: '<a><span aria-label="tutorial on using eslint-plugin-jsx-a11y">click here</span></a>;' },
{ code: '<a><img alt="documentation" /></a>;' },
{
code: '<a>click here</a>',
options: [{
Expand All @@ -49,6 +50,10 @@ ruleTester.run('anchor-ambiguous-text', rule, {
code: '<Link>documentation</Link>;',
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
},
{
code: '<a><Image alt="documentation" /></a>;',
settings: { 'jsx-a11y': { components: { Image: 'img' } } },
},
{
code: '<Link>${here}</Link>;',
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
Expand Down Expand Up @@ -80,12 +85,20 @@ ruleTester.run('anchor-ambiguous-text', rule, {
{ code: '<a><span> click </span> here</a>;', errors: [expectedError] },
{ code: '<a><span aria-hidden>more text</span>learn more</a>;', errors: [expectedError] },
{ code: '<a><span aria-hidden="true">more text</span>learn more</a>;', errors: [expectedError] },
{ code: '<a><img alt="click here"/></a>;', errors: [expectedError] },
{ code: '<a alt="tutorial on using eslint-plugin-jsx-a11y">click here</a>;', errors: [expectedError] },
{ code: '<a><span alt="tutorial on using eslint-plugin-jsx-a11y">click here</span></a>;', errors: [expectedError] },
{ code: '<a><CustomElement>click</CustomElement> here</a>;', errors: [expectedError] },
{
code: '<Link>here</Link>',
errors: [expectedError],
settings: { 'jsx-a11y': { components: { Link: 'a' } } },
},
{
code: '<a><Image alt="click here" /></a>',
errors: [expectedError],
settings: { 'jsx-a11y': { components: { Image: 'img' } } },
},
{
code: '<a>a disallowed word</a>',
errors: [expectedErrorGenerator(['a disallowed word'])],
Expand Down
21 changes: 21 additions & 0 deletions __tests__/src/util/getAccessibleChildText-test.js
Expand Up @@ -35,6 +35,27 @@ describe('getAccessibleChildText', () => {
), elementType)).toBe('bar');
});

it('returns alt text for img child', () => {
expect(getAccessibleChildText(JSXElementMock(
'a',
[],
[JSXElementMock('img', [
JSXAttributeMock('src', 'some/path'),
JSXAttributeMock('alt', 'a sensible label'),
])],
), elementType)).toBe('a sensible label');
});

it('returns blank when alt tag is used on arbitrary element', () => {
expect(getAccessibleChildText(JSXElementMock(
'a',
[],
[JSXElementMock('span', [
JSXAttributeMock('alt', 'a sensible label'),
])],
), elementType)).toBe('');
});

it('returns literal value for JSXText child', () => {
expect(getAccessibleChildText(JSXElementMock(
'a',
Expand Down
10 changes: 9 additions & 1 deletion docs/rules/anchor-ambiguous-text.md
Expand Up @@ -22,7 +22,13 @@ The `words` option allows users to modify the strings that can be checked for in
const DEFAULT_AMBIGUOUS_WORDS = ['click here', 'here', 'link', 'a link', 'learn more'];
```

If an element has the `aria-label` property, its value is used instead of the inner text. Note that the rule still disallows ambiguous `aria-label`s. This rule also skips over elements with `aria-hidden="true"`.
The logic to calculate the inner text of an anchor is as follows:

- if an element has the `aria-label` property, its value is used instead of the inner text
- if an element has `aria-hidden="true`, it is skipped over
- if an element is `<img />` or configured to be interpreted like one, its `alt` value is used as its inner text

Note that this rule still disallows ambiguous `aria-label` or `alt` values.

Note that this rule is case-insensitive and trims whitespace. It only looks for **exact matches**.

Expand All @@ -46,6 +52,8 @@ Note that this rule is case-insensitive and trims whitespace. It only looks for
<a><i></i>a link</a>
<a><span aria-hidden="true">more text</span>learn more</a> // skips over elements with aria-hidden=true
<a aria-label="click here">something</a> // the aria-label here is inaccessible
<a><img alt="click here"/></a> // the alt tag is still ambiguous
<a alt="tutorial on using eslint-plugin-jsx-a11y">click here</a> // the alt tag is only parsed on img
```

## Accessibility guidelines
Expand Down
4 changes: 4 additions & 0 deletions src/util/getAccessibleChildText.js
Expand Up @@ -29,6 +29,10 @@ export default function getAccessibleChildText(node: JSXElement, elementType: (J
// early escape-hatch when aria-label is applied
if (ariaLabel) return standardizeSpaceAndCase(ariaLabel);

// early-return if alt prop exists and is an image
const altTag = getLiteralPropValue(getProp(node.openingElement.attributes, 'alt'));
if (elementType(node.openingElement) === 'img' && altTag) return standardizeSpaceAndCase(altTag);

// skip if aria-hidden is true
if (
isHiddenFromScreenReader(
Expand Down

0 comments on commit bb84abc

Please sign in to comment.