diff --git a/__tests__/src/rules/anchor-ambiguous-text-test.js b/__tests__/src/rules/anchor-ambiguous-text-test.js index fbd52e9ce..7706bb44c 100644 --- a/__tests__/src/rules/anchor-ambiguous-text-test.js +++ b/__tests__/src/rules/anchor-ambiguous-text-test.js @@ -39,6 +39,7 @@ ruleTester.run('anchor-ambiguous-text', rule, { { code: '${here};' }, { code: 'click here;' }, { code: 'click here;' }, + { code: 'documentation;' }, { code: 'click here', options: [{ @@ -49,6 +50,10 @@ ruleTester.run('anchor-ambiguous-text', rule, { code: 'documentation;', settings: { 'jsx-a11y': { components: { Link: 'a' } } }, }, + { + code: 'documentation;', + settings: { 'jsx-a11y': { components: { Image: 'img' } } }, + }, { code: '${here};', settings: { 'jsx-a11y': { components: { Link: 'a' } } }, @@ -80,12 +85,20 @@ ruleTester.run('anchor-ambiguous-text', rule, { { code: ' click here;', errors: [expectedError] }, { code: 'more textlearn more;', errors: [expectedError] }, { code: 'learn more;', errors: [expectedError] }, + { code: 'click here;', errors: [expectedError] }, + { code: 'click here;', errors: [expectedError] }, + { code: 'click here;', errors: [expectedError] }, { code: 'click here;', errors: [expectedError] }, { code: 'here', errors: [expectedError], settings: { 'jsx-a11y': { components: { Link: 'a' } } }, }, + { + code: 'click here', + errors: [expectedError], + settings: { 'jsx-a11y': { components: { Image: 'img' } } }, + }, { code: 'a disallowed word', errors: [expectedErrorGenerator(['a disallowed word'])], diff --git a/__tests__/src/util/getAccessibleChildText-test.js b/__tests__/src/util/getAccessibleChildText-test.js index 02a0b4e1d..6b67db4e0 100644 --- a/__tests__/src/util/getAccessibleChildText-test.js +++ b/__tests__/src/util/getAccessibleChildText-test.js @@ -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', diff --git a/docs/rules/anchor-ambiguous-text.md b/docs/rules/anchor-ambiguous-text.md index ae7bbd45d..d9888a462 100644 --- a/docs/rules/anchor-ambiguous-text.md +++ b/docs/rules/anchor-ambiguous-text.md @@ -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 `` 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**. @@ -46,6 +52,8 @@ Note that this rule is case-insensitive and trims whitespace. It only looks for a link learn more // skips over elements with aria-hidden=true something // the aria-label here is inaccessible +click here // the alt tag is still ambiguous +click here // the alt tag is only parsed on img ``` ## Accessibility guidelines diff --git a/src/util/getAccessibleChildText.js b/src/util/getAccessibleChildText.js index 9763db1a8..b25fd6139 100644 --- a/src/util/getAccessibleChildText.js +++ b/src/util/getAccessibleChildText.js @@ -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(